diff --git a/.github/workflows/stales.yml b/.github/workflows/stales-issues.yml
similarity index 60%
rename from .github/workflows/stales.yml
rename to .github/workflows/stales-issues.yml
index 6d033c2d..5b371568 100644
--- a/.github/workflows/stales.yml
+++ b/.github/workflows/stales-issues.yml
@@ -17,9 +17,4 @@ jobs:
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
- days-before-pr-stale: 30
- days-before-pr-close: 14
- stale-pr-label: "stale"
- stale-pr-message: "This PR is stale because it has been open for 30 days with no activity."
- close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale."
repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/stales-prs.yml b/.github/workflows/stales-prs.yml
new file mode 100644
index 00000000..33d7b8aa
--- /dev/null
+++ b/.github/workflows/stales-prs.yml
@@ -0,0 +1,20 @@
+name: Closeout Stale PRs
+on:
+ schedule:
+ - cron: "0 2 * * *"
+
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ days-before-pr-stale: 30
+ days-before-pr-close: 14
+ stale-pr-label: "stale"
+ stale-pr-message: "This PR is stale because it has been open for 30 days with no activity."
+ close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale."
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.golangci.yml b/.golangci.yml
index 94113d84..1bd7424b 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,7 +1,7 @@
version: "2"
run:
- concurrency: 8
+ allow-parallel-runners: true
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 5m
@@ -10,137 +10,305 @@ run:
issues-exit-code: 1
tests: true
+linters:
+ enable:
+ - sloglint
+ - bodyclose
+ - copyloopvar
+ - depguard
+ - errcheck
+ - errorlint
+ - gocheckcompilerdirectives
+ - gocritic
+ - godox
+ - goprintffuncname
+ - gosec
+ - govet
+ - intrange
+ - ineffassign
+ - misspell
+ - noctx
+ - nolintlint
+ - revive
+ - staticcheck
+ - testifylint
+ - unconvert
+ - unparam
+ - unused
+ - whitespace
+ - gocyclo
+ - funlen
+ - goconst
+ - dogsled
+ - lll
+ # - dupl
+ # - gochecknoinits
+ # - mnd
+
+ settings:
+ dogsled:
+ max-blank-identifiers: 3
+
+ gosec:
+ excludes:
+ - G109
+ - G115
+ - G204
+ - G303
+
+ sloglint:
+ no-mixed-args: true
+ kv-only: true
+ attr-only: false
+ no-global: ""
+ context: ""
+ static-msg: false
+ no-raw-keys: true
+ key-naming-case: camel
+ forbidden-keys:
+ - time
+ - level
+ - msg
+ - source
+ args-on-sep-lines: false
+
+ depguard:
+ rules:
+ logger:
+ deny:
+ # logging is allowed only by logutils.Log,
+ - pkg: "github.com/sirupsen/logrus"
+ desc: logging is allowed only by logutils.Log.
+ - pkg: "github.com/pkg/errors"
+ desc: Should be replaced by standard lib errors package.
+ - pkg: "github.com/instana/testify"
+ desc: It's a fork of github.com/stretchr/testify.
+ files:
+ - "!**/pkg/logutils/**.go"
+
+ dupl:
+ threshold: 100
+
+ funlen:
+ lines: -1
+ statements: 60
+
+ goconst:
+ min-len: 2
+ min-occurrences: 3
+ ignore-strings: 'blee|duh|cl-1|ct-1-1'
+
+ # gocritic:
+ # enabled-tags:
+ # - diagnostic
+ # - experimental
+ # - opinionated
+ # - performance
+ # - style
+ # disabled-checks:
+ # - dupImport # https://github.com/go-critic/go-critic/issues/845
+ # - ifElseChain
+ # - octalLiteral
+ # - whyNoLint
+
+ gocyclo:
+ min-complexity: 35
+
+ godox:
+ keywords:
+ - FIXME
+
+ mnd:
+ checks:
+ - argument
+ - case
+ - condition
+ - return
+ ignored-numbers:
+ - '0'
+ - '1'
+ - '2'
+ - '3'
+ ignored-functions:
+ - strings.SplitN
+
+ govet:
+ settings:
+ printf:
+ funcs:
+ - (github.com/golangci/golangci-lint/v2/pkg/logutils.Log).Infof
+ - (github.com/golangci/golangci-lint/v2/pkg/logutils.Log).Warnf
+ - (github.com/golangci/golangci-lint/v2/pkg/logutils.Log).Errorf
+ - (github.com/golangci/golangci-lint/v2/pkg/logutils.Log).Fatalf
+ enable:
+ - nilness
+ - shadow
+
+ errorlint:
+ asserts: false
+
+ lll:
+ line-length: 170
+
+ misspell:
+ locale: US
+ ignore-rules:
+ - "importas"
+
+ nolintlint:
+ allow-unused: false
+ require-explanation: false
+ require-specific: true
+
+ revive:
+ rules:
+ - name: indent-error-flow
+ - name: unexported-return
+ disabled: true
+ - name: unused-parameter
+ - name: unused-receiver
+
+ exclusions:
+ presets:
+ - comments
+ - std-error-handling
+ - common-false-positives
+ - legacy
+ paths:
+ - test/testdata_etc # test files
+ - internal/go # extracted from Go code
+ - 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
+ rules:
+ - path: (.+)_test\.go
+ linters:
+ - dupl
+ - mnd
+ - lll
+
+ # Based on existing code, the modifications should be limited to make maintenance easier.
+ - path: pkg/golinters/unused/unused.go
+ linters: [gocritic]
+ text: "rangeValCopy: each iteration copies 160 bytes \\(consider pointers or indexing\\)"
+
+ # Related to the result of computation but divided multiple times by 1024.
+ - path: test/bench/bench_test.go
+ linters: [gosec]
+ text: "G115: integer overflow conversion uint64 -> int"
+
+ # The files created during the tests don't need to be secured.
+ - path: scripts/website/expand_templates/linters_test.go
+ linters: [gosec]
+ text: "G306: Expect WriteFile permissions to be 0600 or less"
+
+ # Related to migration command.
+ - path: pkg/commands/internal/migrate/two/
+ linters:
+ - lll
+
+ # Related to migration command.
+ - path: pkg/commands/internal/migrate/
+ linters:
+ - gocritic
+ text: "hugeParam:"
+
+ # The codes are close but this is not duplication.
+ - path: pkg/commands/(formatters|linters).go
+ linters:
+ - dupl
+
formatters:
enable:
- gci
- gofmt
- # - gofumpt
- goimports
- # - golines
-
-linters:
- # disable-all: true
- enable:
- - sloglint
+ settings:
+ gofmt:
+ rewrite-rules:
+ - pattern: 'interface{}'
+ replacement: 'any'
+ goimports:
+ local-prefixes:
+ - github.com/golangci/golangci-lint/v2
exclusions:
- generated: lax
paths:
- - third_party$
- - builtin$
- - examples$
- - \\.(generated\\.deepcopy|pb)\\.go$
+ - test/testdata_etc # test files
+ - internal/go # extracted from Go code
+ - 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
-settings:
- gocyclo:
- min-complexity: 35
+# linters:
+# default: none
+# enable:
+# - sloglint
- govet:
- enable:
- - nilness
+# exclusions:
+# generated: lax
+# paths:
+# - third_party$
+# - builtin$
+# - examples$
+# - \\.(generated\\.deepcopy|pb)\\.go$
- goimports:
- local-prefixes: github.com/derailed/k9s
+# settings:
+# gocyclo:
+# min-complexity: 35
- 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
+# govet:
+# enable:
+# - nilness
- goheader:
- values:
- regexp:
- PROJECT: 'K9s'
- template: |-
- SPDX-License-Identifier: Apache-2.0
- Copyright Authors of {{ PROJECT }}
+# goimports:
+# local-prefixes: github.com/derailed/k9s
- gosec:
- includes:
- - G402
+# 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
- sloglint:
- # Enforce not mixing key-value pairs and attributes.
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-mixed-arguments
- # Default: true
- no-mixed-args: true
- # Enforce using key-value pairs only (overrides no-mixed-args, incompatible with attr-only).
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-value-pairs-only
- # Default: false
- kv-only: true
- # Enforce using attributes only (overrides no-mixed-args, incompatible with kv-only).
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#attributes-only
- # Default: false
- attr-only: false
- # Enforce not using global loggers.
- # Values:
- # - "": disabled
- # - "all": report all global loggers
- # - "default": report only the default slog logger
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global
- # Default: ""
- no-global: ""
- # Enforce using methods that accept a context.
- # Values:
- # - "": disabled
- # - "all": report all contextless calls
- # - "scope": report only if a context exists in the scope of the outermost function
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only
- # Default: ""
- context: ""
- # Enforce using static values for log messages.
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#static-messages
- # Default: false
- static-msg: false
- # Enforce using constants instead of raw keys.
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-raw-keys
- # Default: false
- no-raw-keys: true
- # Enforce a single key naming convention.
- # Values: snake, kebab, camel, pascal
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-naming-convention
- # Default: ""
- key-naming-case: camel
- # Enforce not using specific keys.
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#forbidden-keys
- # Default: []
- forbidden-keys:
- - time
- - level
- - msg
- - source
- # Enforce putting arguments on separate lines.
- # https://github.com/go-simpler/sloglint?tab=readme-ov-file#arguments-on-separate-lines
- # Default: false
- args-on-sep-lines: false
+# 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
+# issues:
- # Excluding configuration per-path, per-linter, per-text and per-source
- # exclude-rules:
- # - linters: [staticcheck]
- # text: "SA1019" # this is rule for deprecated method
+# # default is true. Enables skipping of directories:
+# # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
+# # exclude-dirs-use-default: true
- # - linters: [staticcheck]
- # text: "SA9003: empty branch"
+# # 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: "SA2001: empty critical section"
+# # - linters: [staticcheck]
+# # text: "SA9003: empty branch"
- # - 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
\ No newline at end of file
+# # - 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
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 7f72af67..2a42a91a 100644
--- a/Makefile
+++ b/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.40.10
+VERSION ?= v0.50.0
IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION}
diff --git a/change_logs/release_v0.50.0.md b/change_logs/release_v0.50.0.md
new file mode 100644
index 00000000..d1c71540
--- /dev/null
+++ b/change_logs/release_v0.50.0.md
@@ -0,0 +1,93 @@
+
+
+# Release v0.50
+
+## 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/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
+
+---
+
+## โซ Sounds Behind The Release โญ
+
+* [Afterimage - Justice](https://www.youtube.com/watch?v=9zBJlLbkfzA)
+* [This Is The Day - The The](https://www.youtube.com/watch?v=qBF3YqUzYRc)
+
+## 5-O, 5-0... Spring Cleaning In Effect!
+
+โ ๏ธ Careful on this upgrade! ๐ดโโ ๏ธ
+We've gone thru lots of code revamp/refactor on this drop, so mileage may vary!!
+
+### K9s Slow?
+
+It looks like K9s performance took a dive in the wrong direction circa v0.40.x releases.
+Took a big perf/cleanup pass to improve perf and think this release should help a lot (famous last words...)
+
+> NOTE! As my dear granny use to say: `You can't cook a great meal without trashing the kitchen`,
+> So likely I have broken a few things in the process. So thread carefully and report back!
+
+### Now with Super Column Blow!
+
+By general demand, juice up custom views! In a feature we like to refer to as `Super Column Blow...`
+As of this drop, you can go full `Chuck Norris` and sprinkle some of your JQ_FU with you custom views.
+
+For example...
+
+```yaml
+# views.yaml
+views:
+ v1/pods:
+ sortColumn: NAME:asc
+ columns:
+ - AGE
+ - NAMESPACE
+ - NAME
+ - IMG-VERSION:.spec.containers[0].image|split(":")|.[-1]|R # => Grab the main container image name and pull the image version
+ # => out into the `IMG-VERSION` right aligned column
+```
+
+> NOTE: โข๏ธ This is very much experimental! Not all JQ queries features are supported!
+> (See https://github.com/itchyny/gojq for the details!)
+
+## Videos Are In The Can!
+
+Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
+
+* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
+* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
+* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
+* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
+
+---
+
+## Resolved Issues
+
+* [#3226](https://github.com/derailed/k9s/issues/3226) Filter view will show mess when filtering some string
+* [#3224](https://github.com/derailed/k9s/issues/3224) Respect kubectl.kubernetes.io/default-container annotation
+* [#3222](https://github.com/derailed/k9s/issues/3222) Option to Display Resource Names Without API Version Prefix
+* [#3210](https://github.com/derailed/k9s/issues/3210) Description line is buggy
+
+---
+
+## 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!!
+
+* [#3237](https://github.com/derailed/k9s/pull/3237) fix: List CRDs which has k8s.io in their names
+* [#3223](https://github.com/derailed/k9s/pull/3223) Fixed skin config ref of in_the_navy to in-the-navy
+* [#3110](https://github.com/derailed/k9s/pull/3110) feat: add splashless option to suppress splash screen on start
+
+---
+
+
ยฉ 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
\ No newline at end of file
diff --git a/cmd/info.go b/cmd/info.go
index 44a7e30f..b439d680 100644
--- a/cmd/info.go
+++ b/cmd/info.go
@@ -24,7 +24,7 @@ func infoCmd() *cobra.Command {
}
}
-func printInfo(cmd *cobra.Command, args []string) error {
+func printInfo(*cobra.Command, []string) error {
if err := config.InitLocs(); err != nil {
return err
}
diff --git a/cmd/root.go b/cmd/root.go
index ab76e732..d68a83a1 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -57,7 +57,7 @@ func init() {
fmt.Printf("Fail to init k9s logs location %s\n", err)
}
- rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
+ rootCmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error {
return flagError{err: err}
})
@@ -75,7 +75,7 @@ func Execute() {
}
}
-func run(cmd *cobra.Command, args []string) error {
+func run(*cobra.Command, []string) error {
if err := config.InitLocs(); err != nil {
return err
}
@@ -378,7 +378,7 @@ func initK8sFlagCompletion() {
return cfg.AuthInfos
}))
- _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) {
+ _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, s string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags)
if c, err := client.InitConnection(conn, slog.Default()); err == nil {
if nss, err := c.ValidNamespaceNames(); err == nil {
@@ -391,7 +391,7 @@ func initK8sFlagCompletion() {
}
func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn {
- return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags)
cfg, err := conn.RawConfig()
if err != nil {
diff --git a/cmd/version.go b/cmd/version.go
index 65298b9d..f4c2244c 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -17,7 +17,7 @@ func versionCmd() *cobra.Command {
Use: "version",
Short: "Print version/build info",
Long: "Print version/build information",
- Run: func(cmd *cobra.Command, args []string) {
+ Run: func(*cobra.Command, []string) {
printVersion(short)
},
}
diff --git a/go.mod b/go.mod
index 3d16a30f..9d944a4d 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
github.com/fsnotify/fsnotify v1.8.0
github.com/fvbommel/sortorder v1.1.0
github.com/go-errors/errors v1.5.1
+ github.com/itchyny/gojq v0.12.17
github.com/lmittmann/tint v1.0.7
github.com/mattn/go-colorable v0.1.14
github.com/mattn/go-runewidth v0.0.16
@@ -192,6 +193,7 @@ require (
github.com/huandu/xstrings v1.5.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 58084637..94bd74c7 100644
--- a/go.sum
+++ b/go.sum
@@ -1274,6 +1274,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
+github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
+github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
+github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
diff --git a/internal/client/client.go b/internal/client/client.go
index 2b681ebf..59898396 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -88,12 +88,11 @@ func (a *APIClient) ConnectionOK() bool {
return a.connOK
}
-func makeSAR(ns, gvr, name string) *authorizationv1.SelfSubjectAccessReview {
+func makeSAR(ns string, gvr *GVR, name string) *authorizationv1.SelfSubjectAccessReview {
if ns == ClusterScope {
ns = BlankNamespace
}
- spec := NewGVR(gvr)
- res := spec.GVR()
+ res := gvr.GVR()
return &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
@@ -101,15 +100,15 @@ func makeSAR(ns, gvr, name string) *authorizationv1.SelfSubjectAccessReview {
Group: res.Group,
Version: res.Version,
Resource: res.Resource,
- Subresource: spec.SubResource(),
+ Subresource: gvr.SubResource(),
Name: name,
},
},
}
}
-func makeCacheKey(ns, gvr, n string, vv []string) string {
- return ns + ":" + gvr + ":" + n + "::" + strings.Join(vv, ",")
+func makeCacheKey(ns string, gvr *GVR, n string, vv []string) string {
+ return ns + ":" + gvr.String() + ":" + n + "::" + strings.Join(vv, ",")
}
// ActiveContext returns the current context name.
@@ -147,7 +146,7 @@ func (a *APIClient) clearCache() {
}
// CanI checks if user has access to a certain resource.
-func (a *APIClient) CanI(ns, gvr, name string, verbs []string) (auth bool, err error) {
+func (a *APIClient) CanI(ns string, gvr *GVR, name string, verbs []string) (auth bool, err error) {
if !a.getConnOK() {
return false, errors.New("ACCESS -- No API server connection")
}
@@ -265,7 +264,7 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) {
}
}
- ok, err := a.CanI(ClusterScope, "v1/namespaces", "", ListAccess)
+ ok, err := a.CanI(ClusterScope, NsGVR, "", ListAccess)
if !ok || err != nil {
return nil, fmt.Errorf("user not authorized to list all namespaces")
}
@@ -281,8 +280,8 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) {
return nil, err
}
nns := make(NamespaceNames, len(nn.Items))
- for _, n := range nn.Items {
- nns[n.Name] = struct{}{}
+ for i := range nn.Items {
+ nns[nn.Items[i].Name] = struct{}{}
}
a.cache.Add(cacheNSKey, nns, cacheExpiry)
@@ -457,11 +456,11 @@ func (a *APIClient) Dial() (kubernetes.Interface, error) {
if err != nil {
return nil, err
}
- if c, err := kubernetes.NewForConfig(cfg); err != nil {
+ c, err := kubernetes.NewForConfig(cfg)
+ if err != nil {
return nil, err
- } else {
- a.setClient(c)
}
+ a.setClient(c)
return a.getClient(), nil
}
@@ -586,7 +585,7 @@ func (a *APIClient) reset() {
a.setConnOK(true)
}
-func (a *APIClient) checkCacheBool(key string) (state bool, ok bool) {
+func (a *APIClient) checkCacheBool(key string) (state, ok bool) {
v, found := a.cache.Get(key)
if !found {
return
@@ -617,11 +616,11 @@ func (a *APIClient) supportsMetricsResources() error {
if err != nil {
return err
}
- for _, grp := range apiGroups.Groups {
- if grp.Name != metricsapi.GroupName {
+ for i := range apiGroups.Groups {
+ if apiGroups.Groups[i].Name != metricsapi.GroupName {
continue
}
- if checkMetricsVersion(grp) {
+ if checkMetricsVersion(&(apiGroups.Groups[i])) {
supported = true
return nil
}
@@ -630,7 +629,7 @@ func (a *APIClient) supportsMetricsResources() error {
return metricsUnsupportedErr
}
-func checkMetricsVersion(grp metav1.APIGroup) bool {
+func checkMetricsVersion(grp *metav1.APIGroup) bool {
for _, v := range grp.Versions {
for _, supportedVersion := range supportedMetricsAPIVersions {
if v.Version == supportedVersion {
diff --git a/internal/client/client_test.go b/internal/client/client_test.go
index e6063488..df84cb41 100644
--- a/internal/client/client_test.go
+++ b/internal/client/client_test.go
@@ -14,12 +14,12 @@ import (
func TestMakeSAR(t *testing.T) {
uu := map[string]struct {
ns string
- gvr GVR
+ gvr *GVR
sar *authorizationv1.SelfSubjectAccessReview
}{
"all-pods": {
ns: NamespaceAll,
- gvr: NewGVR("v1/pods"),
+ gvr: PodGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
@@ -30,9 +30,10 @@ func TestMakeSAR(t *testing.T) {
},
},
},
+
"ns-pods": {
ns: "fred",
- gvr: NewGVR("v1/pods"),
+ gvr: PodGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
@@ -43,9 +44,10 @@ func TestMakeSAR(t *testing.T) {
},
},
},
+
"clusterscope-ns": {
ns: ClusterScope,
- gvr: NewGVR("v1/namespaces"),
+ gvr: NsGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
@@ -55,6 +57,7 @@ func TestMakeSAR(t *testing.T) {
},
},
},
+
"subres-pods": {
ns: "fred",
gvr: NewGVR("v1/pods:logs"),
@@ -74,7 +77,7 @@ func TestMakeSAR(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr.String(), ""))
+ assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr, ""))
})
}
}
@@ -153,7 +156,7 @@ func TestCheckCacheBool(t *testing.T) {
const key = "fred"
uu := map[string]struct {
key string
- val interface{}
+ val any
found, actual, sleep bool
}{
"setTrue": {
diff --git a/internal/client/config.go b/internal/client/config.go
index ebbf8bc5..83597eca 100644
--- a/internal/client/config.go
+++ b/internal/client/config.go
@@ -77,7 +77,7 @@ func (c *Config) clientConfig() clientcmd.ClientConfig {
return c.flags.ToRawKubeConfigLoader()
}
-func (c *Config) reset() {}
+func (*Config) reset() {}
// SwitchContext changes the kubeconfig context to a new cluster.
func (c *Config) SwitchContext(name string) error {
@@ -221,17 +221,17 @@ func (c *Config) DelContext(n string) error {
}
// RenameContext renames a context.
-func (c *Config) RenameContext(old string, new string) error {
+func (c *Config) RenameContext(oldCtx, newCtx string) error {
cfg, err := c.RawConfig()
if err != nil {
return err
}
- if _, ok := cfg.Contexts[new]; ok {
- return fmt.Errorf("context with name %s already exists", new)
+ if _, ok := cfg.Contexts[newCtx]; ok {
+ return fmt.Errorf("context with name %s already exists", newCtx)
}
- cfg.Contexts[new] = cfg.Contexts[old]
- delete(cfg.Contexts, old)
+ cfg.Contexts[newCtx] = cfg.Contexts[oldCtx]
+ delete(cfg.Contexts, oldCtx)
acc, err := c.ConfigAccess()
if err != nil {
return err
@@ -243,8 +243,8 @@ func (c *Config) RenameContext(old string, new string) error {
if err != nil {
return err
}
- if current == old {
- return c.SwitchContext(new)
+ if current == oldCtx {
+ return c.SwitchContext(newCtx)
}
return nil
@@ -344,9 +344,9 @@ func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
// Helpers...
func isSet(s *string) bool {
- return s != nil && len(*s) != 0
+ return s != nil && *s != ""
}
-func areSet(s *[]string) bool {
- return s != nil && len(*s) != 0
+func areSet(ss *[]string) bool {
+ return ss != nil && len(*ss) != 0
}
diff --git a/internal/client/config_test.go b/internal/client/config_test.go
index 5e501d3d..f0d7d72a 100644
--- a/internal/client/config_test.go
+++ b/internal/client/config_test.go
@@ -12,9 +12,12 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
+var kubeConfig = "./testdata/config"
+
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
@@ -45,8 +48,6 @@ func TestCallTimeout(t *testing.T) {
}
func TestConfigCurrentContext(t *testing.T) {
- kubeConfig := "./testdata/config"
-
uu := map[string]struct {
context string
e string
@@ -70,14 +71,14 @@ func TestConfigCurrentContext(t *testing.T) {
}
cfg := client.NewConfig(flags)
ctx, err := cfg.CurrentContextName()
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, ctx)
})
}
}
func TestConfigCurrentCluster(t *testing.T) {
- name, kubeConfig := "blee", "./testdata/config"
+ name := "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
cluster string
@@ -102,14 +103,14 @@ func TestConfigCurrentCluster(t *testing.T) {
t.Run(k, func(t *testing.T) {
cfg := client.NewConfig(u.flags)
ct, err := cfg.CurrentClusterName()
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.cluster, ct)
})
}
}
func TestConfigCurrentUser(t *testing.T) {
- name, kubeConfig := "blee", "./testdata/config"
+ name := "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
user string
@@ -129,14 +130,13 @@ func TestConfigCurrentUser(t *testing.T) {
t.Run(k, func(t *testing.T) {
cfg := client.NewConfig(u.flags)
ctx, err := cfg.CurrentUserName()
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.user, ctx)
})
}
}
func TestConfigCurrentNamespace(t *testing.T) {
- kubeConfig := "./testdata/config"
bleeNS, bleeCTX := "blee", "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
@@ -162,7 +162,7 @@ func TestConfigCurrentNamespace(t *testing.T) {
cfg := client.NewConfig(u.flags)
ns, err := cfg.CurrentNamespaceName()
if ns != "" {
- assert.Nil(t, err)
+ require.NoError(t, err)
}
assert.Equal(t, u.namespace, ns)
})
@@ -170,7 +170,6 @@ func TestConfigCurrentNamespace(t *testing.T) {
}
func TestConfigGetContext(t *testing.T) {
- kubeConfig := "./testdata/config"
uu := map[string]struct {
cluster string
err error
@@ -201,7 +200,7 @@ func TestConfigGetContext(t *testing.T) {
}
func TestConfigSwitchContext(t *testing.T) {
- cluster, kubeConfig := "duh", "./testdata/config"
+ cluster := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &cluster,
@@ -209,14 +208,14 @@ func TestConfigSwitchContext(t *testing.T) {
cfg := client.NewConfig(&flags)
err := cfg.SwitchContext("blee")
- assert.Nil(t, err)
+ require.NoError(t, err)
ctx, err := cfg.CurrentContextName()
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "blee", ctx)
}
func TestConfigAccess(t *testing.T) {
- context, kubeConfig := "duh", "./testdata/config"
+ context := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &context,
@@ -224,12 +223,12 @@ func TestConfigAccess(t *testing.T) {
cfg := client.NewConfig(&flags)
acc, err := cfg.ConfigAccess()
- assert.Nil(t, err)
- assert.True(t, len(acc.GetDefaultFilename()) > 0)
+ require.NoError(t, err)
+ assert.NotEmpty(t, acc.GetDefaultFilename())
}
func TestConfigContextNames(t *testing.T) {
- cluster, kubeConfig := "duh", "./testdata/config"
+ cluster := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &cluster,
@@ -237,12 +236,12 @@ func TestConfigContextNames(t *testing.T) {
cfg := client.NewConfig(&flags)
cc, err := cfg.ContextNames()
- assert.Nil(t, err)
- assert.Equal(t, 3, len(cc))
+ require.NoError(t, err)
+ assert.Len(t, cc, 3)
}
func TestConfigContexts(t *testing.T) {
- context, kubeConfig := "duh", "./testdata/config"
+ context := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &context,
@@ -250,39 +249,38 @@ func TestConfigContexts(t *testing.T) {
cfg := client.NewConfig(&flags)
cc, err := cfg.Contexts()
- assert.Nil(t, err)
- assert.Equal(t, 3, len(cc))
+ require.NoError(t, err)
+ assert.Len(t, cc, 3)
}
func TestConfigDelContext(t *testing.T) {
- assert.NoError(t, cp("./testdata/config.2", "./testdata/config.1"))
+ require.NoError(t, cp("./testdata/config.2", "./testdata/config.1"))
- context, kubeConfig := "duh", "./testdata/config.1"
+ context, kubeCfg := "duh", "./testdata/config.1"
flags := genericclioptions.ConfigFlags{
- KubeConfig: &kubeConfig,
+ KubeConfig: &kubeCfg,
Context: &context,
}
cfg := client.NewConfig(&flags)
err := cfg.DelContext("fred")
- assert.NoError(t, err)
+ require.NoError(t, err)
cc, err := cfg.ContextNames()
- assert.NoError(t, err)
- assert.Equal(t, 1, len(cc))
+ require.NoError(t, err)
+ assert.Len(t, cc, 1)
_, ok := cc["blee"]
assert.True(t, ok)
}
func TestConfigRestConfig(t *testing.T) {
- kubeConfig := "./testdata/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
}
cfg := client.NewConfig(&flags)
rc, err := cfg.RESTConfig()
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "https://localhost:3002", rc.Host)
}
@@ -294,12 +292,12 @@ func TestConfigBadConfig(t *testing.T) {
cfg := client.NewConfig(&flags)
_, err := cfg.RESTConfig()
- assert.NotNil(t, err)
+ assert.Error(t, err)
}
// Helpers...
-func cp(src string, dst string) error {
+func cp(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
diff --git a/internal/client/gvr.go b/internal/client/gvr.go
index 1d4a4b68..f1ea041c 100644
--- a/internal/client/gvr.go
+++ b/internal/client/gvr.go
@@ -11,11 +11,12 @@ import (
"github.com/derailed/k9s/internal/slogs"
"github.com/fvbommel/sortorder"
+ apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
-var NoGVR = GVR{}
+var NoGVR = &GVR{}
// GVR represents a kubernetes resource schema as a string.
// Format is group/version/resources:subresource.
@@ -23,12 +24,29 @@ type GVR struct {
raw, g, v, r, sr string
}
-// NewGVR builds a new gvr from a group, version, resource.
-func NewGVR(gvr string) GVR {
- var g, v, r, sr string
+type gvrCache map[string]*GVR
- tokens := strings.Split(gvr, ":")
- raw := gvr
+func (c gvrCache) add(gvr *GVR) {
+ if c.get(gvr.String()) == nil {
+ c[gvr.String()] = gvr
+ }
+}
+
+func (c gvrCache) get(gvrs string) *GVR {
+ if gvr, ok := c[gvrs]; ok {
+ return gvr
+ }
+
+ return nil
+}
+
+var gvrsCache = make(gvrCache)
+
+// NewGVR builds a new gvr from a group, version, resource.
+func NewGVR(path string) *GVR {
+ raw := path
+ tokens := strings.Split(path, ":")
+ var g, v, r, sr string
if len(tokens) == 2 {
raw, sr = tokens[0], tokens[1]
}
@@ -41,34 +59,62 @@ func NewGVR(gvr string) GVR {
case 1:
r = tokens[0]
default:
- slog.Error("GVR init failed!", slogs.Error, fmt.Errorf("can't parse GVR %q", gvr))
+ slog.Error("GVR init failed!", slogs.Error, fmt.Errorf("can't parse GVR %q", path))
}
- return GVR{raw: gvr, g: g, v: v, r: r, sr: sr}
+ gvr := GVR{raw: path, g: g, v: v, r: r, sr: sr}
+ if cgvr := gvrsCache.get(gvr.String()); cgvr != nil {
+ return cgvr
+ }
+ gvrsCache.add(&gvr)
+
+ return &gvr
+}
+
+func (g *GVR) IsK8sRes() bool {
+ return strings.Contains(g.raw, "/")
+}
+
+// WithSubResource builds a new gvr with a sub resource.
+func (g *GVR) WithSubResource(sub string) *GVR {
+ return NewGVR(g.String() + ":" + sub)
}
// NewGVRFromMeta builds a gvr from resource metadata.
-func NewGVRFromMeta(a metav1.APIResource) GVR {
- return GVR{
- raw: path.Join(a.Group, a.Version, a.Name),
- g: a.Group,
- v: a.Version,
- r: a.Name,
+func NewGVRFromMeta(a *metav1.APIResource) *GVR {
+ return NewGVR(path.Join(a.Group, a.Version, a.Name))
+}
+
+// NewGVRFromCRD builds a gvr from a custom resource definition.
+func NewGVRFromCRD(crd *apiext.CustomResourceDefinition) map[*GVR]*apiext.CustomResourceDefinitionVersion {
+ mm := make(map[*GVR]*apiext.CustomResourceDefinitionVersion, len(crd.Spec.Versions))
+ for _, v := range crd.Spec.Versions {
+ if v.Served && !v.Deprecated {
+ gvr := NewGVRFromMeta(&metav1.APIResource{
+ Kind: crd.Spec.Names.Kind,
+ Group: crd.Spec.Group,
+ Name: crd.Spec.Names.Plural,
+ Version: v.Name,
+ })
+ mm[gvr] = &v
+ }
}
+
+ return mm
}
// FromGVAndR builds a gvr from a group/version and resource.
-func FromGVAndR(gv, r string) GVR {
+func FromGVAndR(gv, r string) *GVR {
return NewGVR(path.Join(gv, r))
}
// FQN returns a fully qualified resource name.
-func (g GVR) FQN(n string) string {
+func (g *GVR) FQN(n string) string {
return path.Join(g.AsResourceName(), n)
}
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
-func (g GVR) AsResourceName() string {
+func (g *GVR) AsResourceName() string {
if g.g == "" {
return g.r
}
@@ -77,17 +123,17 @@ func (g GVR) AsResourceName() string {
}
// SubResource returns a sub resource if available.
-func (g GVR) SubResource() string {
+func (g *GVR) SubResource() string {
return g.sr
}
// String returns gvr as string.
-func (g GVR) String() string {
+func (g *GVR) String() string {
return g.raw
}
// GV returns the group version scheme representation.
-func (g GVR) GV() schema.GroupVersion {
+func (g *GVR) GV() schema.GroupVersion {
return schema.GroupVersion{
Group: g.g,
Version: g.v,
@@ -95,7 +141,7 @@ func (g GVR) GV() schema.GroupVersion {
}
// GVK returns a full schema representation.
-func (g GVR) GVK() schema.GroupVersionKind {
+func (g *GVR) GVK() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: g.G(),
Version: g.V(),
@@ -104,7 +150,7 @@ func (g GVR) GVK() schema.GroupVersionKind {
}
// GVR returns a full schema representation.
-func (g GVR) GVR() schema.GroupVersionResource {
+func (g *GVR) GVR() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: g.G(),
Version: g.V(),
@@ -113,7 +159,7 @@ func (g GVR) GVR() schema.GroupVersionResource {
}
// GVSub returns group vervion sub path.
-func (g GVR) GVSub() string {
+func (g *GVR) GVSub() string {
if g.G() == "" {
return g.V()
}
@@ -122,7 +168,7 @@ func (g GVR) GVSub() string {
}
// GR returns a full schema representation.
-func (g GVR) GR() *schema.GroupResource {
+func (g *GVR) GR() *schema.GroupResource {
return &schema.GroupResource{
Group: g.G(),
Resource: g.R(),
@@ -130,32 +176,32 @@ func (g GVR) GR() *schema.GroupResource {
}
// V returns the resource version.
-func (g GVR) V() string {
+func (g *GVR) V() string {
return g.v
}
// RG returns the resource and group.
-func (g GVR) RG() (string, string) {
+func (g *GVR) RG() (resource, group string) {
return g.r, g.g
}
// R returns the resource name.
-func (g GVR) R() string {
+func (g *GVR) R() string {
return g.r
}
// G returns the resource group name.
-func (g GVR) G() string {
+func (g *GVR) G() string {
return g.g
}
// IsDecodable checks if the k8s resource has a decodable view
-func (g GVR) IsDecodable() bool {
+func (g *GVR) IsDecodable() bool {
return g.GVK().Kind == "secrets"
}
// GVRs represents a collection of gvr.
-type GVRs []GVR
+type GVRs []*GVR
// Len returns the list size.
func (g GVRs) Len() int {
diff --git a/internal/client/gvr_test.go b/internal/client/gvr_test.go
index fb310df2..6f449c16 100644
--- a/internal/client/gvr_test.go
+++ b/internal/client/gvr_test.go
@@ -15,15 +15,15 @@ import (
func TestGVRSort(t *testing.T) {
gg := client.GVRs{
- client.NewGVR("v1/pods"),
- client.NewGVR("v1/services"),
- client.NewGVR("apps/v1/deployments"),
+ client.PodGVR,
+ client.SvcGVR,
+ client.DpGVR,
}
sort.Sort(gg)
assert.Equal(t, client.GVRs{
- client.NewGVR("v1/pods"),
- client.NewGVR("v1/services"),
- client.NewGVR("apps/v1/deployments"),
+ client.PodGVR,
+ client.SvcGVR,
+ client.DpGVR,
}, gg)
}
@@ -54,9 +54,9 @@ func TestGVR(t *testing.T) {
gvr string
e schema.GroupVersionResource
}{
- "full": {"apps/v1/deployments", schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
- "core": {"v1/pods", schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
- "bork": {"users", schema.GroupVersionResource{Resource: "users"}},
+ "full": {client.DpGVR.String(), schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
+ "core": {client.PodGVR.String(), schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
+ "bork": {client.UsrGVR.String(), schema.GroupVersionResource{Resource: "users"}},
}
for k := range uu {
@@ -72,9 +72,9 @@ func TestAsGV(t *testing.T) {
gvr string
e schema.GroupVersion
}{
- "full": {"apps/v1/deployments", schema.GroupVersion{Group: "apps", Version: "v1"}},
- "core": {"v1/pods", schema.GroupVersion{Version: "v1"}},
- "bork": {"users", schema.GroupVersion{}},
+ "full": {client.DpGVR.String(), schema.GroupVersion{Group: "apps", Version: "v1"}},
+ "core": {client.PodGVR.String(), schema.GroupVersion{Version: "v1"}},
+ "bork": {client.UsrGVR.String(), schema.GroupVersion{}},
}
for k := range uu {
@@ -90,8 +90,8 @@ func TestNewGVR(t *testing.T) {
g, v, r string
e string
}{
- "full": {"apps", "v1", "deployments", "apps/v1/deployments"},
- "core": {"", "v1", "pods", "v1/pods"},
+ "full": {"apps", "v1", "deployments", client.DpGVR.String()},
+ "core": {"", "v1", "pods", client.PodGVR.String()},
}
for k := range uu {
@@ -107,9 +107,9 @@ func TestGVRAsResourceName(t *testing.T) {
gvr string
e string
}{
- "full": {"apps/v1/deployments", "deployments.v1.apps"},
- "core": {"v1/pods", "pods"},
- "k9s": {"users", "users"},
+ "full": {client.DpGVR.String(), "deployments.v1.apps"},
+ "core": {client.PodGVR.String(), "pods"},
+ "k9s": {client.UsrGVR.String(), "users"},
"empty": {"", ""},
}
@@ -126,9 +126,9 @@ func TestToR(t *testing.T) {
gvr string
e string
}{
- "full": {"apps/v1/deployments", "deployments"},
- "core": {"v1/pods", "pods"},
- "k9s": {"users", "users"},
+ "full": {client.DpGVR.String(), "deployments"},
+ "core": {client.PodGVR.String(), "pods"},
+ "k9s": {client.UsrGVR.String(), "users"},
"empty": {"", ""},
}
@@ -145,9 +145,9 @@ func TestToG(t *testing.T) {
gvr string
e string
}{
- "full": {"apps/v1/deployments", "apps"},
- "core": {"v1/pods", ""},
- "k9s": {"users", ""},
+ "full": {client.DpGVR.String(), "apps"},
+ "core": {client.PodGVR.String(), ""},
+ "k9s": {client.UsrGVR.String(), ""},
"empty": {"", ""},
}
@@ -164,9 +164,9 @@ func TestToV(t *testing.T) {
gvr string
e string
}{
- "full": {"apps/v1/deployments", "v1"},
+ "full": {client.DpGVR.String(), "v1"},
"core": {"v1beta1/pods", "v1beta1"},
- "k9s": {"users", ""},
+ "k9s": {client.UsrGVR.String(), ""},
"empty": {"", ""},
}
@@ -182,9 +182,9 @@ func TestToString(t *testing.T) {
uu := map[string]struct {
gvr string
}{
- "full": {"apps/v1/deployments"},
+ "full": {client.DpGVR.String()},
"core": {"v1beta1/pods"},
- "k9s": {"users"},
+ "k9s": {client.UsrGVR.String()},
"empty": {""},
}
diff --git a/internal/client/gvrs.go b/internal/client/gvrs.go
new file mode 100644
index 00000000..eae47ad7
--- /dev/null
+++ b/internal/client/gvrs.go
@@ -0,0 +1,75 @@
+package client
+
+var (
+ // Apps...
+ DpGVR = NewGVR("apps/v1/deployments")
+ StsGVR = NewGVR("apps/v1/statefulsets")
+ DsGVR = NewGVR("apps/v1/daemonsets")
+ RsGVR = NewGVR("apps/v1/replicasets")
+
+ // Core...
+ SaGVR = NewGVR("v1/serviceaccounts")
+ PvcGVR = NewGVR("v1/persistentvolumeclaims")
+ PvGVR = NewGVR("v1/persistentvolumes")
+ CmGVR = NewGVR("v1/configmaps")
+ SecGVR = NewGVR("v1/secrets")
+ EvGVR = NewGVR("events.k8s.io/v1/events")
+ EpGVR = NewGVR("v1/endpoints")
+ PodGVR = NewGVR("v1/pods")
+ NsGVR = NewGVR("v1/namespaces")
+ NodeGVR = NewGVR("v1/nodes")
+ SvcGVR = NewGVR("v1/services")
+
+ // Autoscaling...
+ HpaGVR = NewGVR("autoscaling/v1/horizontalpodautoscalers")
+
+ // Batch...
+ CjGVR = NewGVR("batch/v1/cronjobs")
+ JobGVR = NewGVR("batch/v1/jobs")
+
+ // Misc...
+ CrdGVR = NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")
+ PcGVR = NewGVR("scheduling.k8s.io/v1/priorityclasses")
+ NpGVR = NewGVR("networking.k8s.io/v1/networkpolicies")
+ ScGVR = NewGVR("storage.k8s.io/v1/storageclasses")
+
+ // Policy...
+ PdbGVR = NewGVR("policy/v1/PodDisruptionBudgets")
+ PspGVR = NewGVR("policy/v1beta1/podsecuritypolicies")
+
+ // Metrics...
+ NmxGVR = NewGVR("metrics.k8s.io/v1beta1/nodes")
+ PmxGVR = NewGVR("metrics.k8s.io/v1beta1/pods")
+
+ // K9s...
+ CpuGVR = NewGVR("cpu")
+ MemGVR = NewGVR("memory")
+ WkGVR = NewGVR("workloads")
+ CoGVR = NewGVR("containers")
+ CtGVR = NewGVR("contexts")
+ RefGVR = NewGVR("references")
+ PuGVR = NewGVR("pulses")
+ ScnGVR = NewGVR("scans")
+ DirGVR = NewGVR("dirs")
+ PfGVR = NewGVR("portforwards")
+ SdGVR = NewGVR("screendumps")
+ BeGVR = NewGVR("benchmarks")
+ AliGVR = NewGVR("aliases")
+ XGVR = NewGVR("xrays")
+ HlpGVR = NewGVR("help")
+ QGVR = NewGVR("quit")
+
+ // Helm...
+ HmGVR = NewGVR("helm")
+ HmhGVR = NewGVR("helm-history")
+
+ // RBAC...
+ RbacGVR = NewGVR("rbac")
+ PolGVR = NewGVR("policy")
+ UsrGVR = NewGVR("users")
+ GrpGVR = NewGVR("groups")
+ CrGVR = NewGVR("rbac.authorization.k8s.io/v1/clusterroles")
+ CrbGVR = NewGVR("rbac.authorization.k8s.io/v1/clusterrolebindings")
+ RoGVR = NewGVR("rbac.authorization.k8s.io/v1/roles")
+ RobGVR = NewGVR("rbac.authorization.k8s.io/v1/rolebindings")
+)
diff --git a/internal/client/helper_test.go b/internal/client/helper_test.go
index 5cd46519..3b4ccd17 100644
--- a/internal/client/helper_test.go
+++ b/internal/client/helper_test.go
@@ -32,7 +32,7 @@ func TestMetaFQN(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, client.MetaFQN(u.meta))
+ assert.Equal(t, u.e, client.MetaFQN(&u.meta))
})
}
}
@@ -60,7 +60,7 @@ func TestCoFQN(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, client.CoFQN(u.meta, u.co))
+ assert.Equal(t, u.e, client.CoFQN(&u.meta, u.co))
})
}
}
diff --git a/internal/client/helpers.go b/internal/client/helpers.go
index d2dbabd5..116e6145 100644
--- a/internal/client/helpers.go
+++ b/internal/client/helpers.go
@@ -52,14 +52,14 @@ func IsClusterScoped(ns string) bool {
}
// Namespaced converts a resource path to namespace and resource name.
-func Namespaced(p string) (string, string) {
- ns, n := path.Split(p)
+func Namespaced(p string) (ns, name string) {
+ ns, name = path.Split(p)
- return strings.Trim(ns, "/"), n
+ return strings.Trim(ns, "/"), name
}
// CoFQN returns a fully qualified container name.
-func CoFQN(m metav1.ObjectMeta, co string) string {
+func CoFQN(m *metav1.ObjectMeta, co string) string {
return MetaFQN(m) + ":" + co
}
@@ -72,7 +72,7 @@ func FQN(ns, n string) string {
}
// MetaFQN returns a fully qualified resource name.
-func MetaFQN(m metav1.ObjectMeta) string {
+func MetaFQN(m *metav1.ObjectMeta) string {
if m.Namespace == "" {
return FQN(ClusterScope, m.Name)
}
@@ -90,6 +90,9 @@ func mustHomeDir() string {
}
func toHostDir(host string) string {
- h := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
+ h := strings.Replace(
+ strings.Replace(host, "https://", "", 1),
+ "http://", "", 1,
+ )
return toFileName.ReplaceAllString(h, "_")
}
diff --git a/internal/client/metrics.go b/internal/client/metrics.go
index f79aeac4..b239db68 100644
--- a/internal/client/metrics.go
+++ b/internal/client/metrics.go
@@ -20,8 +20,6 @@ import (
const (
mxCacheSize = 100
mxCacheExpiry = 1 * time.Minute
- podMXGVR = "metrics.k8s.io/v1beta1/pods"
- nodeMXGVR = "metrics.k8s.io/v1beta1/nodes"
)
// MetricsDial tracks global metric server handle.
@@ -57,22 +55,22 @@ func NewMetricsServer(c Connection) *MetricsServer {
}
// ClusterLoad retrieves all cluster nodes metrics.
-func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsList, mx *ClusterMetrics) error {
+func (*MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsList, mx *ClusterMetrics) error {
if nos == nil || nmx == nil {
return fmt.Errorf("invalid node or node metrics lists")
}
nodeMetrics := make(NodesMetrics, len(nos.Items))
- for _, no := range nos.Items {
- nodeMetrics[no.Name] = NodeMetrics{
- AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
- AllocatableMEM: no.Status.Allocatable.Memory().Value(),
+ for i := range nos.Items {
+ nodeMetrics[nos.Items[i].Name] = NodeMetrics{
+ AllocatableCPU: nos.Items[i].Status.Allocatable.Cpu().MilliValue(),
+ AllocatableMEM: nos.Items[i].Status.Allocatable.Memory().Value(),
}
}
- for _, mx := range nmx.Items {
- if node, ok := nodeMetrics[mx.Name]; ok {
- node.CurrentCPU = mx.Usage.Cpu().MilliValue()
- node.CurrentMEM = mx.Usage.Memory().Value()
- nodeMetrics[mx.Name] = node
+ for i := range nmx.Items {
+ if node, ok := nodeMetrics[nmx.Items[i].Name]; ok {
+ node.CurrentCPU = nmx.Items[i].Usage.Cpu().MilliValue()
+ node.CurrentMEM = nmx.Items[i].Usage.Memory().Value()
+ nodeMetrics[nmx.Items[i].Name] = node
}
}
@@ -88,7 +86,7 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
return nil
}
-func (m *MetricsServer) checkAccess(ns, gvr, msg string) error {
+func (m *MetricsServer) checkAccess(ns string, gvr *GVR, msg string) error {
if !m.HasMetrics() {
return errors.New("no metrics-server detected on cluster")
}
@@ -104,29 +102,31 @@ func (m *MetricsServer) checkAccess(ns, gvr, msg string) error {
}
// NodesMetrics retrieves metrics for a given set of nodes.
-func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
+func (*MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
if nodes == nil || metrics == nil {
return
}
- for _, no := range nodes.Items {
- mmx[no.Name] = NodeMetrics{
- AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
- AllocatableMEM: ToMB(no.Status.Allocatable.Memory().Value()),
- AllocatableEphemeral: ToMB(no.Status.Allocatable.StorageEphemeral().Value()),
- TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
- TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
- TotalEphemeral: ToMB(no.Status.Capacity.StorageEphemeral().Value()),
+ for i := range nodes.Items {
+ mmx[nodes.Items[i].Name] = NodeMetrics{
+ AllocatableCPU: nodes.Items[i].Status.Allocatable.Cpu().MilliValue(),
+ AllocatableMEM: ToMB(nodes.Items[i].Status.Allocatable.Memory().Value()),
+ AllocatableEphemeral: ToMB(nodes.Items[i].Status.Allocatable.StorageEphemeral().Value()),
+ TotalCPU: nodes.Items[i].Status.Capacity.Cpu().MilliValue(),
+ TotalMEM: ToMB(nodes.Items[i].Status.Capacity.Memory().Value()),
+ TotalEphemeral: ToMB(nodes.Items[i].Status.Capacity.StorageEphemeral().Value()),
}
}
- for _, c := range metrics.Items {
- if mx, ok := mmx[c.Name]; ok {
- mx.CurrentCPU = c.Usage.Cpu().MilliValue()
- mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
- mx.AvailableCPU = mx.AllocatableCPU - mx.CurrentCPU
- mx.AvailableMEM = mx.AllocatableMEM - mx.CurrentMEM
- mmx[c.Name] = mx
+ for i := range metrics.Items {
+ mx, ok := mmx[metrics.Items[i].Name]
+ if !ok {
+ continue
}
+ mx.CurrentCPU = metrics.Items[i].Usage.Cpu().MilliValue()
+ mx.CurrentMEM = ToMB(metrics.Items[i].Usage.Memory().Value())
+ mx.AvailableCPU = mx.AllocatableCPU - mx.CurrentCPU
+ mx.AvailableMEM = mx.AllocatableMEM - mx.CurrentMEM
+ mmx[metrics.Items[i].Name] = mx
}
}
@@ -151,7 +151,7 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe
const msg = "user is not authorized to list node metrics"
mx := new(mv1beta1.NodeMetricsList)
- if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil {
+ if err := m.checkAccess(ClusterScope, NmxGVR, msg); err != nil {
return mx, err
}
@@ -182,7 +182,7 @@ func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1bet
const msg = "user is not authorized to list node metrics"
mx := new(mv1beta1.NodeMetrics)
- if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil {
+ if err := m.checkAccess(ClusterScope, NmxGVR, msg); err != nil {
return mx, err
}
@@ -222,7 +222,7 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
if ns == NamespaceAll {
ns = BlankNamespace
}
- if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
+ if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
return mx, err
}
@@ -273,7 +273,7 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be
if ns == NamespaceAll {
ns = BlankNamespace
}
- if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
+ if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
return mx, err
}
@@ -290,19 +290,19 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be
}
// PodsMetrics retrieves metrics for all pods in a given namespace.
-func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetrics) {
+func (*MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetrics) {
if pods == nil {
return
}
// Compute all pod's containers metrics.
- for _, p := range pods.Items {
+ for i := range pods.Items {
var mx PodMetrics
- for _, c := range p.Containers {
+ for _, c := range pods.Items[i].Containers {
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
}
- mmx[p.Namespace+"/"+p.Name] = mx
+ mmx[pods.Items[i].Namespace+"/"+pods.Items[i].Name] = mx
}
}
diff --git a/internal/client/metrics_test.go b/internal/client/metrics_test.go
index 8a433c86..e0b84d28 100644
--- a/internal/client/metrics_test.go
+++ b/internal/client/metrics_test.go
@@ -79,7 +79,7 @@ func TestPodsMetrics(t *testing.T) {
mmx := make(client.PodsMetrics)
m.PodsMetrics(u.metrics, mmx)
- assert.Equal(t, u.eSize, len(mmx))
+ assert.Len(t, mmx, u.eSize)
if u.eSize == 0 {
return
}
@@ -104,7 +104,7 @@ func BenchmarkPodsMetrics(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
m.PodsMetrics(&metrics, mmx)
}
}
@@ -175,7 +175,7 @@ func TestNodesMetrics(t *testing.T) {
mmx := make(client.NodesMetrics)
m.NodesMetrics(u.nodes, u.metrics, mmx)
- assert.Equal(t, u.eSize, len(mmx))
+ assert.Len(t, mmx, u.eSize)
if u.eSize == 0 {
return
}
@@ -206,7 +206,7 @@ func BenchmarkNodesMetrics(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
m.NodesMetrics(&nodes, &metrics, mmx)
}
}
@@ -290,7 +290,7 @@ func BenchmarkClusterLoad(b *testing.B) {
var mx client.ClusterMetrics
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
_ = m.ClusterLoad(&nodes, &metrics, &mx)
}
}
diff --git a/internal/client/types.go b/internal/client/types.go
index b5a7d3cf..e28f3e2e 100644
--- a/internal/client/types.go
+++ b/internal/client/types.go
@@ -83,7 +83,7 @@ type PodsMetricsMap map[string]*mv1beta1.PodMetrics
// Authorizer checks what a user can or cannot do to a resource.
type Authorizer interface {
// CanI returns true if the user can use these actions for a given resource.
- CanI(ns, gvr, n string, verbs []string) (bool, error)
+ CanI(ns string, gvr *GVR, n string, verbs []string) (bool, error)
}
// Connection represents a Kubernetes apiserver connection.
diff --git a/internal/color/colorize_test.go b/internal/color/colorize_test.go
index 3d71f83f..b2cd5dd2 100644
--- a/internal/color/colorize_test.go
+++ b/internal/color/colorize_test.go
@@ -47,7 +47,7 @@ func TestHighlight(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, string(color.Highlight([]byte(u.text), u.indices, u.color)))
+ assert.Equal(t, u.e, string(color.Highlight(u.text, u.indices, u.color)))
})
}
}
diff --git a/internal/config/alias.go b/internal/config/alias.go
index 6503c493..05d85cdd 100644
--- a/internal/config/alias.go
+++ b/internal/config/alias.go
@@ -5,22 +5,25 @@ package config
import (
"errors"
+ "fmt"
"io/fs"
"log/slog"
"os"
"sync"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v3"
+ "k8s.io/apimachinery/pkg/util/sets"
)
// Alias tracks shortname to GVR mappings.
-type Alias map[string]string
+type Alias map[string]*client.GVR
// ShortNames represents a collection of shortnames for aliases.
-type ShortNames map[string][]string
+type ShortNames map[*client.GVR][]string
// Aliases represents a collection of aliases.
type Aliases struct {
@@ -35,29 +38,17 @@ func NewAliases() *Aliases {
}
}
-func (a *Aliases) AliasesFor(s string) []string {
- aa := make([]string, 0, 10)
-
+func (a *Aliases) AliasesFor(gvr *client.GVR) sets.Set[string] {
a.mx.RLock()
defer a.mx.RUnlock()
- for k, v := range a.Alias {
- if v == s {
- aa = append(aa, k)
+
+ ss := sets.New[string]()
+ for alias, aliasGVR := range a.Alias {
+ if aliasGVR == gvr {
+ ss.Insert(alias)
}
}
- return aa
-}
-
-// Keys returns all aliases keys.
-func (a *Aliases) Keys() []string {
- a.mx.RLock()
- defer a.mx.RUnlock()
-
- ss := make([]string, 0, len(a.Alias))
- for k := range a.Alias {
- ss = append(ss, k)
- }
return ss
}
@@ -89,24 +80,27 @@ func (a *Aliases) Clear() {
}
// Get retrieves an alias.
-func (a *Aliases) Get(k string) (string, bool) {
+func (a *Aliases) Get(alias string) (*client.GVR, bool) {
a.mx.RLock()
defer a.mx.RUnlock()
- v, ok := a.Alias[k]
- return v, ok
+ gvr, ok := a.Alias[alias]
+ if ok && !gvr.IsK8sRes() {
+ if rgvr, found := a.Alias[gvr.String()]; found {
+ return rgvr, found
+ }
+ }
+
+ return gvr, ok
}
// Define declares a new alias.
-func (a *Aliases) Define(gvr string, aliases ...string) {
+func (a *Aliases) Define(gvr *client.GVR, aliases ...string) {
+ if gvr.String() == "deployment" {
+ fmt.Println("!!YO!!")
+ }
a.mx.Lock()
defer a.mx.Unlock()
-
- // BOZO!! Could not get full events struct using this api group??
- if gvr == "events.k8s.io/v1/events" || gvr == "extensions/v1beta1" {
- return
- }
-
for _, alias := range aliases {
if _, ok := a.Alias[alias]; !ok && alias != "" {
a.Alias[alias] = gvr
@@ -117,12 +111,10 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
// Load K9s aliases.
func (a *Aliases) Load(path string) error {
a.loadDefaultAliases()
-
f, err := EnsureAliasesCfgFile()
if err != nil {
slog.Error("Unable to gen config aliases", slogs.Error, err)
}
-
// load global alias file
if err := a.LoadFile(f); err != nil {
return err
@@ -132,11 +124,18 @@ func (a *Aliases) Load(path string) error {
return a.LoadFile(path)
}
+type aliases struct {
+ Alias map[string]string `yaml:"aliases"`
+}
+
+func newAliases(s int) aliases {
+ return aliases{
+ Alias: make(map[string]string, s),
+ }
+}
+
// LoadFile loads alias from a given file.
func (a *Aliases) LoadFile(path string) error {
- if path == "" {
- return nil
- }
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
@@ -149,23 +148,23 @@ func (a *Aliases) LoadFile(path string) error {
slog.Warn("Aliases validation failed", slogs.Error, err)
}
- var aa Aliases
+ var aa aliases
if err := yaml.Unmarshal(bb, &aa); err != nil {
return err
}
a.mx.Lock()
defer a.mx.Unlock()
- for k, v := range aa.Alias {
- a.Alias[k] = v
+ for alias, cmd := range aa.Alias {
+ a.Alias[alias] = client.NewGVR(cmd)
}
return nil
}
-func (a *Aliases) declare(key string, aliases ...string) {
- a.Alias[key] = key
+func (a *Aliases) declare(gvr *client.GVR, aliases ...string) {
+ a.Alias[gvr.String()] = gvr
for _, alias := range aliases {
- a.Alias[alias] = key
+ a.Alias[alias] = gvr
}
}
@@ -173,20 +172,20 @@ func (a *Aliases) loadDefaultAliases() {
a.mx.Lock()
defer a.mx.Unlock()
- a.declare("help", "h", "?")
- a.declare("quit", "q", "q!", "qa", "Q")
- a.declare("aliases", "alias", "a")
- a.declare("helm", "charts", "chart", "hm")
- a.declare("dir", "d")
- a.declare("contexts", "context", "ctx")
- a.declare("users", "user", "usr")
- a.declare("groups", "group", "grp")
- a.declare("portforwards", "portforward", "pf")
- a.declare("benchmarks", "benchmark", "bench")
- a.declare("screendumps", "screendump", "sd")
- a.declare("pulses", "pulse", "pu", "hz")
- a.declare("xrays", "xray", "x")
- a.declare("workloads", "workload", "wk")
+ a.declare(client.HlpGVR, "h", "?")
+ a.declare(client.QGVR, "q", "q!", "qa", "Q")
+ a.declare(client.AliGVR, "alias", "a")
+ a.declare(client.HmGVR, "charts", "chart", "hm")
+ a.declare(client.DirGVR, "dir", "d")
+ a.declare(client.CtGVR, "context", "ctx")
+ a.declare(client.UsrGVR, "user", "usr")
+ a.declare(client.GrpGVR, "group", "grp")
+ a.declare(client.PfGVR, "portforward", "pf")
+ a.declare(client.BeGVR, "benchmark", "bench")
+ a.declare(client.SdGVR, "screendump", "sd")
+ a.declare(client.PuGVR, "pulse", "pu", "hz")
+ a.declare(client.XGVR, "xray", "x")
+ a.declare(client.WkGVR, "workload", "wk")
}
// Save alias to disk.
@@ -200,6 +199,10 @@ func (a *Aliases) SaveAliases(path string) error {
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
return err
}
+ aa := newAliases(len(a.Alias))
+ for alias, gvr := range a.Alias {
+ aa.Alias[alias] = gvr.String()
+ }
- return data.SaveYAML(path, a)
+ return data.SaveYAML(path, aa)
}
diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go
index c67f4f58..9391663d 100644
--- a/internal/config/alias_test.go
+++ b/internal/config/alias_test.go
@@ -4,44 +4,45 @@
package config_test
import (
- "fmt"
+ "maps"
"os"
"path"
"slices"
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestAliasClear(t *testing.T) {
a := testAliases()
a.Clear()
- assert.Equal(t, 0, len(a.Keys()))
+ assert.Empty(t, slices.Collect(maps.Keys(a.Alias)))
}
func TestAliasKeys(t *testing.T) {
a := testAliases()
- kk := a.Keys()
- slices.Sort(kk)
+ kk := maps.Keys(a.Alias)
- assert.Equal(t, []string{"a1", "a11", "a2", "a3"}, kk)
+ assert.Equal(t, []string{"a1", "a11", "a2", "a3"}, slices.Sorted(kk))
}
func TestAliasShortNames(t *testing.T) {
a := testAliases()
ess := config.ShortNames{
- "gvr1": []string{"a1", "a11"},
- "gvr2": []string{"a2"},
- "gvr3": []string{"a3"},
+ gvr1: []string{"a1", "a11"},
+ gvr2: []string{"a2"},
+ gvr3: []string{"a3"},
}
ss := a.ShortNames()
- assert.Equal(t, len(ess), len(ss))
+ assert.Len(t, ss, len(ess))
for k, v := range ss {
v1, ok := ess[k]
- assert.True(t, ok, fmt.Sprintf("missing: %q", k))
+ assert.True(t, ok, "missing: %q", k)
slices.Sort(v)
assert.Equal(t, v1, v)
}
@@ -49,41 +50,41 @@ func TestAliasShortNames(t *testing.T) {
func TestAliasDefine(t *testing.T) {
type aliasDef struct {
- cmd string
+ gvr *client.GVR
aliases []string
}
uu := map[string]struct {
aliases []aliasDef
- registeredCommands map[string]string
+ registeredCommands map[string]*client.GVR
}{
"simple": {
aliases: []aliasDef{
{
- cmd: "one",
+ gvr: client.NewGVR("one"),
aliases: []string{"blee", "duh"},
},
},
- registeredCommands: map[string]string{
- "blee": "one",
- "duh": "one",
+ registeredCommands: map[string]*client.GVR{
+ "blee": client.NewGVR("one"),
+ "duh": client.NewGVR("one"),
},
},
"duplicates": {
aliases: []aliasDef{
{
- cmd: "one",
+ gvr: client.NewGVR("one"),
aliases: []string{"blee", "duh"},
}, {
- cmd: "two",
+ gvr: client.NewGVR("two"),
aliases: []string{"blee", "duh", "fred", "zorg"},
},
},
- registeredCommands: map[string]string{
- "blee": "one",
- "duh": "one",
- "fred": "two",
- "zorg": "two",
+ registeredCommands: map[string]*client.GVR{
+ "blee": client.NewGVR("one"),
+ "duh": client.NewGVR("one"),
+ "fred": client.NewGVR("two"),
+ "zorg": client.NewGVR("two"),
},
},
}
@@ -94,7 +95,7 @@ func TestAliasDefine(t *testing.T) {
configAlias := config.NewAliases()
for _, aliases := range u.aliases {
for _, a := range aliases.aliases {
- configAlias.Define(aliases.cmd, a)
+ configAlias.Define(aliases.gvr, a)
}
}
for alias, cmd := range u.registeredCommands {
@@ -109,33 +110,39 @@ func TestAliasDefine(t *testing.T) {
func TestAliasesLoad(t *testing.T) {
config.AppConfigDir = "testdata/aliases"
a := config.NewAliases()
+ require.NoError(t, a.Load(path.Join(config.AppConfigDir, "plain.yaml")))
- assert.Nil(t, a.Load(path.Join(config.AppConfigDir, "plain.yaml")))
- assert.Equal(t, 54, len(a.Alias))
+ assert.Len(t, a.Alias, 55)
}
func TestAliasesSave(t *testing.T) {
- assert.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod))
- defer assert.NoError(t, os.RemoveAll("/tmp/test-aliases"))
+ require.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod))
+ defer require.NoError(t, os.RemoveAll("/tmp/test-aliases"))
config.AppAliasesFile = "/tmp/test-aliases/aliases.yaml"
a := testAliases()
c := len(a.Alias)
- assert.Equal(t, c, len(a.Alias))
- assert.Nil(t, a.Save())
- assert.Nil(t, a.LoadFile(config.AppAliasesFile))
- assert.Equal(t, c, len(a.Alias))
+ assert.Len(t, a.Alias, c)
+ require.NoError(t, a.Save())
+ require.NoError(t, a.LoadFile(config.AppAliasesFile))
+ assert.Len(t, a.Alias, c)
}
// Helpers...
+var (
+ gvr1 = client.NewGVR("gvr1")
+ gvr2 = client.NewGVR("gvr2")
+ gvr3 = client.NewGVR("gvr3")
+)
+
func testAliases() *config.Aliases {
a := config.NewAliases()
- a.Alias["a1"] = "gvr1"
- a.Alias["a11"] = "gvr1"
- a.Alias["a2"] = "gvr2"
- a.Alias["a3"] = "gvr3"
+ a.Alias["a1"] = gvr1
+ a.Alias["a11"] = gvr1
+ a.Alias["a2"] = gvr2
+ a.Alias["a3"] = gvr3
return a
}
diff --git a/internal/config/benchmark_test.go b/internal/config/benchmark_test.go
index cd80d442..c9f272fa 100644
--- a/internal/config/benchmark_test.go
+++ b/internal/config/benchmark_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestBenchEmpty(t *testing.T) {
@@ -55,11 +56,11 @@ func TestBenchLoad(t *testing.T) {
t.Run(k, func(t *testing.T) {
b, err := NewBench(u.file)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.c, b.Benchmarks.Defaults.C)
assert.Equal(t, u.n, b.Benchmarks.Defaults.N)
- assert.Equal(t, u.svcCount, len(b.Benchmarks.Services))
- assert.Equal(t, u.coCount, len(b.Benchmarks.Containers))
+ assert.Len(t, b.Benchmarks.Services, u.svcCount)
+ assert.Len(t, b.Benchmarks.Containers, u.coCount)
})
}
}
@@ -105,8 +106,8 @@ func TestBenchServiceLoad(t *testing.T) {
t.Run(k, func(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_good.yaml")
- assert.Nil(t, err)
- assert.Equal(t, 2, len(b.Benchmarks.Services))
+ require.NoError(t, err)
+ assert.Len(t, b.Benchmarks.Services, 2)
svc := b.Benchmarks.Services[u.key]
assert.Equal(t, u.c, svc.C)
assert.Equal(t, u.n, svc.N)
@@ -123,16 +124,16 @@ func TestBenchServiceLoad(t *testing.T) {
func TestBenchReLoad(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, 2, b.Benchmarks.Defaults.C)
- assert.NoError(t, b.Reload("testdata/benchmarks/b_containers_1.yaml"))
+ require.NoError(t, b.Reload("testdata/benchmarks/b_containers_1.yaml"))
assert.Equal(t, 20, b.Benchmarks.Defaults.C)
}
func TestBenchLoadToast(t *testing.T) {
_, err := NewBench("testdata/toast.yaml")
- assert.NotNil(t, err)
+ assert.Error(t, err)
}
func TestBenchContainerLoad(t *testing.T) {
@@ -176,8 +177,8 @@ func TestBenchContainerLoad(t *testing.T) {
t.Run(k, func(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
- assert.Nil(t, err)
- assert.Equal(t, 2, len(b.Benchmarks.Services))
+ require.NoError(t, err)
+ assert.Len(t, b.Benchmarks.Services, 2)
co := b.Benchmarks.Containers[u.key]
assert.Equal(t, u.c, co.C)
assert.Equal(t, u.n, co.N)
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index a8db98ec..031eb742 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -18,6 +18,7 @@ import (
"github.com/derailed/k9s/internal/config/mock"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
@@ -54,16 +55,16 @@ func TestConfigSave(t *testing.T) {
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig()
_, err := c.K9s.ActivateContext(u.ct)
- assert.NoError(t, err)
+ require.NoError(t, err)
if u.flags != nil {
c.K9s.Override(u.k9sFlags)
- assert.NoError(t, c.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags)))
+ require.NoError(t, c.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags)))
}
- assert.NoError(t, c.Save(true))
+ require.NoError(t, c.Save(true))
bb, err := os.ReadFile(config.AppConfigFile)
- assert.NoError(t, err)
+ require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/default.yaml")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, string(ee), string(bb))
})
}
@@ -115,7 +116,7 @@ func TestSetActiveView(t *testing.T) {
c := mock.NewMockConfig()
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
- assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
+ require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
c.SetActiveView(u.view)
@@ -158,7 +159,7 @@ func TestActiveContextName(t *testing.T) {
c := mock.NewMockConfig()
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
- assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
+ require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
assert.Equal(t, u.e, c.ActiveContextName())
@@ -206,7 +207,7 @@ func TestActiveView(t *testing.T) {
c := mock.NewMockConfig()
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
- assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
+ require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
assert.Equal(t, u.e, c.ActiveView())
@@ -349,7 +350,7 @@ func TestConfigActivateContext(t *testing.T) {
assert.Equal(t, u.err, err.Error())
return
}
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.cl, ct.ClusterName)
})
}
@@ -393,9 +394,9 @@ func TestConfigCurrentContext(t *testing.T) {
cfg := mock.NewMockConfig()
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
- assert.NoError(t, err)
+ require.NoError(t, err)
ct, err := cfg.CurrentContext()
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.cluster, ct.ClusterName)
assert.Equal(t, u.namespace, ct.Namespace.Active)
})
@@ -408,7 +409,7 @@ func TestConfigRefine(t *testing.T) {
cl1 = "cl-1"
ct2 = "ct-1-2"
ns1, ns2, nsx = "ns-1", "ns-2", "ns-x"
- true = true
+ trueVal = true
)
uu := map[string]struct {
@@ -465,7 +466,7 @@ func TestConfigRefine(t *testing.T) {
Namespace: &ns2,
},
k9sFlags: &config.Flags{
- AllNamespaces: &true,
+ AllNamespaces: &trueVal,
},
cluster: "cl-1",
context: "ct-1-1",
@@ -516,7 +517,7 @@ func TestConfigRefine(t *testing.T) {
if err != nil {
assert.Equal(t, u.err, err.Error())
} else {
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.context, cfg.K9s.ActiveContextName())
assert.Equal(t, u.namespace, cfg.ActiveNamespace())
}
@@ -528,14 +529,14 @@ func TestConfigValidate(t *testing.T) {
cfg := mock.NewMockConfig()
cfg.SetConnection(mock.NewMockConnection())
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Validate("ct-1-1", "cl-1")
}
func TestConfigLoad(t *testing.T) {
cfg := mock.NewMockConfig()
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, 2, cfg.K9s.RefreshRate)
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
@@ -544,13 +545,13 @@ func TestConfigLoad(t *testing.T) {
func TestConfigLoadCrap(t *testing.T) {
cfg := mock.NewMockConfig()
- assert.NotNil(t, cfg.Load("testdata/configs/k9s_not_there.yaml", true))
+ assert.Error(t, cfg.Load("testdata/configs/k9s_not_there.yaml", true))
}
func TestConfigSaveFile(t *testing.T) {
cfg := mock.NewMockConfig()
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.K9s.RefreshRate = 100
cfg.K9s.ReadOnly = true
@@ -559,28 +560,28 @@ func TestConfigSaveFile(t *testing.T) {
cfg.K9s.UI.UseFullGVRTitle = true
cfg.Validate("ct-1-1", "cl-1")
- path := filepath.Join("/tmp", "k9s.yaml")
- assert.NoError(t, cfg.SaveFile(path))
+ path := filepath.Join(os.TempDir(), "k9s.yaml")
+ require.NoError(t, cfg.SaveFile(path))
raw, err := os.ReadFile(path)
- assert.Nil(t, err)
+ require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/expected.yaml")
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, string(ee), string(raw))
}
func TestConfigReset(t *testing.T) {
cfg := mock.NewMockConfig()
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Reset()
cfg.Validate("ct-1-1", "cl-1")
- path := filepath.Join("/tmp", "k9s.yaml")
- assert.NoError(t, cfg.SaveFile(path))
+ path := filepath.Join(os.TempDir(), "k9s.yaml")
+ require.NoError(t, cfg.SaveFile(path))
bb, err := os.ReadFile(path)
- assert.Nil(t, err)
+ require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/k9s.yaml")
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, string(ee), string(bb))
}
diff --git a/internal/config/data/context.go b/internal/config/data/context.go
index 15021033..060e01eb 100644
--- a/internal/config/data/context.go
+++ b/internal/config/data/context.go
@@ -68,7 +68,7 @@ func (c *Context) GetClusterName() string {
}
// Validate ensures a context config is tip top.
-func (c *Context) Validate(conn client.Connection, contextName, clusterName string) {
+func (c *Context) Validate(conn client.Connection, _, clusterName string) {
c.mx.Lock()
defer c.mx.Unlock()
diff --git a/internal/config/data/context_test.go b/internal/config/data/context_test.go
index 30318ead..8f313df5 100644
--- a/internal/config/data/context_test.go
+++ b/internal/config/data/context_test.go
@@ -17,7 +17,7 @@ func TestClusterValidate(t *testing.T) {
assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
- assert.Equal(t, 1, len(c.Namespace.Favorites))
+ assert.Len(t, c.Namespace.Favorites, 1)
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}
@@ -27,6 +27,6 @@ func TestClusterValidateEmpty(t *testing.T) {
assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
- assert.Equal(t, 1, len(c.Namespace.Favorites))
+ assert.Len(t, c.Namespace.Favorites, 1)
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}
diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go
index 4d29943b..0ef36d9a 100644
--- a/internal/config/data/dir_test.go
+++ b/internal/config/data/dir_test.go
@@ -12,6 +12,7 @@ import (
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
@@ -68,12 +69,12 @@ func TestDirLoad(t *testing.T) {
ks := mock.NewMockKubeSettings(u.flags)
if strings.Index(u.dir, "/tmp") == 0 {
- assert.NoError(t, mock.EnsureDir(u.dir))
+ require.NoError(t, mock.EnsureDir(u.dir))
}
d := data.NewDir(u.dir)
ct, err := ks.CurrentContext()
- assert.NoError(t, err)
+ require.NoError(t, err)
if err != nil {
return
}
diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go
index beae284a..d2bce306 100644
--- a/internal/config/data/helpers.go
+++ b/internal/config/data/helpers.go
@@ -44,8 +44,8 @@ func EnsureDirPath(path string, mod os.FileMode) error {
// EnsureFullPath ensures a directory exist from the given path.
func EnsureFullPath(path string, mod os.FileMode) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
- if err = os.MkdirAll(path, mod); err != nil {
- return err
+ if e := os.MkdirAll(path, mod); e != nil {
+ return e
}
}
diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go
index fc7b6cfa..b81895ea 100644
--- a/internal/config/data/helpers_test.go
+++ b/internal/config/data/helpers_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestSanitizeFileName(t *testing.T) {
@@ -65,27 +66,27 @@ func TestHelperInList(t *testing.T) {
func TestEnsureDirPathNone(t *testing.T) {
const mod = 0744
- dir := filepath.Join("/tmp", "k9s-test")
+ dir := filepath.Join(os.TempDir(), "k9s-test")
_ = os.Remove(dir)
path := filepath.Join(dir, "duh.yaml")
- assert.NoError(t, data.EnsureDirPath(path, mod))
+ require.NoError(t, data.EnsureDirPath(path, mod))
p, err := os.Stat(dir)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}
func TestEnsureDirPathNoOpt(t *testing.T) {
var mod os.FileMode = 0744
- dir := filepath.Join("/tmp", "k9s-test")
- assert.NoError(t, os.RemoveAll(dir))
- assert.NoError(t, os.Mkdir(dir, mod))
+ dir := filepath.Join(os.TempDir(), "k9s-test")
+ require.NoError(t, os.RemoveAll(dir))
+ require.NoError(t, os.Mkdir(dir, mod))
path := filepath.Join(dir, "duh.yaml")
- assert.NoError(t, data.EnsureDirPath(path, mod))
+ require.NoError(t, data.EnsureDirPath(path, mod))
p, err := os.Stat(dir)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}
diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go
index f37488f1..b345b9a2 100644
--- a/internal/config/data/ns.go
+++ b/internal/config/data/ns.go
@@ -80,7 +80,7 @@ func (n *Namespace) Validate(conn client.Connection) {
}
// SetActive set the active namespace.
-func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
+func (n *Namespace) SetActive(ns string, _ KubeSettings) error {
if n == nil {
n = NewActiveNamespace(ns)
}
@@ -111,7 +111,7 @@ func (n *Namespace) addFavNS(ns string) {
nfv := make([]string, 0, MaxFavoritesNS)
nfv = append(nfv, ns)
- for i := 0; i < len(n.Favorites); i++ {
+ for i := range n.Favorites {
if i+1 < MaxFavoritesNS {
nfv = append(nfv, n.Favorites[i])
}
diff --git a/internal/config/data/ns_test.go b/internal/config/data/ns_test.go
index 5d39cd37..16453835 100644
--- a/internal/config/data/ns_test.go
+++ b/internal/config/data/ns_test.go
@@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNSValidate(t *testing.T) {
@@ -41,7 +42,7 @@ func TestNsValidateMaxNS(t *testing.T) {
ns.Favorites = allNS
ns.Validate(mock.NewMockConnection())
- assert.Equal(t, data.MaxFavoritesNS, len(ns.Favorites))
+ assert.Len(t, ns.Favorites, data.MaxFavoritesNS)
}
func TestNSSetActive(t *testing.T) {
@@ -61,7 +62,7 @@ func TestNSSetActive(t *testing.T) {
ns := data.NewNamespace()
for _, u := range uu {
err := ns.SetActive(u.ns, mk)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.ns, ns.Active)
assert.Equal(t, u.fav, ns.Favorites)
}
diff --git a/internal/config/data/view.go b/internal/config/data/view.go
index 044972eb..7c77b467 100644
--- a/internal/config/data/view.go
+++ b/internal/config/data/view.go
@@ -17,7 +17,7 @@ func NewView() *View {
// Validate a view configuration.
func (v *View) Validate() {
- if len(v.Active) == 0 {
+ if v.Active == "" {
v.Active = DefaultView
}
}
diff --git a/internal/config/files.go b/internal/config/files.go
index c3d2f675..6e806255 100644
--- a/internal/config/files.go
+++ b/internal/config/files.go
@@ -178,8 +178,8 @@ func initXDGLocs() error {
AppViewsFile = filepath.Join(AppConfigDir, "views.yaml")
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
- if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil {
- slog.Warn("No skins dir detected", slogs.Error, err)
+ if e := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); e != nil {
+ slog.Warn("No skins dir detected", slogs.Error, e)
}
AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
diff --git a/internal/config/files_int_test.go b/internal/config/files_int_test.go
index c6094ead..cdcb00e4 100644
--- a/internal/config/files_int_test.go
+++ b/internal/config/files_int_test.go
@@ -11,21 +11,22 @@ import (
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_initXDGLocs(t *testing.T) {
tmp, err := UserTmpDir()
- assert.NoError(t, err)
+ require.NoError(t, err)
- assert.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
- assert.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
- assert.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
- assert.NoError(t, os.Unsetenv("XDG_DATA_HOME"))
+ require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
+ require.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
+ require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
+ require.NoError(t, os.Unsetenv("XDG_DATA_HOME"))
- assert.NoError(t, os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "k9s-xdg", "config")))
- assert.NoError(t, os.Setenv("XDG_CACHE_HOME", filepath.Join(tmp, "k9s-xdg", "cache")))
- assert.NoError(t, os.Setenv("XDG_STATE_HOME", filepath.Join(tmp, "k9s-xdg", "state")))
- assert.NoError(t, os.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "k9s-xdg", "data")))
+ require.NoError(t, os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "k9s-xdg", "config")))
+ require.NoError(t, os.Setenv("XDG_CACHE_HOME", filepath.Join(tmp, "k9s-xdg", "cache")))
+ require.NoError(t, os.Setenv("XDG_STATE_HOME", filepath.Join(tmp, "k9s-xdg", "state")))
+ require.NoError(t, os.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "k9s-xdg", "data")))
xdg.Reload()
uu := map[string]struct {
@@ -55,7 +56,7 @@ func Test_initXDGLocs(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.NoError(t, initXDGLocs())
+ require.NoError(t, initXDGLocs())
assert.Equal(t, u.configDir, AppConfigDir)
assert.Equal(t, u.configFile, AppConfigFile)
assert.Equal(t, u.benchmarksDir, AppBenchmarksDir)
@@ -63,13 +64,13 @@ func Test_initXDGLocs(t *testing.T) {
assert.Equal(t, u.contextHotkeysFile, AppContextHotkeysFile("cl-1", "ct-1-1"))
assert.Equal(t, u.contextConfig, AppContextConfig("cl-1", "ct-1-1"))
dir, err := DumpsDir("cl-1", "ct-1-1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.dumpsDir, dir)
bdir, err := EnsureBenchmarksDir("cl-1", "ct-1-1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.benchDir, bdir)
hk, err := EnsureHotkeysCfgFile()
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.hkFile, hk)
})
}
diff --git a/internal/config/files_test.go b/internal/config/files_test.go
index 99da056a..2356e110 100644
--- a/internal/config/files_test.go
+++ b/internal/config/files_test.go
@@ -12,11 +12,12 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestInitLogLoc(t *testing.T) {
tmp, err := config.UserTmpDir()
- assert.NoError(t, err)
+ require.NoError(t, err)
uu := map[string]struct {
dir string
@@ -39,33 +40,33 @@ func TestInitLogLoc(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.NoError(t, os.Unsetenv(config.K9sEnvLogsDir))
- assert.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
- assert.NoError(t, os.Unsetenv(config.K9sEnvConfigDir))
+ require.NoError(t, os.Unsetenv(config.K9sEnvLogsDir))
+ require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
+ require.NoError(t, os.Unsetenv(config.K9sEnvConfigDir))
switch k {
case "log-env":
- assert.NoError(t, os.Setenv(config.K9sEnvLogsDir, u.dir))
+ require.NoError(t, os.Setenv(config.K9sEnvLogsDir, u.dir))
case "xdg-env":
- assert.NoError(t, os.Setenv("XDG_STATE_HOME", u.dir))
+ require.NoError(t, os.Setenv("XDG_STATE_HOME", u.dir))
xdg.Reload()
case "cfg-env":
- assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, u.dir))
+ require.NoError(t, os.Setenv(config.K9sEnvConfigDir, u.dir))
}
err := config.InitLogLoc()
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, config.AppLogFile)
- assert.NoError(t, os.RemoveAll(config.AppLogFile))
+ require.NoError(t, os.RemoveAll(config.AppLogFile))
})
}
}
func TestEnsureBenchmarkCfg(t *testing.T) {
- assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
- assert.NoError(t, config.InitLocs())
- defer assert.NoError(t, os.RemoveAll("/tmp/test-config"))
+ require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
+ require.NoError(t, config.InitLocs())
+ defer require.NoError(t, os.RemoveAll("/tmp/test-config"))
- assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod))
- assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
+ require.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod))
+ require.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
uu := map[string]struct {
cluster, context string
@@ -88,10 +89,10 @@ func TestEnsureBenchmarkCfg(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
f, err := config.EnsureBenchmarksCfgFile(u.cluster, u.context)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.f, f)
bb, err := os.ReadFile(f)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, string(bb))
})
}
@@ -99,7 +100,7 @@ func TestEnsureBenchmarkCfg(t *testing.T) {
func TestSkinFileFromName(t *testing.T) {
config.AppSkinsDir = "/tmp/k9s-test/skins"
- defer assert.NoError(t, os.RemoveAll("/tmp/k9s-test/skins"))
+ defer require.NoError(t, os.RemoveAll("/tmp/k9s-test/skins"))
uu := map[string]struct {
n string
diff --git a/internal/config/helpers.go b/internal/config/helpers.go
index 08b5a174..b56c9816 100644
--- a/internal/config/helpers.go
+++ b/internal/config/helpers.go
@@ -23,7 +23,7 @@ func IsBoolSet(b *bool) bool {
}
func isStringSet(s *string) bool {
- return s != nil && len(*s) > 0
+ return s != nil && *s != ""
}
func isYamlFile(file string) bool {
diff --git a/internal/config/hotkey_test.go b/internal/config/hotkey_test.go
index 66c986af..6ee93c97 100644
--- a/internal/config/hotkey_test.go
+++ b/internal/config/hotkey_test.go
@@ -8,18 +8,18 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestHotKeyLoad(t *testing.T) {
h := config.NewHotKeys()
- assert.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml"))
-
- assert.Equal(t, 1, len(h.HotKey))
+ require.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml"))
+ assert.Len(t, h.HotKey, 1)
k, ok := h.HotKey["pods"]
assert.True(t, ok)
assert.Equal(t, "shift-0", k.ShortCut)
assert.Equal(t, "Launch pod view", k.Description)
assert.Equal(t, "pods", k.Command)
- assert.Equal(t, true, k.KeepHistory)
+ assert.True(t, k.KeepHistory)
}
diff --git a/internal/config/json/validator_test.go b/internal/config/json/validator_test.go
index 7b78f251..79e74d2b 100644
--- a/internal/config/json/validator_test.go
+++ b/internal/config/json/validator_test.go
@@ -4,23 +4,22 @@
package json_test
import (
- "fmt"
"os"
"path/filepath"
- "strings"
"testing"
"github.com/derailed/k9s/internal/config/json"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestValidatePluginSnippet(t *testing.T) {
plugPath := "testdata/plugins/snippet.yaml"
bb, err := os.ReadFile(plugPath)
- assert.NoError(t, err)
+ require.NoError(t, err)
p := json.NewValidator()
- assert.NoError(t, p.Validate(json.PluginSchema, bb), plugPath)
+ require.NoError(t, p.Validate(json.PluginSchema, bb), plugPath)
}
func TestValidatePlugins(t *testing.T) {
@@ -51,7 +50,7 @@ func TestValidatePlugins(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.path)
- assert.NoError(t, err)
+ require.NoError(t, err)
v := json.NewValidator()
if err := v.Validate(u.schema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
@@ -63,7 +62,7 @@ func TestValidatePlugins(t *testing.T) {
func TestValidatePluginDir(t *testing.T) {
plugDir := "../../../plugins"
ee, err := os.ReadDir(plugDir)
- assert.NoError(t, err)
+ require.NoError(t, err)
for _, e := range ee {
if e.IsDir() {
continue
@@ -72,31 +71,31 @@ func TestValidatePluginDir(t *testing.T) {
if ext == ".md" {
continue
}
- assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name()))
- assert.False(t, strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name()))
+ assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
+ assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
bb, err := os.ReadFile(filepath.Join(plugDir, e.Name()))
- assert.NoError(t, err)
+ require.NoError(t, err)
p := json.NewValidator()
- assert.NoError(t, p.Validate(json.PluginsSchema, bb), e.Name())
+ require.NoError(t, p.Validate(json.PluginsSchema, bb), e.Name())
}
}
func TestValidateSkinDir(t *testing.T) {
skinDir := "../../../skins"
ee, err := os.ReadDir(skinDir)
- assert.NoError(t, err)
+ require.NoError(t, err)
p := json.NewValidator()
for _, e := range ee {
if e.IsDir() {
continue
}
ext := filepath.Ext(e.Name())
- assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name()))
- assert.True(t, !strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name()))
+ assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
+ assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
bb, err := os.ReadFile(filepath.Join(skinDir, e.Name()))
- assert.NoError(t, err)
- assert.NoError(t, p.Validate(json.SkinSchema, bb), e.Name())
+ require.NoError(t, err)
+ require.NoError(t, p.Validate(json.SkinSchema, bb), e.Name())
}
}
@@ -119,7 +118,7 @@ func TestValidateSkin(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
- assert.NoError(t, err)
+ require.NoError(t, err)
if err := v.Validate(json.SkinSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
@@ -146,7 +145,7 @@ func TestValidateK9s(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
- assert.NoError(t, err)
+ require.NoError(t, err)
if err := v.Validate(json.K9sSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
@@ -174,7 +173,7 @@ Additional property namespaces is not allowed`,
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
- assert.NoError(t, err)
+ require.NoError(t, err)
if err := v.Validate(json.ContextSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
@@ -202,7 +201,7 @@ aliases is required`,
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
- assert.NoError(t, err)
+ require.NoError(t, err)
if err := v.Validate(json.AliasesSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
@@ -232,7 +231,7 @@ columns is required`,
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
- assert.NoError(t, err)
+ require.NoError(t, err)
if err := v.Validate(json.ViewsSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
diff --git a/internal/config/k9s.go b/internal/config/k9s.go
index 55c89df3..df2752de 100644
--- a/internal/config/k9s.go
+++ b/internal/config/k9s.go
@@ -24,14 +24,14 @@ type K9s struct {
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
- MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"`
+ MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
PortForwardAddress string `yaml:"portForwardAddress"`
UI UI `json:"ui" yaml:"ui"`
SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"`
DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"`
- ShellPod ShellPod `json:"shellPod" yaml:"shellPod"`
+ ShellPod *ShellPod `json:"shellPod" yaml:"shellPod"`
ImageScans ImageScans `json:"imageScans" yaml:"imageScans"`
Logger Logger `json:"logger" yaml:"logger"`
Thresholds Threshold `json:"thresholds" yaml:"thresholds"`
@@ -95,7 +95,11 @@ func (k *K9s) Save(contextName, clusterName string, force bool) error {
)
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) || force {
- slog.Debug("[CONFIG] Saving context config to disk", slogs.Path, path, slogs.Cluster, k.getActiveConfig().Context.GetClusterName(), slogs.Context, k.getActiveContextName())
+ slog.Debug("[CONFIG] Saving context config to disk",
+ slogs.Path, path,
+ slogs.Cluster, k.getActiveConfig().Context.GetClusterName(),
+ slogs.Context, k.getActiveContextName(),
+ )
return k.dir.Save(path, k.getActiveConfig())
}
@@ -298,8 +302,8 @@ func (k *K9s) Override(k9sFlags *Flags) {
k.manualReadOnly = k9sFlags.ReadOnly
}
if k9sFlags.Write != nil && *k9sFlags.Write {
- var false bool
- k.manualReadOnly = &false
+ var falseVal bool
+ k.manualReadOnly = &falseVal
}
k.manualCommand = k9sFlags.Command
k.manualScreenDumpDir = k9sFlags.ScreenDumpDir
@@ -382,7 +386,7 @@ func (k *K9s) Validate(c client.Connection, contextName, clusterName string) {
if k.getActiveConfig() == nil {
_, _ = k.ActivateContext(contextName)
}
- k.ShellPod = k.ShellPod.Validate()
+ k.ShellPod.Validate()
k.Logger = k.Logger.Validate()
k.Thresholds = k.Thresholds.Validate()
diff --git a/internal/config/k9s_int_test.go b/internal/config/k9s_int_test.go
index c6113e10..a2b18ec5 100644
--- a/internal/config/k9s_int_test.go
+++ b/internal/config/k9s_int_test.go
@@ -7,13 +7,14 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_k9sOverrides(t *testing.T) {
var (
- true = true
- cmd = "po"
- dir = "/tmp/blee"
+ trueVal = true
+ cmd = "po"
+ dir = "/tmp/blee"
)
uu := map[string]struct {
@@ -71,15 +72,15 @@ func Test_k9sOverrides(t *testing.T) {
Headless: false,
Logoless: false,
Crumbsless: false,
- manualHeadless: &true,
- manualLogoless: &true,
- manualCrumbsless: &true,
- manualSplashless: &true,
+ manualHeadless: &trueVal,
+ manualLogoless: &trueVal,
+ manualCrumbsless: &trueVal,
+ manualSplashless: &trueVal,
},
SkipLatestRevCheck: false,
DisablePodCounting: false,
manualRefreshRate: 100,
- manualReadOnly: &true,
+ manualReadOnly: &trueVal,
manualCommand: &cmd,
manualScreenDumpDir: &dir,
},
@@ -123,7 +124,7 @@ func Test_screenDumpDirOverride(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := NewConfig(nil)
- assert.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.K9s.manualScreenDumpDir = &u.dir
assert.Equal(t, u.e, cfg.K9s.AppScreenDumpDir())
diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go
index d74c59c3..6afc155a 100644
--- a/internal/config/k9s_test.go
+++ b/internal/config/k9s_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
@@ -94,7 +95,7 @@ func TestK9sMerge(t *testing.T) {
UI: config.UI{},
SkipLatestRevCheck: false,
DisablePodCounting: false,
- ShellPod: config.ShellPod{},
+ ShellPod: new(config.ShellPod),
ImageScans: config.ImageScans{},
Logger: config.Logger{},
Thresholds: nil,
@@ -135,14 +136,14 @@ func TestContextScreenDumpDir(t *testing.T) {
cfg := mock.NewMockConfig()
_, err := cfg.K9s.ActivateContext("ct-1-1")
- assert.NoError(t, err)
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, err)
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, "/tmp/k9s-test/screen-dumps/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir())
}
func TestAppScreenDumpDir(t *testing.T) {
cfg := mock.NewMockConfig()
- assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
+ require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, "/tmp/k9s-test/screen-dumps", cfg.K9s.AppScreenDumpDir())
}
diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go
index 0f92ab0f..97ab1f65 100644
--- a/internal/config/mock/test_helpers.go
+++ b/internal/config/mock/test_helpers.go
@@ -89,7 +89,7 @@ func (m mockKubeSettings) CurrentContextName() (string, error) {
func (m mockKubeSettings) CurrentClusterName() (string, error) {
return *m.flags.ClusterName, nil
}
-func (m mockKubeSettings) CurrentNamespaceName() (string, error) {
+func (mockKubeSettings) CurrentNamespaceName() (string, error) {
return "default", nil
}
func (m mockKubeSettings) GetContext(s string) (*api.Context, error) {
@@ -111,7 +111,7 @@ func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) {
return mm, nil
}
-func (m mockKubeSettings) SetProxy(proxy func(*http.Request) (*url.URL, error)) {}
+func (mockKubeSettings) SetProxy(func(*http.Request) (*url.URL, error)) {}
type mockConnection struct {
ct string
@@ -124,57 +124,57 @@ func NewMockConnectionWithContext(ct string) mockConnection {
return mockConnection{ct: ct}
}
-func (m mockConnection) CanI(ns, gvr, n string, verbs []string) (bool, error) {
+func (mockConnection) CanI(string, *client.GVR, string, []string) (bool, error) {
return true, nil
}
-func (m mockConnection) Config() *client.Config {
+func (mockConnection) Config() *client.Config {
return nil
}
-func (m mockConnection) ConnectionOK() bool {
+func (mockConnection) ConnectionOK() bool {
return false
}
-func (m mockConnection) Dial() (kubernetes.Interface, error) {
+func (mockConnection) Dial() (kubernetes.Interface, error) {
return nil, nil
}
-func (m mockConnection) DialLogs() (kubernetes.Interface, error) {
+func (mockConnection) DialLogs() (kubernetes.Interface, error) {
return nil, nil
}
-func (m mockConnection) SwitchContext(ctx string) error {
+func (mockConnection) SwitchContext(string) error {
return nil
}
-func (m mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
+func (mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
return nil, nil
}
-func (m mockConnection) RestConfig() (*restclient.Config, error) {
+func (mockConnection) RestConfig() (*restclient.Config, error) {
return nil, nil
}
-func (m mockConnection) MXDial() (*versioned.Clientset, error) {
+func (mockConnection) MXDial() (*versioned.Clientset, error) {
return nil, nil
}
-func (m mockConnection) DynDial() (dynamic.Interface, error) {
+func (mockConnection) DynDial() (dynamic.Interface, error) {
return nil, nil
}
-func (m mockConnection) HasMetrics() bool {
+func (mockConnection) HasMetrics() bool {
return false
}
-func (m mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
+func (mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
return nil, nil
}
-func (m mockConnection) IsValidNamespace(string) bool {
+func (mockConnection) IsValidNamespace(string) bool {
return true
}
-func (m mockConnection) ServerVersion() (*version.Info, error) {
+func (mockConnection) ServerVersion() (*version.Info, error) {
return nil, nil
}
-func (m mockConnection) CheckConnectivity() bool {
+func (mockConnection) CheckConnectivity() bool {
return false
}
func (m mockConnection) ActiveContext() string {
return m.ct
}
-func (m mockConnection) ActiveNamespace() string {
+func (mockConnection) ActiveNamespace() string {
return ""
}
-func (m mockConnection) IsActiveNamespace(string) bool {
+func (mockConnection) IsActiveNamespace(string) bool {
return false
}
diff --git a/internal/config/plugin.go b/internal/config/plugin.go
index 4d23d0ff..cc41c12f 100644
--- a/internal/config/plugin.go
+++ b/internal/config/plugin.go
@@ -114,16 +114,16 @@ func (p *Plugins) load(path string) error {
if err := yaml.Unmarshal(bb, &oo); err != nil {
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
}
- for k, v := range oo.Plugins {
- p.Plugins[k] = v
+ for k := range oo.Plugins {
+ p.Plugins[k] = oo.Plugins[k]
}
case json.PluginMultiSchema:
var oo plugins
if err := yaml.Unmarshal(bb, &oo); err != nil {
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
}
- for k, v := range oo {
- p.Plugins[k] = v
+ for k := range oo {
+ p.Plugins[k] = oo[k]
}
}
diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go
index 3bbbc08a..2de77bbb 100644
--- a/internal/config/plugin_test.go
+++ b/internal/config/plugin_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPluginLoad(t *testing.T) {
@@ -103,10 +104,10 @@ func TestSinglePluginFileLoad(t *testing.T) {
}
p := NewPlugins()
- assert.NoError(t, p.load("testdata/plugins/plugins.yaml"))
- assert.NoError(t, p.loadDir("/random/dir/not/exist"))
+ require.NoError(t, p.load("testdata/plugins/plugins.yaml"))
+ require.NoError(t, p.loadDir("/random/dir/not/exist"))
- assert.Equal(t, 1, len(p.Plugins))
+ assert.Len(t, p.Plugins, 1)
v, ok := p.Plugins["blah"]
assert.True(t, ok)
@@ -169,8 +170,8 @@ func TestMultiplePluginFilesLoad(t *testing.T) {
for k, u := range uu {
t.Run(k, func(t *testing.T) {
p := NewPlugins()
- assert.NoError(t, p.load(u.path))
- assert.NoError(t, p.loadDir(u.dir))
+ require.NoError(t, p.load(u.path))
+ require.NoError(t, p.loadDir(u.dir))
assert.Equal(t, u.ee, p)
})
}
diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go
index 08540f2c..43682bcd 100644
--- a/internal/config/shell_pod.go
+++ b/internal/config/shell_pod.go
@@ -26,8 +26,8 @@ type ShellPod struct {
}
// NewShellPod returns a new instance.
-func NewShellPod() ShellPod {
- return ShellPod{
+func NewShellPod() *ShellPod {
+ return &ShellPod{
Image: defaultDockerShellImage,
Namespace: "default",
Limits: defaultLimits(),
@@ -35,15 +35,13 @@ func NewShellPod() ShellPod {
}
// Validate validates the configuration.
-func (s ShellPod) Validate() ShellPod {
+func (s *ShellPod) Validate() {
if s.Image == "" {
s.Image = defaultDockerShellImage
}
if len(s.Limits) == 0 {
s.Limits = defaultLimits()
}
-
- return s
}
func defaultLimits() Limits {
diff --git a/internal/config/styles.go b/internal/config/styles.go
index bdbce4d3..9811ff4e 100644
--- a/internal/config/styles.go
+++ b/internal/config/styles.go
@@ -20,14 +20,21 @@ type StyleListener interface {
StylesChanged(*Styles)
}
+// TextStyle tracks text styles.
type TextStyle string
const (
+ // TextStyleNormal is the default text style.
TextStyleNormal TextStyle = "normal"
- TextStyleBold TextStyle = "bold"
- TextStyleDim TextStyle = "dim"
+
+ // TextStyleBold is the bold text style.
+ TextStyleBold TextStyle = "bold"
+
+ // TextStyleDim is the dim text style.
+ TextStyleDim TextStyle = "dim"
)
+// ToShortString returns a short string representation of the text style.
func (ts TextStyle) ToShortString() string {
switch ts {
case TextStyleNormal:
@@ -283,8 +290,8 @@ func newCharts() Charts {
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
ResourceColors: map[string]Colors{
- "cpu": {Color("dodgerblue"), Color("darkslateblue")},
- "mem": {Color("yellow"), Color("goldenrod")},
+ CPU: {Color("dodgerblue"), Color("darkslateblue")},
+ MEM: {Color("yellow"), Color("goldenrod")},
},
}
}
diff --git a/internal/config/styles_test.go b/internal/config/styles_test.go
index b2ada278..7f754463 100644
--- a/internal/config/styles_test.go
+++ b/internal/config/styles_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/tcell/v2"
"github.com/derailed/tview"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNewStyle(t *testing.T) {
@@ -38,7 +39,7 @@ func TestColor(t *testing.T) {
func TestSkinHappy(t *testing.T) {
s := config.NewStyles()
- assert.Nil(t, s.Load("../../skins/black-and-wtf.yaml"))
+ require.NoError(t, s.Load("../../skins/black-and-wtf.yaml"))
s.Update()
assert.Equal(t, "#ffffff", s.Body().FgColor.String())
diff --git a/internal/config/threshold.go b/internal/config/threshold.go
index 0e1486bf..01046209 100644
--- a/internal/config/threshold.go
+++ b/internal/config/threshold.go
@@ -55,14 +55,14 @@ type Threshold map[string]*Severity
// NewThreshold returns a new threshold.
func NewThreshold() Threshold {
return Threshold{
- "cpu": NewSeverity(),
- "memory": NewSeverity(),
+ CPU: NewSeverity(),
+ MEM: NewSeverity(),
}
}
// Validate a namespace is setup correctly.
func (t Threshold) Validate() Threshold {
- for _, k := range []string{"cpu", "memory"} {
+ for _, k := range []string{CPU, MEM} {
v, ok := t[k]
if !ok {
t[k] = NewSeverity()
@@ -92,7 +92,7 @@ func (t Threshold) LevelFor(k string, v int) SeverityLevel {
// SeverityColor returns a defcon level associated level.
func (t *Threshold) SeverityColor(k string, v int) string {
- // nolint:exhaustive
+ //nolint:exhaustive
switch t.LevelFor(k, v) {
case SeverityHigh:
return "red"
diff --git a/internal/config/threshold_test.go b/internal/config/threshold_test.go
index 11cabae3..360bff71 100644
--- a/internal/config/threshold_test.go
+++ b/internal/config/threshold_test.go
@@ -48,32 +48,37 @@ func TestLevelFor(t *testing.T) {
e config.SeverityLevel
}{
"normal": {
- k: "cpu",
+ k: config.CPU,
v: 0,
e: config.SeverityLow,
},
"4": {
- k: "cpu",
+ k: config.CPU,
v: 71,
e: config.SeverityMedium,
},
"3": {
- k: "cpu",
+ k: config.CPU,
v: 75,
e: config.SeverityMedium,
},
"2": {
- k: "cpu",
+ k: config.CPU,
v: 80,
e: config.SeverityMedium,
},
"1": {
- k: "cpu",
+ k: config.CPU,
v: 100,
e: config.SeverityHigh,
},
"over": {
- k: "cpu",
+ k: config.CPU,
+ v: 150,
+ e: config.SeverityLow,
+ },
+ "over-mem": {
+ k: config.MEM,
v: 150,
e: config.SeverityLow,
},
diff --git a/internal/config/types.go b/internal/config/types.go
index f40f93c4..2d3e5c60 100644
--- a/internal/config/types.go
+++ b/internal/config/types.go
@@ -6,6 +6,12 @@ package config
const (
defaultRefreshRate = 2
defaultMaxConnRetry = 5
+
+ // CPU tracks cpu usage.
+ CPU = "cpu"
+
+ // MEM tracks memory usage.
+ MEM = "memory"
)
// UI tracks ui specific configs.
diff --git a/internal/config/views.go b/internal/config/views.go
index 5fdd6820..88433711 100644
--- a/internal/config/views.go
+++ b/internal/config/views.go
@@ -45,7 +45,7 @@ func (v *ViewSetting) IsBlank() bool {
return v == nil || (len(v.Columns) == 0 && v.SortColumn == "")
}
-func (v *ViewSetting) SortCol() (string, bool, error) {
+func (v *ViewSetting) SortCol() (name string, asc bool, err error) {
if v == nil || v.SortColumn == "" {
return "", false, fmt.Errorf("no sort column specified")
}
@@ -180,9 +180,7 @@ func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
}
k := gvr
kk := slices.Collect(maps.Keys(v.Views))
- slices.SortFunc(kk, func(s1, s2 string) int {
- return strings.Compare(s1, s2)
- })
+ slices.SortFunc(kk, strings.Compare)
slices.Reverse(kk)
for _, key := range kk {
if !strings.HasPrefix(key, gvr) && !strings.HasPrefix(gvr, key) {
@@ -219,7 +217,6 @@ func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
vs := v.Views[key]
return &vs
}
-
}
return nil
diff --git a/internal/config/views_int_test.go b/internal/config/views_int_test.go
index e9eb0e11..8ee7cca0 100644
--- a/internal/config/views_int_test.go
+++ b/internal/config/views_int_test.go
@@ -6,7 +6,9 @@ package config
import (
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestCustomView_getVS(t *testing.T) {
@@ -22,14 +24,14 @@ func TestCustomView_getVS(t *testing.T) {
},
"gvr": {
- gvr: "v1/pods",
+ gvr: client.PodGVR.String(),
e: &ViewSetting{
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
},
"gvr+ns": {
- gvr: "v1/pods",
+ gvr: client.PodGVR.String(),
ns: "default",
e: &ViewSetting{
Columns: []string{"NAME", "IP", "AGE"},
@@ -37,7 +39,7 @@ func TestCustomView_getVS(t *testing.T) {
},
"rx": {
- gvr: "v1/pods",
+ gvr: client.PodGVR.String(),
ns: "ns-fred",
e: &ViewSetting{
Columns: []string{"AGE", "NAME", "IP"},
@@ -52,7 +54,7 @@ func TestCustomView_getVS(t *testing.T) {
},
"toast-no-ns": {
- gvr: "v1/pods",
+ gvr: client.PodGVR.String(),
ns: "zorg",
e: &ViewSetting{
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
@@ -60,13 +62,13 @@ func TestCustomView_getVS(t *testing.T) {
},
"toast-no-res": {
- gvr: "v1/services",
+ gvr: client.SvcGVR.String(),
ns: "zorg",
},
}
v := NewCustomView()
- assert.NoError(t, v.Load("testdata/views/views.yaml"))
+ require.NoError(t, v.Load("testdata/views/views.yaml"))
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, v.getVS(u.gvr, u.ns))
diff --git a/internal/config/views_test.go b/internal/config/views_test.go
index b5e22cb0..01dd94bf 100644
--- a/internal/config/views_test.go
+++ b/internal/config/views_test.go
@@ -7,8 +7,10 @@ import (
"log/slog"
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func init() {
@@ -26,7 +28,7 @@ func TestCustomViewLoad(t *testing.T) {
"gvr": {
path: "testdata/views/views.yaml",
- key: "v1/pods",
+ key: client.PodGVR.String(),
e: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
@@ -41,7 +43,7 @@ func TestCustomViewLoad(t *testing.T) {
t.Run(k, func(t *testing.T) {
cfg := config.NewCustomView()
- assert.NoError(t, cfg.Load(u.path))
+ require.NoError(t, cfg.Load(u.path))
assert.Equal(t, u.e, cfg.Views[u.key].Columns)
})
}
diff --git a/internal/dao/accessor.go b/internal/dao/accessor.go
new file mode 100644
index 00000000..5069cdc0
--- /dev/null
+++ b/internal/dao/accessor.go
@@ -0,0 +1,56 @@
+package dao
+
+import (
+ "log/slog"
+
+ "github.com/derailed/k9s/internal/client"
+ "github.com/derailed/k9s/internal/slogs"
+)
+
+var accessors = Accessors{
+ *client.WkGVR: new(Workload),
+ *client.CtGVR: new(Context),
+ *client.CoGVR: new(Container),
+ *client.ScnGVR: new(ImageScan),
+ *client.SdGVR: new(ScreenDump),
+ *client.BeGVR: new(Benchmark),
+ *client.PfGVR: new(PortForward),
+ *client.DirGVR: new(Dir),
+
+ *client.SvcGVR: new(Service),
+ *client.PodGVR: new(Pod),
+ *client.NodeGVR: new(Node),
+ *client.NsGVR: new(Namespace),
+ *client.CmGVR: new(ConfigMap),
+ *client.SecGVR: new(Secret),
+
+ *client.DpGVR: new(Deployment),
+ *client.DsGVR: new(DaemonSet),
+ *client.StsGVR: new(StatefulSet),
+ *client.RsGVR: new(ReplicaSet),
+
+ *client.CjGVR: new(CronJob),
+ *client.JobGVR: new(Job),
+
+ *client.HmGVR: new(HelmChart),
+ *client.HmhGVR: new(HelmHistory),
+
+ *client.CrdGVR: new(CustomResourceDefinition),
+}
+
+// Accessors represents a collection of dao accessors.
+type Accessors map[client.GVR]Accessor
+
+// AccessorFor returns a client accessor for a resource if registered.
+// Otherwise it returns a generic accessor.
+// Customize here for non resource types or types with metrics or logs.
+func AccessorFor(f Factory, gvr *client.GVR) (Accessor, error) {
+ r, ok := accessors[*gvr]
+ if !ok {
+ r = new(Scaler)
+ slog.Debug("No DAO registry entry. Using generics!", slogs.GVR, gvr)
+ }
+ r.Init(f, gvr)
+
+ return r, nil
+}
diff --git a/internal/dao/alias.go b/internal/dao/alias.go
index a1295286..c6f48b20 100644
--- a/internal/dao/alias.go
+++ b/internal/dao/alias.go
@@ -14,8 +14,8 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render"
- "github.com/derailed/k9s/internal/view/cmd"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/sets"
)
var _ Accessor = (*Alias)(nil)
@@ -32,22 +32,18 @@ func NewAlias(f Factory) *Alias {
a := Alias{
Aliases: config.NewAliases(),
}
- a.Init(f, client.NewGVR("aliases"))
+ a.Init(f, client.AliGVR)
return &a
}
-func (a *Alias) AliasesFor(s string) []string {
- return a.Aliases.AliasesFor(s)
-}
-
-// Check verifies an alias is defined for this command.
-func (a *Alias) Check(cmd string) (string, bool) {
- return a.Aliases.Get(cmd)
+// AliasesFor returns a set of aliases for a given gvr.
+func (a *Alias) AliasesFor(gvr *client.GVR) sets.Set[string] {
+ return a.Aliases.AliasesFor(gvr)
}
// List returns a collection of aliases.
-func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
+func (*Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
aa, ok := ctx.Value(internal.KeyAliases).(*Alias)
if !ok {
return nil, fmt.Errorf("expecting *Alias but got %T", ctx.Value(internal.KeyAliases))
@@ -66,24 +62,19 @@ func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
}
// AsGVR returns a matching gvr if it exists.
-func (a *Alias) AsGVR(c string) (client.GVR, string, bool) {
- exp, ok := a.Aliases.Get(c)
- if !ok {
- return client.NoGVR, "", ok
- }
- p := cmd.NewInterpreter(exp)
- if strings.Contains(p.Cmd(), "/") {
- return client.NewGVR(p.Cmd()), "", true
- }
- if gvr, ok := a.Aliases.Get(p.Cmd()); ok {
- return client.NewGVR(gvr), exp, true
+func (a *Alias) AsGVR(alias string) (*client.GVR, string, bool) {
+ gvr, ok := a.Aliases.Get(alias)
+ if ok {
+ if pgvr := MetaAccess.Lookup(alias); pgvr != client.NoGVR {
+ return pgvr, "", ok
+ }
}
- return client.NoGVR, "", false
+ return gvr, "", ok
}
// Get fetch a resource.
-func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) {
+func (*Alias) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
@@ -109,25 +100,21 @@ func (a *Alias) load(path string) error {
if IsK9sMeta(meta) {
continue
}
-
- gvrStr := gvr.String()
if IsCRD(meta) {
crdGVRS = append(crdGVRS, gvr)
continue
}
-
- a.Define(gvrStr, gvr.AsResourceName())
+ a.Define(gvr, gvr.AsResourceName())
// Allow single shot commands for k8s resources only!
if isStandardGroup(gvr.GVSub()) {
- a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
- a.Define(gvrStr, meta.SingularName)
-
+ a.Define(gvr, meta.Name)
+ a.Define(gvr, meta.SingularName)
}
if len(meta.ShortNames) > 0 {
- a.Define(gvrStr, meta.ShortNames...)
+ a.Define(gvr, meta.ShortNames...)
}
- a.Define(gvrStr, gvrStr)
+ a.Define(gvr, gvr.String())
}
for _, gvr := range crdGVRS {
@@ -135,15 +122,14 @@ func (a *Alias) load(path string) error {
if err != nil {
return err
}
- gvrStr := gvr.String()
- a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
- a.Define(gvrStr, meta.SingularName)
+ a.Define(gvr, strings.ToLower(meta.Kind), meta.Name)
+ a.Define(gvr, meta.SingularName)
if len(meta.ShortNames) > 0 {
- a.Define(gvrStr, meta.ShortNames...)
+ a.Define(gvr, meta.ShortNames...)
}
- a.Define(gvrStr, gvrStr)
- a.Define(gvrStr, meta.Name+"."+meta.Group)
+ a.Define(gvr, gvr.String())
+ a.Define(gvr, meta.Name+"."+meta.Group)
}
return nil
diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go
index d8edd96e..0d59ee08 100644
--- a/internal/dao/alias_test.go
+++ b/internal/dao/alias_test.go
@@ -13,27 +13,28 @@ import (
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestAsGVR(t *testing.T) {
a := dao.NewAlias(makeFactory())
- a.Define("v1/pods", "po", "pod", "pods")
- a.Define("workloads", "workloads", "workload", "wkl")
+ a.Define(client.PodGVR, "po", "pod", "pods")
+ a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl")
uu := map[string]struct {
cmd string
ok bool
- gvr client.GVR
+ gvr *client.GVR
}{
"ok": {
cmd: "pods",
ok: true,
- gvr: client.NewGVR("v1/pods"),
+ gvr: client.PodGVR,
},
"ok-short": {
cmd: "po",
ok: true,
- gvr: client.NewGVR("v1/pods"),
+ gvr: client.PodGVR,
},
"missing": {
cmd: "zorg",
@@ -41,7 +42,7 @@ func TestAsGVR(t *testing.T) {
"alias": {
cmd: "wkl",
ok: true,
- gvr: client.NewGVR("workloads"),
+ gvr: client.WkGVR,
},
}
@@ -59,27 +60,30 @@ func TestAsGVR(t *testing.T) {
func TestAliasList(t *testing.T) {
a := dao.Alias{}
- a.Init(makeFactory(), client.NewGVR("aliases"))
+ a.Init(makeFactory(), client.AliGVR)
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
oo, err := a.List(ctx, "-")
- assert.Nil(t, err)
- assert.Equal(t, 2, len(oo))
- assert.Equal(t, 2, len(oo[0].(render.AliasRes).Aliases))
+ require.NoError(t, err)
+ assert.Len(t, oo, 2)
+ assert.Len(t, oo[0].(render.AliasRes).Aliases, 2)
}
// ----------------------------------------------------------------------------
// Helpers...
func makeAliases() *dao.Alias {
+ gvr1 := client.NewGVR("v1/fred")
+ gvr2 := client.NewGVR("v1/blee")
+
return &dao.Alias{
Aliases: &config.Aliases{
Alias: config.Alias{
- "fred": "v1/fred",
- "f": "v1/fred",
- "blee": "v1/blee",
- "b": "v1/blee",
+ "fred": gvr1,
+ "f": gvr1,
+ "blee": gvr2,
+ "b": gvr2,
},
},
}
diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go
index 683cb2a4..e6ab04c8 100644
--- a/internal/dao/benchmark.go
+++ b/internal/dao/benchmark.go
@@ -30,17 +30,17 @@ type Benchmark struct {
}
// Delete nukes a resource.
-func (b *Benchmark) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
+func (*Benchmark) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
return os.Remove(path)
}
// Get returns a resource.
-func (b *Benchmark) Get(context.Context, string) (runtime.Object, error) {
+func (*Benchmark) Get(context.Context, string) (runtime.Object, error) {
panic("NYI")
}
// List returns a collection of resources.
-func (b *Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error) {
+func (*Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyDir).(string)
if !ok {
return nil, errors.New("no benchmark dir found in context")
diff --git a/internal/dao/benchmark_test.go b/internal/dao/benchmark_test.go
index 12fed066..e5497779 100644
--- a/internal/dao/benchmark_test.go
+++ b/internal/dao/benchmark_test.go
@@ -12,17 +12,18 @@ import (
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestBenchmarkList(t *testing.T) {
a := dao.Benchmark{}
- a.Init(makeFactory(), client.NewGVR("benchmarks"))
+ a.Init(makeFactory(), client.BeGVR)
ctx := context.WithValue(context.Background(), internal.KeyDir, "testdata/bench")
ctx = context.WithValue(ctx, internal.KeyPath, "")
oo, err := a.List(ctx, "-")
- assert.Nil(t, err)
- assert.Equal(t, 1, len(oo))
+ require.NoError(t, err)
+ assert.Len(t, oo, 1)
assert.Equal(t, "testdata/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
}
diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go
index 8b0b1b7c..363641b2 100644
--- a/internal/dao/cluster.go
+++ b/internal/dao/cluster.go
@@ -9,7 +9,6 @@ import (
"fmt"
"log/slog"
"sync"
- "time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
@@ -19,9 +18,12 @@ import (
// RefScanner represents a resource reference scanner.
type RefScanner interface {
// Init initializes the scanner
- Init(Factory, client.GVR)
+ Init(Factory, *client.GVR)
+
// Scan scan the resource for references.
- Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error)
+ Scan(ctx context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error)
+
+ // ScanSA scan the resource for serviceaccount references.
ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error)
}
@@ -40,27 +42,21 @@ var (
_ RefScanner = (*DaemonSet)(nil)
_ RefScanner = (*Job)(nil)
_ RefScanner = (*CronJob)(nil)
- // _ RefScanner = (*Pod)(nil)
)
-func scanners() map[string]RefScanner {
- return map[string]RefScanner{
- "apps/v1/deployments": &Deployment{},
- "apps/v1/statefulsets": &StatefulSet{},
- "apps/v1/daemonsets": &DaemonSet{},
- "batch/v1/jobs": &Job{},
- "batch/v1/cronjobs": &CronJob{},
- // "v1/pods": &Pod{},
+func scanners() map[*client.GVR]RefScanner {
+ return map[*client.GVR]RefScanner{
+ client.DpGVR: new(Deployment),
+ client.DsGVR: new(DaemonSet),
+ client.StsGVR: new(StatefulSet),
+ client.CjGVR: new(CronJob),
+ client.JobGVR: new(Job),
}
}
// ScanForRefs scans cluster resources for resource references.
func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
- defer func(t time.Time) {
- slog.Debug("Cluster Scan", slogs.Elapsed, time.Since(t))
- }(time.Now())
-
- gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
+ rgvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, errors.New("expecting context GVR")
}
@@ -73,15 +69,14 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
slog.Warn("Expecting context Wait key. Using default")
}
- ss := scanners()
var wg sync.WaitGroup
- wg.Add(len(ss))
out := make(chan Refs)
- for k, s := range ss {
- go func(ctx context.Context, kind string, s RefScanner, out chan Refs, wait bool) {
+ for gvr, scanner := range scanners() {
+ wg.Add(1)
+ go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
defer wg.Done()
- s.Init(f, client.NewGVR(kind))
- refs, err := s.Scan(ctx, gvr, fqn, wait)
+ s.Init(f, gvr)
+ refs, err := s.Scan(ctx, rgvr, fqn, wait)
if err != nil {
slog.Error("Reference scan failed for",
slogs.RefType, fmt.Sprintf("%T", s),
@@ -94,7 +89,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
case <-ctx.Done():
return
}
- }(ctx, k, s, out, wait)
+ }(ctx, gvr, scanner, out, wait)
}
go func() {
@@ -112,10 +107,6 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
// ScanForSARefs scans cluster resources for serviceaccount refs.
func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
- defer func(t time.Time) {
- slog.Debug("Time to scan Cluster SA", slogs.Elapsed, time.Since(t))
- }(time.Now())
-
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("expecting context Path")
@@ -125,14 +116,13 @@ func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
return nil, errors.New("expecting context Wait")
}
- ss := scanners()
var wg sync.WaitGroup
- wg.Add(len(ss))
out := make(chan Refs)
- for k, s := range ss {
- go func(ctx context.Context, kind string, s RefScanner, out chan Refs, wait bool) {
+ for gvr, scanner := range scanners() {
+ wg.Add(1)
+ go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
defer wg.Done()
- s.Init(f, client.NewGVR(kind))
+ s.Init(f, gvr)
refs, err := s.ScanSA(ctx, fqn, wait)
if err != nil {
slog.Error("ServiceAccount scan failed",
@@ -146,7 +136,7 @@ func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
case <-ctx.Done():
return
}
- }(ctx, k, s, out, wait)
+ }(ctx, gvr, scanner, out, wait)
}
go func() {
diff --git a/internal/dao/container.go b/internal/dao/container.go
index 14b20344..7ef72b0d 100644
--- a/internal/dao/container.go
+++ b/internal/dao/container.go
@@ -54,14 +54,33 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
return nil, err
}
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)+len(po.Spec.EphemeralContainers))
- for i, co := range po.Spec.InitContainers {
- res = append(res, makeContainerRes(initIDX, i, co, po, cmx[co.Name]))
+ for i := range po.Spec.InitContainers {
+ res = append(res, makeContainerRes(
+ initIDX,
+ i,
+ &(po.Spec.InitContainers[i]),
+ po,
+ cmx[po.Spec.InitContainers[i].Name]),
+ )
}
- for i, co := range po.Spec.Containers {
- res = append(res, makeContainerRes(mainIDX, i, co, po, cmx[co.Name]))
+ for i := range po.Spec.Containers {
+ res = append(res, makeContainerRes(
+ mainIDX,
+ i,
+ &(po.Spec.Containers[i]),
+ po,
+ cmx[po.Spec.Containers[i].Name]),
+ )
}
- for i, co := range po.Spec.EphemeralContainers {
- res = append(res, makeContainerRes(ephIDX, i, v1.Container(co.EphemeralContainerCommon), po, cmx[co.Name]))
+ for i := range po.Spec.EphemeralContainers {
+ co := v1.Container(po.Spec.EphemeralContainers[i].EphemeralContainerCommon)
+ res = append(res, makeContainerRes(
+ ephIDX,
+ i,
+ &co,
+ po,
+ cmx[co.Name]),
+ )
}
return res, nil
@@ -70,7 +89,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
// TailLogs tails a given container logs.
func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
po := Pod{}
- po.Init(c.Factory, client.NewGVR("v1/pods"))
+ po.Init(c.Factory, client.PodGVR)
return po.TailLogs(ctx, opts)
}
@@ -78,34 +97,34 @@ func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan,
// ----------------------------------------------------------------------------
// Helpers...
-func makeContainerRes(kind string, idx int, co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics) render.ContainerRes {
+func makeContainerRes(kind string, idx int, co *v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics) render.ContainerRes {
return render.ContainerRes{
Idx: kind + strconv.Itoa(idx+1),
- Container: &co,
- Status: getContainerStatus(kind, co.Name, po.Status),
+ Container: co,
+ Status: getContainerStatus(kind, co.Name, &po.Status),
MX: cmx,
Age: po.GetCreationTimestamp(),
}
}
-func getContainerStatus(kind string, name string, status v1.PodStatus) *v1.ContainerStatus {
+func getContainerStatus(kind, name string, status *v1.PodStatus) *v1.ContainerStatus {
switch kind {
case mainIDX:
- for _, s := range status.ContainerStatuses {
- if s.Name == name {
- return &s
+ for i := range status.ContainerStatuses {
+ if status.ContainerStatuses[i].Name == name {
+ return &status.ContainerStatuses[i]
}
}
case initIDX:
- for _, s := range status.InitContainerStatuses {
- if s.Name == name {
- return &s
+ for i := range status.InitContainerStatuses {
+ if status.InitContainerStatuses[i].Name == name {
+ return &status.InitContainerStatuses[i]
}
}
case ephIDX:
- for _, s := range status.EphemeralContainerStatuses {
- if s.Name == name {
- return &s
+ for i := range status.EphemeralContainerStatuses {
+ if status.EphemeralContainerStatuses[i].Name == name {
+ return &status.EphemeralContainerStatuses[i]
}
}
}
@@ -114,7 +133,7 @@ func getContainerStatus(kind string, name string, status v1.PodStatus) *v1.Conta
}
func (c *Container) fetchPod(fqn string) (*v1.Pod, error) {
- o, err := c.getFactory().Get("v1/pods", fqn, true, labels.Everything())
+ o, err := c.getFactory().Get(client.PodGVR, fqn, true, labels.Everything())
if err != nil {
return nil, fmt.Errorf("failed to locate pod %q: %w", fqn, err)
}
diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go
index f53c8295..4b916ba3 100644
--- a/internal/dao/container_test.go
+++ b/internal/dao/container_test.go
@@ -12,6 +12,7 @@ import (
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/watch"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
@@ -28,12 +29,12 @@ import (
func TestContainerList(t *testing.T) {
c := dao.Container{}
- c.Init(makePodFactory(), client.NewGVR("containers"))
+ c.Init(makePodFactory(), client.CoGVR)
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
oo, err := c.List(ctx, "")
- assert.Nil(t, err)
- assert.Equal(t, 1, len(oo))
+ require.NoError(t, err)
+ assert.Len(t, oo, 1)
}
// ----------------------------------------------------------------------------
@@ -45,58 +46,58 @@ func makeConn() *conn {
return &conn{}
}
-func (c *conn) Config() *client.Config { return nil }
-func (c *conn) Dial() (kubernetes.Interface, error) { return nil, nil }
-func (c *conn) DialLogs() (kubernetes.Interface, error) { return nil, nil }
-func (c *conn) ConnectionOK() bool { return true }
-func (c *conn) SwitchContext(ctx string) error { return nil }
-func (c *conn) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, nil }
-func (c *conn) RestConfig() (*restclient.Config, error) { return nil, nil }
-func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
-func (c *conn) DynDial() (dynamic.Interface, error) { return nil, nil }
-func (c *conn) HasMetrics() bool { return false }
-func (c *conn) CheckConnectivity() bool { return false }
-func (c *conn) IsNamespaced(n string) bool { return false }
-func (c *conn) SupportsResource(group string) bool { return false }
-func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }
-func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error) {
- return "", false, nil
-}
-func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil }
-func (c *conn) CurrentNamespaceName() (string, error) { return "", nil }
-func (c *conn) CanI(ns, gvr, n string, verbs []string) (bool, error) { return true, nil }
-func (c *conn) ActiveContext() string { return "" }
-func (c *conn) ActiveNamespace() string { return "" }
-func (c *conn) IsValidNamespace(string) bool { return true }
-func (c *conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil }
-func (c *conn) IsActiveNamespace(string) bool { return false }
+func (*conn) Config() *client.Config { return nil }
+func (*conn) Dial() (kubernetes.Interface, error) { return nil, nil }
+func (*conn) DialLogs() (kubernetes.Interface, error) { return nil, nil }
+func (*conn) ConnectionOK() bool { return true }
+func (*conn) SwitchContext(string) error { return nil }
+func (*conn) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, nil }
+func (*conn) RestConfig() (*restclient.Config, error) { return nil, nil }
+func (*conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
+func (*conn) DynDial() (dynamic.Interface, error) { return nil, nil }
+func (*conn) HasMetrics() bool { return false }
+func (*conn) CheckConnectivity() bool { return false }
+func (*conn) IsNamespaced(string) bool { return false }
+func (*conn) SupportsResource(string) bool { return false }
+func (*conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }
+func (*conn) SupportsRes(string, []string) (a string, b bool, e error) { return "", false, nil }
+func (*conn) ServerVersion() (*version.Info, error) { return nil, nil }
+func (*conn) CurrentNamespaceName() (string, error) { return "", nil }
+func (*conn) CanI(string, *client.GVR, string, []string) (bool, error) { return true, nil }
+func (*conn) ActiveContext() string { return "" }
+func (*conn) ActiveNamespace() string { return "" }
+func (*conn) IsValidNamespace(string) bool { return true }
+func (*conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil }
+func (*conn) IsActiveNamespace(string) bool { return false }
type podFactory struct{}
var _ dao.Factory = &testFactory{}
-func (f podFactory) Client() client.Connection {
+func (podFactory) Client() client.Connection {
return makeConn()
}
-func (f podFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
- var m map[string]interface{}
+func (podFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
+ var m map[string]any
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: m}, nil
}
-func (f podFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (podFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
-func (f podFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) { return nil, nil }
-func (f podFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (podFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-func (f podFactory) WaitForCacheSync() {}
-func (f podFactory) Forwarders() watch.Forwarders { return nil }
-func (f podFactory) DeleteForwarder(string) {}
+func (podFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
+ return nil, nil
+}
+func (podFactory) WaitForCacheSync() {}
+func (podFactory) Forwarders() watch.Forwarders { return nil }
+func (podFactory) DeleteForwarder(string) {}
func makePodFactory() dao.Factory {
return podFactory{}
diff --git a/internal/dao/context.go b/internal/dao/context.go
index de655be2..2800505f 100644
--- a/internal/dao/context.go
+++ b/internal/dao/context.go
@@ -28,7 +28,7 @@ func (c *Context) config() *client.Config {
}
// Get a Context.
-func (c *Context) Get(ctx context.Context, path string) (runtime.Object, error) {
+func (c *Context) Get(_ context.Context, path string) (runtime.Object, error) {
co, err := c.config().GetContext(path)
if err != nil {
return nil, err
@@ -37,7 +37,7 @@ func (c *Context) Get(ctx context.Context, path string) (runtime.Object, error)
}
// List all Contexts on the current cluster.
-func (c *Context) List(_ context.Context, _ string) ([]runtime.Object, error) {
+func (c *Context) List(context.Context, string) ([]runtime.Object, error) {
ctxs, err := c.config().Contexts()
if err != nil {
return nil, err
diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go
index ba5514c3..98497e93 100644
--- a/internal/dao/cronjob.go
+++ b/internal/dao/cronjob.go
@@ -20,10 +20,7 @@ import (
"k8s.io/apimachinery/pkg/util/rand"
)
-const (
- maxJobNameSize = 42
- jobGVR = "batch/v1/jobs"
-)
+const maxJobNameSize = 42
var (
_ Accessor = (*CronJob)(nil)
@@ -37,7 +34,7 @@ type CronJob struct {
}
// ListImages lists container images.
-func (c *CronJob) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (c *CronJob) ListImages(_ context.Context, fqn string) ([]string, error) {
cj, err := c.GetInstance(fqn)
if err != nil {
return nil, err
@@ -49,7 +46,7 @@ func (c *CronJob) ListImages(ctx context.Context, fqn string) ([]string, error)
// Run a CronJob.
func (c *CronJob) Run(path string) error {
ns, n := client.Namespaced(path)
- auth, err := c.Client().CanI(ns, jobGVR, n, []string{client.GetVerb, client.CreateVerb})
+ auth, err := c.Client().CanI(ns, c.gvr, n, []string{client.GetVerb, client.CreateVerb})
if err != nil {
return err
}
@@ -57,7 +54,7 @@ func (c *CronJob) Run(path string) error {
return fmt.Errorf("user is not authorized to run jobs")
}
- o, err := c.getFactory().Get(c.GVR(), path, true, labels.Everything())
+ o, err := c.getFactory().Get(c.gvr, path, true, labels.Everything())
if err != nil {
return err
}
@@ -70,7 +67,7 @@ func (c *CronJob) Run(path string) error {
if len(cj.Name) >= maxJobNameSize {
jobName = cj.Name[0:maxJobNameSize]
}
- true := true
+ trueVal := true
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName + "-manual-" + rand.String(3),
@@ -81,8 +78,8 @@ func (c *CronJob) Run(path string) error {
{
APIVersion: c.gvr.GV().String(),
Kind: "CronJob",
- BlockOwnerDeletion: &true,
- Controller: &true,
+ BlockOwnerDeletion: &trueVal,
+ Controller: &trueVal,
Name: cj.Name,
UID: cj.UID,
},
@@ -102,9 +99,9 @@ func (c *CronJob) Run(path string) error {
}
// ScanSA scans for serviceaccount refs.
-func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (c *CronJob) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := c.getFactory().List(c.GVR(), ns, wait, labels.Everything())
+ oo, err := c.getFactory().List(c.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -129,7 +126,7 @@ func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, erro
// GetInstance fetch a matching cronjob.
func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
- o, err := c.getFactory().Get(c.GVR(), fqn, true, labels.Everything())
+ o, err := c.getFactory().Get(c.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -146,7 +143,7 @@ func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
// ToggleSuspend toggles suspend/resume on a CronJob.
func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
ns, n := client.Namespaced(path)
- auth, err := c.Client().CanI(ns, c.GVR(), n, []string{client.GetVerb, client.UpdateVerb})
+ auth, err := c.Client().CanI(ns, c.gvr, n, []string{client.GetVerb, client.UpdateVerb})
if err != nil {
return err
}
@@ -166,8 +163,8 @@ func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
current := !*cj.Spec.Suspend
cj.Spec.Suspend = ¤t
} else {
- true := true
- cj.Spec.Suspend = &true
+ trueVal := true
+ cj.Spec.Suspend = &trueVal
}
_, err = dial.BatchV1().CronJobs(ns).Update(ctx, cj, metav1.UpdateOptions{})
@@ -175,9 +172,9 @@ func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
}
// Scan scans for cluster resource refs.
-func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (c *CronJob) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := c.getFactory().List(c.GVR(), ns, wait, labels.Everything())
+ oo, err := c.getFactory().List(c.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -190,7 +187,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait boo
return nil, errors.New("expecting CronJob resource")
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
continue
}
@@ -198,7 +195,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait boo
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait)
if err != nil {
slog.Warn("Failed to locate secret",
@@ -214,7 +211,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait boo
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
continue
}
diff --git a/internal/dao/cruiser.go b/internal/dao/cruiser.go
index 25a194ee..f9221ba1 100644
--- a/internal/dao/cruiser.go
+++ b/internal/dao/cruiser.go
@@ -10,12 +10,12 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
-func mustMap(o runtime.Object, field string) map[string]interface{} {
+func mustMap(o runtime.Object, field string) map[string]any {
u, ok := o.(*unstructured.Unstructured)
if !ok {
panic("no unstructured")
}
- m, ok := u.Object[field].(map[string]interface{})
+ m, ok := u.Object[field].(map[string]any)
if !ok {
panic(fmt.Sprintf("map extract failed for %q", field))
}
@@ -23,20 +23,20 @@ func mustMap(o runtime.Object, field string) map[string]interface{} {
return m
}
-func mustSlice(o runtime.Object, field string) []interface{} {
+func mustSlice(o runtime.Object, field string) []any {
u, ok := o.(*unstructured.Unstructured)
if !ok {
- return []interface{}{}
+ return nil
}
- s, ok := u.Object[field].([]interface{})
+ s, ok := u.Object[field].([]any)
if !ok {
- return []interface{}{}
+ return nil
}
return s
}
-func mustField(o map[string]interface{}, field string) interface{} {
+func mustField(o map[string]any, field string) any {
f, ok := o[field]
if !ok {
panic(fmt.Sprintf("no field for %q", field))
diff --git a/internal/dao/cruiser_test.go b/internal/dao/cruiser_test.go
index 6e88cb86..05d2021d 100644
--- a/internal/dao/cruiser_test.go
+++ b/internal/dao/cruiser_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -24,20 +25,20 @@ func TestCruiserSlice(t *testing.T) {
o := loadJSON(t, "crb")
s := mustSlice(o, "subjects")
- assert.Equal(t, 1, len(s))
- assert.Equal(t, "fernand", mustField(s[0].(map[string]interface{}), "name"))
- assert.Equal(t, "User", mustField(s[0].(map[string]interface{}), "kind"))
+ assert.Len(t, s, 1)
+ assert.Equal(t, "fernand", mustField(s[0].(map[string]any), "name"))
+ assert.Equal(t, "User", mustField(s[0].(map[string]any), "kind"))
}
// Helpers...
-func loadJSON(t assert.TestingT, n string) *unstructured.Unstructured {
+func loadJSON(t require.TestingT, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
+ require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
+ require.NoError(t, err)
return &o
}
diff --git a/internal/dao/describe.go b/internal/dao/describe.go
index 541f453c..6e07f3b2 100644
--- a/internal/dao/describe.go
+++ b/internal/dao/describe.go
@@ -12,7 +12,7 @@ import (
)
// Describe describes a resource.
-func Describe(c client.Connection, gvr client.GVR, path string) (string, error) {
+func Describe(c client.Connection, gvr *client.GVR, path string) (string, error) {
mapper := RestMapper{Connection: c}
m, err := mapper.ToRESTMapper()
if err != nil {
diff --git a/internal/dao/dir.go b/internal/dao/dir.go
index ef9c67ba..bff00c2d 100644
--- a/internal/dao/dir.go
+++ b/internal/dao/dir.go
@@ -27,14 +27,14 @@ type Dir struct {
// NewDir returns a new set of aliases.
func NewDir(f Factory) *Dir {
var a Dir
- a.Init(f, client.NewGVR("dir"))
+ a.Init(f, client.DirGVR)
return &a
}
var yamlRX = regexp.MustCompile(`.*\.(yml|yaml|json)`)
// List returns a collection of aliases.
-func (a *Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) {
+func (*Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("no dir in context")
@@ -60,6 +60,6 @@ func (a *Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) {
}
// Get fetch a resource.
-func (a *Dir) Get(_ context.Context, _ string) (runtime.Object, error) {
+func (*Dir) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
diff --git a/internal/dao/dir_test.go b/internal/dao/dir_test.go
index c33e5d78..a0ba3a96 100644
--- a/internal/dao/dir_test.go
+++ b/internal/dao/dir_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNewDir(t *testing.T) {
@@ -17,6 +18,6 @@ func TestNewDir(t *testing.T) {
ctx := context.WithValue(context.Background(), internal.KeyPath, "testdata/dir")
oo, err := d.List(ctx, "")
- assert.Nil(t, err)
- assert.Equal(t, 2, len(oo))
+ require.NoError(t, err)
+ assert.Len(t, oo, 2)
}
diff --git a/internal/dao/dp.go b/internal/dao/dp.go
index 61d2695f..57b4aa52 100644
--- a/internal/dao/dp.go
+++ b/internal/dao/dp.go
@@ -41,7 +41,7 @@ type Deployment struct {
}
// ListImages lists container images.
-func (d *Deployment) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (d *Deployment) ListImages(_ context.Context, fqn string) ([]string, error) {
dp, err := d.GetInstance(fqn)
if err != nil {
return nil, err
@@ -52,76 +52,12 @@ func (d *Deployment) ListImages(ctx context.Context, fqn string) ([]string, erro
// Scale a Deployment.
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
- ns, n := client.Namespaced(path)
- auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", n, []string{client.GetVerb, client.UpdateVerb})
- if err != nil {
- return err
- }
- if !auth {
- return fmt.Errorf("user is not authorized to scale a deployment")
- }
-
- dial, err := d.Client().Dial()
- if err != nil {
- return err
- }
- scale, err := dial.AppsV1().Deployments(ns).GetScale(ctx, n, metav1.GetOptions{})
- if err != nil {
- return err
- }
- scale.Spec.Replicas = replicas
- _, err = dial.AppsV1().Deployments(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
-
- return err
+ return scaleRes(ctx, d.getFactory(), client.DpGVR, path, replicas)
}
// Restart a Deployment rollout.
func (d *Deployment) Restart(ctx context.Context, path string) error {
- o, err := d.getFactory().Get("apps/v1/deployments", path, true, labels.Everything())
- if err != nil {
- return err
- }
- var dp appsv1.Deployment
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
- if err != nil {
- return err
- }
-
- auth, err := d.Client().CanI(dp.Namespace, "apps/v1/deployments", dp.Name, client.PatchAccess)
- if err != nil {
- return err
- }
- if !auth {
- return fmt.Errorf("user is not authorized to restart a deployment")
- }
-
- dial, err := d.Client().Dial()
- if err != nil {
- return err
- }
-
- before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), &dp)
- if err != nil {
- return err
- }
-
- after, err := polymorphichelpers.ObjectRestarterFn(&dp)
- if err != nil {
- return err
- }
- diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, dp)
- if err != nil {
- return err
- }
- _, err = dial.AppsV1().Deployments(dp.Namespace).Patch(
- ctx,
- dp.Name,
- types.StrategicMergePatchType,
- diff,
- metav1.PatchOptions{},
- )
-
- return err
+ return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path)
}
// TailLogs tail logs for all pods represented by this Deployment.
@@ -149,7 +85,7 @@ func (d *Deployment) Pod(fqn string) (string, error) {
// GetInstance fetch a matching deployment.
func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
- o, err := d.Factory.Get(d.GVR(), fqn, true, labels.Everything())
+ o, err := d.Factory.Get(d.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -164,9 +100,9 @@ func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
}
// ScanSA scans for serviceaccount refs.
-func (d *Deployment) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (d *Deployment) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything())
+ oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -190,9 +126,9 @@ func (d *Deployment) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, e
}
// Scan scans for resource references.
-func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (d *Deployment) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything())
+ oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -205,7 +141,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
return nil, errors.New("expecting Deployment resource")
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&dp.Spec.Template.Spec, n) {
continue
}
@@ -213,7 +149,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait)
if err != nil {
slog.Warn("Fail to locate secret",
@@ -229,7 +165,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
- case PvcGVR:
+ case client.PvcGVR:
if !hasPVC(&dp.Spec.Template.Spec, n) {
continue
}
@@ -237,7 +173,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&dp.Spec.Template.Spec, n) {
continue
}
@@ -246,7 +182,6 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
FQN: client.FQN(dp.Namespace, dp.Name),
})
}
-
}
return refs, nil
@@ -265,7 +200,7 @@ func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) {
// SetImages sets container images.
func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
- auth, err := d.Client().CanI(ns, "apps/v1/deployments", n, client.PatchAccess)
+ auth, err := d.Client().CanI(ns, d.gvr, n, client.PatchAccess)
if err != nil {
return err
}
@@ -290,9 +225,11 @@ func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs Imag
return err
}
+// Helpers...
+
func hasPVC(spec *v1.PodSpec, name string) bool {
- for _, v := range spec.Volumes {
- if v.PersistentVolumeClaim != nil && v.PersistentVolumeClaim.ClaimName == name {
+ for i := range spec.Volumes {
+ if spec.Volumes[i].PersistentVolumeClaim != nil && spec.Volumes[i].PersistentVolumeClaim.ClaimName == name {
return true
}
}
@@ -304,24 +241,24 @@ func hasPC(spec *v1.PodSpec, name string) bool {
}
func hasConfigMap(spec *v1.PodSpec, name string) bool {
- for _, c := range spec.InitContainers {
- if containerHasConfigMap(c.EnvFrom, c.Env, name) {
+ for i := range spec.InitContainers {
+ if containerHasConfigMap(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
return true
}
}
- for _, c := range spec.Containers {
- if containerHasConfigMap(c.EnvFrom, c.Env, name) {
+ for i := range spec.Containers {
+ if containerHasConfigMap(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
return true
}
}
- for _, c := range spec.EphemeralContainers {
- if containerHasConfigMap(c.EnvFrom, c.Env, name) {
+ for i := range spec.EphemeralContainers {
+ if containerHasConfigMap(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
return true
}
}
- for _, v := range spec.Volumes {
- if cm := v.ConfigMap; cm != nil {
+ for i := range spec.Volumes {
+ if cm := spec.Volumes[i].ConfigMap; cm != nil {
if cm.Name == name {
return true
}
@@ -331,20 +268,20 @@ func hasConfigMap(spec *v1.PodSpec, name string) bool {
}
func hasSecret(f Factory, spec *v1.PodSpec, ns, name string, wait bool) (bool, error) {
- for _, c := range spec.InitContainers {
- if containerHasSecret(c.EnvFrom, c.Env, name) {
+ for i := range spec.InitContainers {
+ if containerHasSecret(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
return true, nil
}
}
- for _, c := range spec.Containers {
- if containerHasSecret(c.EnvFrom, c.Env, name) {
+ for i := range spec.Containers {
+ if containerHasSecret(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
return true, nil
}
}
- for _, c := range spec.EphemeralContainers {
- if containerHasSecret(c.EnvFrom, c.Env, name) {
+ for i := range spec.EphemeralContainers {
+ if containerHasSecret(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
return true, nil
}
}
@@ -356,7 +293,7 @@ func hasSecret(f Factory, spec *v1.PodSpec, ns, name string, wait bool) (bool, e
}
if saName := spec.ServiceAccountName; saName != "" {
- o, err := f.Get("v1/serviceaccounts", client.FQN(ns, saName), wait, labels.Everything())
+ o, err := f.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())
if err != nil {
return false, err
}
@@ -374,13 +311,14 @@ func hasSecret(f Factory, spec *v1.PodSpec, ns, name string, wait bool) (bool, e
}
}
- for _, v := range spec.Volumes {
- if sec := v.Secret; sec != nil {
+ for i := range spec.Volumes {
+ if sec := spec.Volumes[i].Secret; sec != nil {
if sec.SecretName == name {
return true, nil
}
}
}
+
return false, nil
}
@@ -419,3 +357,110 @@ func containerHasConfigMap(envFrom []v1.EnvFromSource, env []v1.EnvVar, name str
return false
}
+
+func scaleRes(ctx context.Context, f Factory, gvr *client.GVR, path string, replicas int32) error {
+ ns, n := client.Namespaced(path)
+ auth, err := f.Client().CanI(ns, client.NewGVR(gvr.String()+":scale"), n, []string{client.GetVerb, client.UpdateVerb})
+ if err != nil {
+ return err
+ }
+ if !auth {
+ return fmt.Errorf("user is not authorized to scale: %s", gvr)
+ }
+
+ dial, err := f.Client().Dial()
+ if err != nil {
+ return err
+ }
+
+ switch gvr {
+ case client.DpGVR:
+ scale, e := dial.AppsV1().Deployments(ns).GetScale(ctx, n, metav1.GetOptions{})
+ if e != nil {
+ return e
+ }
+ scale.Spec.Replicas = replicas
+ _, e = dial.AppsV1().Deployments(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
+ return e
+ case client.StsGVR:
+ scale, e := dial.AppsV1().StatefulSets(ns).GetScale(ctx, n, metav1.GetOptions{})
+ if e != nil {
+ return e
+ }
+ scale.Spec.Replicas = replicas
+ _, e = dial.AppsV1().StatefulSets(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
+ return e
+ default:
+ return fmt.Errorf("unsupported resource for scaling: %s", gvr)
+ }
+}
+
+func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GVR, path string) error {
+ o, err := f.Get(gvr, path, true, labels.Everything())
+ if err != nil {
+ return err
+ }
+ var r = new(T)
+ err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, r)
+ if err != nil {
+ return err
+ }
+
+ ns, n := client.Namespaced(path)
+ auth, err := f.Client().CanI(ns, gvr, n, client.PatchAccess)
+ if err != nil {
+ return err
+ }
+ if !auth {
+ return fmt.Errorf("user is not authorized to restart %q", gvr)
+ }
+
+ dial, err := f.Client().Dial()
+ if err != nil {
+ return err
+ }
+
+ before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), *r)
+ if err != nil {
+ return err
+ }
+ after, err := polymorphichelpers.ObjectRestarterFn(*r)
+ if err != nil {
+ return err
+ }
+ diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, *r)
+ if err != nil {
+ return err
+ }
+
+ switch gvr {
+ case client.DpGVR:
+ _, err = dial.AppsV1().Deployments(ns).Patch(
+ ctx,
+ n,
+ types.StrategicMergePatchType,
+ diff,
+ metav1.PatchOptions{},
+ )
+
+ case client.DsGVR:
+ _, err = dial.AppsV1().DaemonSets(ns).Patch(
+ ctx,
+ n,
+ types.StrategicMergePatchType,
+ diff,
+ metav1.PatchOptions{},
+ )
+
+ case client.StsGVR:
+ _, err = dial.AppsV1().StatefulSets(ns).Patch(
+ ctx,
+ n,
+ types.StrategicMergePatchType,
+ diff,
+ metav1.PatchOptions{},
+ )
+ }
+
+ return err
+}
diff --git a/internal/dao/ds.go b/internal/dao/ds.go
index dcbadaed..1e3a7c25 100644
--- a/internal/dao/ds.go
+++ b/internal/dao/ds.go
@@ -22,9 +22,6 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/strategicpatch"
- "k8s.io/kubectl/pkg/polymorphichelpers"
- "k8s.io/kubectl/pkg/scheme"
)
var (
@@ -43,7 +40,7 @@ type DaemonSet struct {
}
// ListImages lists container images.
-func (d *DaemonSet) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (d *DaemonSet) ListImages(_ context.Context, fqn string) ([]string, error) {
ds, err := d.GetInstance(fqn)
if err != nil {
return nil, err
@@ -54,51 +51,7 @@ func (d *DaemonSet) ListImages(ctx context.Context, fqn string) ([]string, error
// Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
- o, err := d.getFactory().Get("apps/v1/daemonsets", path, true, labels.Everything())
- if err != nil {
- return err
- }
- var ds appsv1.DaemonSet
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
- if err != nil {
- return err
- }
-
- auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", ds.Name, client.PatchAccess)
- if err != nil {
- return err
- }
- if !auth {
- return fmt.Errorf("user is not authorized to restart a daemonset")
- }
-
- dial, err := d.Client().Dial()
- if err != nil {
- return err
- }
-
- before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), &ds)
- if err != nil {
- return err
- }
-
- after, err := polymorphichelpers.ObjectRestarterFn(&ds)
- if err != nil {
- return err
- }
- diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, ds)
- if err != nil {
- return err
- }
- _, err = dial.AppsV1().DaemonSets(ds.Namespace).Patch(
- ctx,
- ds.Name,
- types.StrategicMergePatchType,
- diff,
- metav1.PatchOptions{},
- )
-
- return err
+ return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path)
}
// TailLogs tail logs for all pods represented by this DaemonSet.
@@ -130,14 +83,14 @@ func podLogs(ctx context.Context, sel map[string]string, opts *LogOptions) ([]Lo
}
ns, _ := client.Namespaced(opts.Path)
- oo, err := f.List("v1/pods", ns, true, lsel)
+ oo, err := f.List(client.PodGVR, ns, true, lsel)
if err != nil {
return nil, err
}
opts.MultiPods = true
var po Pod
- po.Init(f, client.NewGVR("v1/pods"))
+ po.Init(f, client.PodGVR)
outs := make([]LogChan, 0, len(oo))
for _, o := range oo {
@@ -169,7 +122,7 @@ func (d *DaemonSet) Pod(fqn string) (string, error) {
// GetInstance returns a daemonset instance.
func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
- o, err := d.getFactory().Get(d.gvrStr(), fqn, true, labels.Everything())
+ o, err := d.getFactory().Get(d.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -184,9 +137,9 @@ func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
}
// ScanSA scans for serviceaccount refs.
-func (d *DaemonSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (d *DaemonSet) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything())
+ oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -210,9 +163,9 @@ func (d *DaemonSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, er
}
// Scan scans for cluster refs.
-func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (d *DaemonSet) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything())
+ oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -225,7 +178,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
return nil, errors.New("expecting StatefulSet resource")
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&ds.Spec.Template.Spec, n) {
continue
}
@@ -233,7 +186,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait)
if err != nil {
slog.Warn("Unable to locate secret",
@@ -249,7 +202,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
- case PvcGVR:
+ case client.PvcGVR:
if !hasPVC(&ds.Spec.Template.Spec, n) {
continue
}
@@ -257,7 +210,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&ds.Spec.Template.Spec, n) {
continue
}
@@ -284,7 +237,7 @@ func (d *DaemonSet) GetPodSpec(path string) (*v1.PodSpec, error) {
// SetImages sets container images.
func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
- auth, err := d.Client().CanI(ns, "apps/v1/daemonset", n, client.PatchAccess)
+ auth, err := d.Client().CanI(ns, d.gvr, n, client.PatchAccess)
if err != nil {
return err
}
diff --git a/internal/dao/dynamic.go b/internal/dao/dynamic.go
index de97b268..9e2293b3 100644
--- a/internal/dao/dynamic.go
+++ b/internal/dao/dynamic.go
@@ -40,13 +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)
-
opts := []string{d.gvr.AsResourceName()}
ns, n := client.Namespaced(fqn)
if n != "" {
opts = append(opts, n)
}
-
allNS := client.IsAllNamespaces(ns)
flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags())
f := cmdutil.NewFactory(flags)
@@ -70,7 +68,6 @@ func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, er
if err != nil {
return nil, err
}
-
oo := make([]runtime.Object, 0, len(infos))
for _, info := range infos {
o, err := decodeIntoTable(info.Object, allNS)
@@ -93,7 +90,6 @@ func decodeIntoTable(obj runtime.Object, allNs bool) (runtime.Object, error) {
if isEvent {
obj = event.Object.Object
}
-
if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] {
return nil, fmt.Errorf("attempt to decode non-Table object: %v", obj.GetObjectKind().GroupVersionKind())
}
@@ -133,7 +129,7 @@ func decodeIntoTable(obj runtime.Object, allNs bool) (runtime.Object, error) {
ns = m.GetNamespace()
}
if allNs {
- cells := make([]interface{}, 0, len(row.Cells)+1)
+ cells := make([]any, 0, len(row.Cells)+1)
cells = append(cells, ns)
cells = append(cells, row.Cells...)
row.Cells = cells
diff --git a/internal/dao/generic.go b/internal/dao/generic.go
index e7d95ccc..c5c4f6ff 100644
--- a/internal/dao/generic.go
+++ b/internal/dao/generic.go
@@ -104,7 +104,7 @@ func (g *Generic) ToYAML(path string, showManaged bool) (string, error) {
// Delete deletes a resource.
func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
ns, n := client.Namespaced(path)
- auth, err := g.Client().CanI(ns, g.gvrStr(), n, []string{client.DeleteVerb})
+ auth, err := g.Client().CanI(ns, g.gvr, n, []string{client.DeleteVerb})
if err != nil {
return err
}
diff --git a/internal/dao/helm_chart.go b/internal/dao/helm_chart.go
index 43c712b9..5ba52e07 100644
--- a/internal/dao/helm_chart.go
+++ b/internal/dao/helm_chart.go
@@ -32,7 +32,7 @@ type HelmChart struct {
}
// List returns a collection of resources.
-func (h *HelmChart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
+func (h *HelmChart) List(_ context.Context, ns string) ([]runtime.Object, error) {
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
@@ -102,7 +102,7 @@ func (h *HelmChart) Describe(path string) (string, error) {
}
// ToYAML returns the chart manifest.
-func (h *HelmChart) ToYAML(path string, showManaged bool) (string, error) {
+func (h *HelmChart) ToYAML(path string, _ bool) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
@@ -164,7 +164,7 @@ func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action.
return cfg, err
}
-func helmLogger(fmat string, args ...interface{}) {
+func helmLogger(fmat string, args ...any) {
slog.Debug("Log",
slogs.Log, fmt.Sprintf(fmat, args...),
slogs.Subsys, "helm",
diff --git a/internal/dao/helm_history.go b/internal/dao/helm_history.go
index bae7ca28..10900ee4 100644
--- a/internal/dao/helm_history.go
+++ b/internal/dao/helm_history.go
@@ -59,7 +59,7 @@ func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, err
// Get returns a resource.
func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) {
fqn, rev, found := strings.Cut(path, ":")
- if !found || len(rev) == 0 {
+ if !found || rev == "" {
return nil, fmt.Errorf("invalid path %q", path)
}
@@ -99,7 +99,7 @@ func (h *HelmHistory) Describe(path string) (string, error) {
}
// ToYAML returns the chart manifest.
-func (h *HelmHistory) ToYAML(path string, showManaged bool) (string, error) {
+func (h *HelmHistory) ToYAML(path string, _ bool) (string, error) {
rel, err := h.Get(context.Background(), path)
if err != nil {
return "", err
diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go
index 63d4c0a5..1bf6531b 100644
--- a/internal/dao/helpers.go
+++ b/internal/dao/helpers.go
@@ -27,14 +27,14 @@ const (
)
// GetDefaultContainer returns a container name if specified in an annotation.
-func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) {
+func GetDefaultContainer(m *metav1.ObjectMeta, spec *v1.PodSpec) (string, bool) {
defaultContainer, ok := m.Annotations[DefaultContainerAnnotation]
if !ok {
return "", false
}
- for _, container := range spec.Containers {
- if container.Name == defaultContainer {
+ for i := range spec.Containers {
+ if spec.Containers[i].Name == defaultContainer {
return defaultContainer, true
}
}
@@ -93,7 +93,7 @@ func ToYAML(o runtime.Object, showManaged bool) (string, error) {
if !showManaged {
o = o.DeepCopyObject()
uo := o.(*unstructured.Unstructured).Object
- if meta, ok := uo["metadata"].(map[string]interface{}); ok {
+ if meta, ok := uo["metadata"].(map[string]any); ok {
delete(meta, "managedFields")
}
}
diff --git a/internal/dao/helpers_test.go b/internal/dao/helpers_test.go
index f201654b..23dcec14 100644
--- a/internal/dao/helpers_test.go
+++ b/internal/dao/helpers_test.go
@@ -20,6 +20,7 @@ func TestToPerc(t *testing.T) {
}
for _, u := range uu {
+ //nolint:testifylint
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
}
}
diff --git a/internal/dao/img_scan.go b/internal/dao/img_scan.go
index 4db8705b..3a5daa45 100644
--- a/internal/dao/img_scan.go
+++ b/internal/dao/img_scan.go
@@ -21,7 +21,7 @@ type ImageScan struct {
NonResource
}
-func (is *ImageScan) listImages(ctx context.Context, gvr client.GVR, path string) ([]string, error) {
+func (is *ImageScan) listImages(ctx context.Context, gvr *client.GVR, path string) ([]string, error) {
res, err := AccessorFor(is.Factory, gvr)
if err != nil {
return nil, err
@@ -40,7 +40,7 @@ func (is *ImageScan) List(ctx context.Context, _ string) ([]runtime.Object, erro
if !ok {
return nil, fmt.Errorf("no context path for %q", is.gvr)
}
- gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
+ gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, fmt.Errorf("no context gvr for %q", is.gvr)
}
diff --git a/internal/dao/job.go b/internal/dao/job.go
index eca8f6c9..c44dab2e 100644
--- a/internal/dao/job.go
+++ b/internal/dao/job.go
@@ -32,7 +32,7 @@ type Job struct {
}
// ListImages lists container images.
-func (j *Job) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (j *Job) ListImages(_ context.Context, fqn string) ([]string, error) {
job, err := j.GetInstance(fqn)
if err != nil {
return nil, err
@@ -74,7 +74,7 @@ func (j *Job) List(ctx context.Context, ns string) ([]runtime.Object, error) {
// TailLogs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
- o, err := j.getFactory().Get(j.gvrStr(), opts.Path, true, labels.Everything())
+ o, err := j.getFactory().Get(j.gvr, opts.Path, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -93,7 +93,7 @@ func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
}
func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
- o, err := j.getFactory().Get(j.gvrStr(), fqn, true, labels.Everything())
+ o, err := j.getFactory().Get(j.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -108,9 +108,9 @@ func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
}
// ScanSA scans for serviceaccount refs.
-func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (j *Job) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := j.getFactory().List(j.GVR(), ns, wait, labels.Everything())
+ oo, err := j.getFactory().List(j.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -134,9 +134,9 @@ func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
}
// Scan scans for resource references.
-func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (j *Job) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := j.getFactory().List(j.GVR(), ns, wait, labels.Everything())
+ oo, err := j.getFactory().List(j.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -149,7 +149,7 @@ func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
return nil, errors.New("expecting Job resource")
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&job.Spec.Template.Spec, n) {
continue
}
@@ -157,7 +157,7 @@ func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait)
if err != nil {
slog.Warn("Locate secret failed",
@@ -173,7 +173,7 @@ func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&job.Spec.Template.Spec, n) {
continue
}
diff --git a/internal/dao/log_item.go b/internal/dao/log_item.go
index ff90bbff..0c8fe230 100644
--- a/internal/dao/log_item.go
+++ b/internal/dao/log_item.go
@@ -84,11 +84,11 @@ func (l *LogItem) Render(paint string, showTime bool, bb *bytes.Buffer) {
}
if !l.SingleContainer && l.Container != "" {
- if len(l.Pod) > 0 {
+ if l.Pod != "" {
bb.WriteString(" ")
}
bb.WriteString("[" + paint + "::b]" + l.Container + "[-::-] ")
- } else if len(l.Pod) > 0 {
+ } else if l.Pod != "" {
bb.WriteString("[-::] ")
}
diff --git a/internal/dao/log_item_test.go b/internal/dao/log_item_test.go
index f805f96f..c25878a4 100644
--- a/internal/dao/log_item_test.go
+++ b/internal/dao/log_item_test.go
@@ -112,7 +112,7 @@ func BenchmarkLogItemRenderTS(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
i.Render("yellow", true, bb)
}
@@ -125,7 +125,7 @@ func BenchmarkLogItemRenderNoTS(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
i.Render("yellow", false, bb)
}
diff --git a/internal/dao/log_items.go b/internal/dao/log_items.go
index 898c0c84..c5ad94f8 100644
--- a/internal/dao/log_items.go
+++ b/internal/dao/log_items.go
@@ -171,25 +171,25 @@ func (l *LogItems) DumpDebug(m string) {
}
// Filter filters out log items based on given filter.
-func (l *LogItems) Filter(index int, q string, showTime bool) ([]int, [][]int, error) {
+func (l *LogItems) Filter(index int, q string, showTime bool) (matches []int, indices [][]int, err error) {
if q == "" {
- return nil, nil, nil
+ return
}
if f, ok := internal.IsFuzzySelector(q); ok {
- mm, ii := l.fuzzyFilter(index, f, showTime)
- return mm, ii, nil
+ matches, indices = l.fuzzyFilter(index, f, showTime)
+ return
}
- matches, indices, err := l.filterLogs(index, q, showTime)
+ matches, indices, err = l.filterLogs(index, q, showTime)
if err != nil {
- return nil, nil, err
+ return
}
return matches, indices, nil
}
-func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) ([]int, [][]int) {
+func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) (matches []int, indices [][]int) {
q = strings.TrimSpace(q)
- matches, indices := make([]int, 0, len(l.items)), make([][]int, 0, 10)
+ matches, indices = make([]int, 0, len(l.items)), make([][]int, 0, len(l.items))
mm := fuzzy.Find(q, l.StrLines(index, showTime))
for _, m := range mm {
matches = append(matches, m.Index)
@@ -199,7 +199,7 @@ func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) ([]int, [][]i
return matches, indices
}
-func (l *LogItems) filterLogs(index int, q string, showTime bool) ([]int, [][]int, error) {
+func (l *LogItems) filterLogs(index int, q string, showTime bool) (matches []int, indices [][]int, err error) {
var invert bool
if internal.IsInverseSelector(q) {
invert = true
@@ -209,7 +209,7 @@ func (l *LogItems) filterLogs(index int, q string, showTime bool) ([]int, [][]in
if err != nil {
return nil, nil, err
}
- matches, indices := make([]int, 0, len(l.items)), make([][]int, 0, 10)
+ matches, indices = make([]int, 0, len(l.items)), make([][]int, 0, len(l.items))
ll := make([][]byte, len(l.items[index:]))
l.Lines(index, showTime, ll)
for i, line := range ll {
diff --git a/internal/dao/log_options.go b/internal/dao/log_options.go
index edd20afb..40824bdb 100644
--- a/internal/dao/log_options.go
+++ b/internal/dao/log_options.go
@@ -31,9 +31,10 @@ type LogOptions struct {
// Info returns the option pod and container info.
func (o *LogOptions) Info() string {
- if len(o.Container) != 0 {
+ if o.Container != "" {
return fmt.Sprintf("%s (%s)", o.Path, o.Container)
}
+
return o.Path
}
@@ -131,7 +132,7 @@ func (o *LogOptions) ToLogItem(bytes []byte) *LogItem {
return item
}
-func (o *LogOptions) ToErrLogItem(err error) *LogItem {
+func (*LogOptions) ToErrLogItem(err error) *LogItem {
t := time.Now().UTC().Format(time.RFC3339Nano)
item := NewLogItem([]byte(fmt.Sprintf("%s [orange::b]%s[::-]\n", t, err)))
item.IsError = true
diff --git a/internal/dao/node.go b/internal/dao/node.go
index 68a30ec6..048f3695 100644
--- a/internal/dao/node.go
+++ b/internal/dao/node.go
@@ -102,8 +102,8 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
}
if !cordoned {
- if err = n.ToggleCordon(path, true); err != nil {
- return err
+ if e := n.ToggleCordon(path, true); e != nil {
+ return e
}
}
@@ -168,7 +168,13 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
}
shouldCountPods, _ := ctx.Value(internal.KeyPodCounting).(bool)
-
+ var pods []runtime.Object
+ if shouldCountPods {
+ pods, err = n.getFactory().List(client.PodGVR, client.BlankNamespace, false, labels.Everything())
+ if err != nil {
+ slog.Error("Unable to list pods", slogs.Error, err)
+ }
+ }
res := make([]runtime.Object, 0, len(oo))
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
@@ -180,7 +186,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
_, name := client.Namespaced(fqn)
podCount := -1
if shouldCountPods {
- podCount, err = n.CountPods(name)
+ podCount, err = n.CountPods(pods, name)
if err != nil {
slog.Error("Unable to get pods count",
slogs.ResName, name,
@@ -199,21 +205,16 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
}
// CountPods counts the pods scheduled on a given node.
-func (n *Node) CountPods(nodeName string) (int, error) {
+func (*Node) CountPods(oo []runtime.Object, nodeName string) (int, error) {
var count int
- oo, err := n.getFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything())
- if err != nil {
- return 0, err
- }
-
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
- return count, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
+ return count, fmt.Errorf("expecting *Unstructured but got `%T", o)
}
- spec, ok := u.Object["spec"].(map[string]interface{})
+ spec, ok := u.Object["spec"].(map[string]any)
if !ok {
- return count, fmt.Errorf("expecting interface map but got `%T", o)
+ return count, fmt.Errorf("expecting spec interface map but got `%T", o)
}
if node, ok := spec["nodeName"]; ok && node == nodeName {
count++
@@ -225,7 +226,7 @@ func (n *Node) CountPods(nodeName string) (int, error) {
// GetPods returns all pods running on given node.
func (n *Node) GetPods(nodeName string) ([]*v1.Pod, error) {
- oo, err := n.getFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything())
+ oo, err := n.getFactory().List(client.PodGVR, client.BlankNamespace, false, labels.Everything())
if err != nil {
return nil, err
}
@@ -258,9 +259,9 @@ func (n *Node) ensureCordoned(path string) (bool, error) {
// Helpers...
// FetchNode retrieves a node.
-func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
+func FetchNode(_ context.Context, f Factory, path string) (*v1.Node, error) {
_, n := client.Namespaced(path)
- auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", n, client.GetAccess)
+ auth, err := f.Client().CanI(client.ClusterScope, client.NodeGVR, n, client.GetAccess)
if err != nil {
return nil, err
}
@@ -268,7 +269,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
return nil, fmt.Errorf("user is not authorized to list nodes")
}
- o, err := f.Get("v1/nodes", client.FQN(client.ClusterScope, path), true, labels.Everything())
+ o, err := f.Get(client.NodeGVR, client.FQN(client.ClusterScope, path), true, labels.Everything())
if err != nil {
return nil, err
}
@@ -283,8 +284,8 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
}
// FetchNodes retrieves all nodes.
-func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) {
- auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", "", client.ListAccess)
+func FetchNodes(_ context.Context, f Factory, _ string) (*v1.NodeList, error) {
+ auth, err := f.Client().CanI(client.ClusterScope, client.NodeGVR, "", client.ListAccess)
if err != nil {
return nil, err
}
@@ -292,7 +293,7 @@ func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList,
return nil, fmt.Errorf("user is not authorized to list nodes")
}
- oo, err := f.List("v1/nodes", "", false, labels.Everything())
+ oo, err := f.List(client.NodeGVR, "", false, labels.Everything())
if err != nil {
return nil, err
}
diff --git a/internal/dao/non_resource.go b/internal/dao/non_resource.go
index b7b1e6f3..b407e5a4 100644
--- a/internal/dao/non_resource.go
+++ b/internal/dao/non_resource.go
@@ -16,20 +16,19 @@ import (
type NonResource struct {
Factory
- gvr client.GVR
+ gvr *client.GVR
mx sync.RWMutex
includeObj bool
}
// Init initializes the resource.
-func (n *NonResource) Init(f Factory, gvr client.GVR) {
+func (n *NonResource) Init(f Factory, gvr *client.GVR) {
n.mx.Lock()
- {
- n.Factory, n.gvr = f, gvr
- }
+ n.Factory, n.gvr = f, gvr
n.mx.Unlock()
}
+// SetIncludeObject sets if resource object should be included in the api server response.
func (n *NonResource) SetIncludeObject(f bool) {
n.includeObj = f
}
@@ -57,6 +56,6 @@ func (n *NonResource) GVR() string {
}
// Get returns the given resource.
-func (n *NonResource) Get(context.Context, string) (runtime.Object, error) {
+func (*NonResource) Get(context.Context, string) (runtime.Object, error) {
return nil, fmt.Errorf("nyi")
}
diff --git a/internal/dao/patch.go b/internal/dao/patch.go
index e834f196..b678c25c 100644
--- a/internal/dao/patch.go
+++ b/internal/dao/patch.go
@@ -75,7 +75,7 @@ func getPatchPodSpec(imageSpecs ImageSpecs) PodSpec {
return podSpec
}
-func extractElements(imageSpecs ImageSpecs) (initElementsOrders []Element, initElements []Element, elementsOrders []Element, elements []Element) {
+func extractElements(imageSpecs ImageSpecs) (initElementsOrders, initElements, elementsOrders, elements []Element) {
for _, spec := range imageSpecs {
if spec.Init {
initElementsOrders = append(initElementsOrders, Element{Name: spec.Name})
diff --git a/internal/dao/pod.go b/internal/dao/pod.go
index d402999d..c32e22c3 100644
--- a/internal/dao/pod.go
+++ b/internal/dao/pod.go
@@ -69,7 +69,7 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
}
// ListImages lists container images.
-func (p *Pod) ListImages(ctx context.Context, path string) ([]string, error) {
+func (p *Pod) ListImages(_ context.Context, path string) ([]string, error) {
pod, err := p.GetInstance(path)
if err != nil {
return nil, err
@@ -108,7 +108,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
continue
}
- spec, ok := u.Object["spec"].(map[string]interface{})
+ spec, ok := u.Object["spec"].(map[string]any)
if !ok {
return res, fmt.Errorf("expecting interface map but got `%T", o)
}
@@ -123,7 +123,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
// Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
ns, n := client.Namespaced(path)
- auth, err := p.Client().CanI(ns, "v1/pods:log", n, client.GetAccess)
+ auth, err := p.Client().CanI(ns, client.NewGVR(client.PodGVR.String()+":log"), n, client.GetAccess)
if err != nil {
return nil, err
}
@@ -147,13 +147,13 @@ func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
}
cc := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
- for _, c := range pod.Spec.Containers {
- cc = append(cc, c.Name)
+ for i := range pod.Spec.Containers {
+ cc = append(cc, pod.Spec.Containers[i].Name)
}
if includeInit {
- for _, c := range pod.Spec.InitContainers {
- cc = append(cc, c.Name)
+ for i := range pod.Spec.InitContainers {
+ cc = append(cc, pod.Spec.InitContainers[i].Name)
}
}
@@ -161,13 +161,13 @@ func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
}
// Pod returns a pod victim by name.
-func (p *Pod) Pod(fqn string) (string, error) {
+func (*Pod) Pod(fqn string) (string, error) {
return fqn, nil
}
// GetInstance returns a pod instance.
func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) {
- o, err := p.getFactory().Get(p.gvrStr(), fqn, true, labels.Everything())
+ o, err := p.getFactory().Get(p.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -187,7 +187,7 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
if !ok {
return nil, errors.New("no factory in context")
}
- o, err := fac.Get(p.gvrStr(), opts.Path, true, labels.Everything())
+ o, err := fac.Get(p.gvr, opts.Path, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -201,26 +201,26 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
}
outs := make([]LogChan, 0, coCounts)
- if co, ok := GetDefaultContainer(po.ObjectMeta, po.Spec); ok && !opts.AllContainers {
+ if co, ok := GetDefaultContainer(&po.ObjectMeta, &po.Spec); ok && !opts.AllContainers {
opts.DefaultContainer = co
return append(outs, tailLogs(ctx, p, opts)), nil
}
if opts.HasContainer() && !opts.AllContainers {
return append(outs, tailLogs(ctx, p, opts)), nil
}
- for _, co := range po.Spec.InitContainers {
+ for i := range po.Spec.InitContainers {
cfg := opts.Clone()
- cfg.Container = co.Name
+ cfg.Container = po.Spec.InitContainers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
- for _, co := range po.Spec.Containers {
+ for i := range po.Spec.Containers {
cfg := opts.Clone()
- cfg.Container = co.Name
+ cfg.Container = po.Spec.Containers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
- for _, co := range po.Spec.EphemeralContainers {
+ for i := range po.Spec.EphemeralContainers {
cfg := opts.Clone()
- cfg.Container = co.Name
+ cfg.Container = po.Spec.EphemeralContainers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
@@ -228,9 +228,9 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
}
// ScanSA scans for ServiceAccount refs.
-func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (p *Pod) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := p.getFactory().List(p.GVR(), ns, wait, labels.Everything())
+ oo, err := p.getFactory().List(p.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -258,9 +258,9 @@ func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
}
// Scan scans for cluster resource refs.
-func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (p *Pod) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := p.getFactory().List(p.GVR(), ns, wait, labels.Everything())
+ oo, err := p.getFactory().List(p.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -277,7 +277,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
continue
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&pod.Spec, n) {
continue
}
@@ -285,7 +285,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
if err != nil {
slog.Warn("Locate secret failed",
@@ -301,7 +301,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
- case PvcGVR:
+ case client.PvcGVR:
if !hasPVC(&pod.Spec, n) {
continue
}
@@ -309,7 +309,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&pod.Spec, n) {
continue
}
@@ -336,20 +336,20 @@ func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan {
go func() {
defer wg.Done()
podOpts := opts.ToPodLogOptions()
- for r := 0; r < logRetryCount; r++ {
+ for range logRetryCount {
req, err := logger.Logs(opts.Path, podOpts)
if err == nil {
// This call will block if nothing is in the stream!!
- if stream, e := req.Stream(ctx); e == nil {
+ stream, e := req.Stream(ctx)
+ if e == nil {
wg.Add(1)
go readLogs(ctx, &wg, stream, out, opts)
return
- } else {
- slog.Error("Stream logs failed",
- slogs.Error, e,
- slogs.Container, opts.Info(),
- )
}
+ slog.Error("Stream logs failed",
+ slogs.Error, e,
+ slogs.Container, opts.Info(),
+ )
} else {
slog.Error("Log request failed",
slogs.Container, opts.Info(),
@@ -419,7 +419,7 @@ func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out
}
// MetaFQN returns a fully qualified resource name.
-func MetaFQN(m metav1.ObjectMeta) string {
+func MetaFQN(m *metav1.ObjectMeta) string {
if m.Namespace == "" {
return m.Name
}
@@ -434,13 +434,14 @@ func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) {
return nil, err
}
podSpec := pod.Spec
+
return &podSpec, nil
}
// SetImages sets container images.
func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
- auth, err := p.Client().CanI(ns, "v1/pod", n, client.PatchAccess)
+ auth, err := p.Client().CanI(ns, p.gvr, n, client.PatchAccess)
if err != nil {
return err
}
@@ -469,10 +470,11 @@ func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs)
jsonPatch,
metav1.PatchOptions{},
)
+
return err
}
-func (p *Pod) isControlled(path string) (string, bool, error) {
+func (p *Pod) isControlled(path string) (fqn string, ok bool, err error) {
pod, err := p.GetInstance(path)
if err != nil {
return "", false, err
@@ -481,6 +483,7 @@ func (p *Pod) isControlled(path string) (string, bool, error) {
if len(references) > 0 {
return fmt.Sprintf("%s/%s", references[0].Kind, references[0].Name), true, nil
}
+
return "", false, nil
}
diff --git a/internal/dao/pod_test.go b/internal/dao/pod_test.go
index f6f1593c..796267c8 100644
--- a/internal/dao/pod_test.go
+++ b/internal/dao/pod_test.go
@@ -51,7 +51,7 @@ func TestGetDefaultContainer(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- container, ok := GetDefaultContainer(u.po.ObjectMeta, u.po.Spec)
+ container, ok := GetDefaultContainer(&u.po.ObjectMeta, &u.po.Spec)
assert.Equal(t, u.wantContainer, container)
assert.Equal(t, u.wantOk, ok)
})
diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go
index c42bbb69..bd96451d 100644
--- a/internal/dao/port_forwarder.go
+++ b/internal/dao/port_forwarder.go
@@ -122,7 +122,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por
p.path, p.tunnel, p.age = path, tt, time.Now()
ns, n := client.Namespaced(path)
- auth, err := p.Client().CanI(ns, "v1/pods", n, client.GetAccess)
+ auth, err := p.Client().CanI(ns, client.PodGVR, n, client.GetAccess)
if err != nil {
return nil, err
}
@@ -132,7 +132,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por
podName := strings.Split(n, "|")[0]
var res Pod
- res.Init(p, client.NewGVR("v1/pods"))
+ res.Init(p, client.PodGVR)
pod, err := res.GetInstance(client.FQN(ns, podName))
if err != nil {
return nil, err
@@ -141,7 +141,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por
return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
}
- auth, err = p.Client().CanI(ns, "v1/pods:portforward", "", []string{client.CreateVerb})
+ auth, err = p.Client().CanI(ns, client.PodGVR.WithSubResource("portforward"), "", []string{client.CreateVerb})
if err != nil {
return nil, err
}
diff --git a/internal/dao/pulse.go b/internal/dao/pulse.go
index b7d09c7b..15d4f94e 100644
--- a/internal/dao/pulse.go
+++ b/internal/dao/pulse.go
@@ -16,6 +16,6 @@ type Pulse struct {
}
// List lists out pulses.
-func (h *Pulse) List(ctx context.Context, ns string) ([]runtime.Object, error) {
+func (*Pulse) List(context.Context, string) ([]runtime.Object, error) {
return nil, fmt.Errorf("NYI")
}
diff --git a/internal/dao/rbac.go b/internal/dao/rbac.go
index afcd4ce6..2e078e2e 100644
--- a/internal/dao/rbac.go
+++ b/internal/dao/rbac.go
@@ -18,13 +18,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
-const (
- crbGVR = "rbac.authorization.k8s.io/v1/clusterrolebindings"
- crGVR = "rbac.authorization.k8s.io/v1/clusterroles"
- rbGVR = "rbac.authorization.k8s.io/v1/rolebindings"
- rGVR = "rbac.authorization.k8s.io/v1/roles"
-)
-
var (
_ Accessor = (*Rbac)(nil)
_ Nuker = (*Rbac)(nil)
@@ -37,7 +30,7 @@ type Rbac struct {
// List lists out rbac resources.
func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) {
- gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
+ gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, fmt.Errorf("expecting a context gvr")
}
@@ -61,23 +54,22 @@ func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) {
}
func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
- o, err := r.getFactory().Get(crbGVR, path, true, labels.Everything())
+ crbo, err := r.getFactory().Get(client.CrbGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
-
var crb rbacv1.ClusterRoleBinding
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
+ err = runtime.DefaultUnstructuredConverter.FromUnstructured(crbo.(*unstructured.Unstructured).Object, &crb)
if err != nil {
return nil, err
}
- crbo, err := r.getFactory().Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything())
+ cro, err := r.getFactory().Get(client.CrGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
var cr rbacv1.ClusterRole
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(crbo.(*unstructured.Unstructured).Object, &cr)
+ err = runtime.DefaultUnstructuredConverter.FromUnstructured(crbo.(*unstructured.Unstructured).Object, &cro)
if err != nil {
return nil, err
}
@@ -86,30 +78,29 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
}
func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
- o, err := r.getFactory().Get(rbGVR, path, true, labels.Everything())
+ rbo, err := r.getFactory().Get(client.RobGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
-
var rb rbacv1.RoleBinding
- if err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb); err != nil {
- return nil, err
+ if e := runtime.DefaultUnstructuredConverter.FromUnstructured(rbo.(*unstructured.Unstructured).Object, &rb); e != nil {
+ return nil, e
}
if rb.RoleRef.Kind == "ClusterRole" {
- o, e := r.getFactory().Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything())
+ cro, e := r.getFactory().Get(client.CrGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything())
if e != nil {
return nil, e
}
var cr rbacv1.ClusterRole
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
+ err = runtime.DefaultUnstructuredConverter.FromUnstructured(cro.(*unstructured.Unstructured).Object, &cr)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil
}
- ro, err := r.getFactory().Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything())
+ ro, err := r.getFactory().Get(client.RoGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
@@ -124,11 +115,10 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) {
slog.Debug("LOAD-CR", slogs.FQN, fqn)
- o, err := r.getFactory().Get(crGVR, fqn, true, labels.Everything())
+ o, err := r.getFactory().Get(client.CrGVR, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
-
var cr rbacv1.ClusterRole
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
if err != nil {
@@ -139,11 +129,10 @@ func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) {
}
func (r *Rbac) loadRole(path string) ([]runtime.Object, error) {
- o, err := r.getFactory().Get(rGVR, path, true, labels.Everything())
+ o, err := r.getFactory().Get(client.RoGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
-
var ro rbacv1.Role
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro)
if err != nil {
diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go
index 067b45b3..4dd89bff 100644
--- a/internal/dao/rbac_policy.go
+++ b/internal/dao/rbac_policy.go
@@ -29,7 +29,7 @@ type Policy struct {
}
// List returns available policies.
-func (p *Policy) List(ctx context.Context, ns string) ([]runtime.Object, error) {
+func (p *Policy) List(ctx context.Context, _ string) ([]runtime.Object, error) {
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
if !ok {
return nil, fmt.Errorf("expecting a context subject kind")
@@ -67,11 +67,10 @@ func (p *Policy) loadClusterRoleBinding(kind, name string) (render.Policies, err
ns, n := client.Namespaced(name)
var nn []string
- for _, crb := range crbs {
- for _, s := range crb.Subjects {
- s := s
+ for i := range crbs {
+ for _, s := range crbs[i].Subjects {
if isSameSubject(kind, ns, n, &s) {
- nn = append(nn, crb.RoleRef.Name)
+ nn = append(nn, crbs[i].RoleRef.Name)
}
}
}
@@ -81,11 +80,11 @@ func (p *Policy) loadClusterRoleBinding(kind, name string) (render.Policies, err
}
rows := make(render.Policies, 0, len(nn))
- for _, cr := range crs {
- if !inList(nn, cr.Name) {
+ for i := range crs {
+ if !inList(nn, crs[i].Name) {
continue
}
- rows = append(rows, parseRules(client.NotNamespaced, "CR:"+cr.Name, cr.Rules)...)
+ rows = append(rows, parseRules(client.NotNamespaced, "CR:"+crs[i].Name, crs[i].Rules)...)
}
return rows, nil
@@ -101,13 +100,13 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) {
return nil, err
}
rows := make(render.Policies, 0, len(crs))
- for _, cr := range crs {
- if rbNs, ok := rbsMap["ClusterRole:"+cr.Name]; ok {
+ for i := range crs {
+ if rbNs, ok := rbsMap["ClusterRole:"+crs[i].Name]; ok {
slog.Debug("Loading rules for clusterrole",
slogs.Namespace, rbNs,
- slogs.ResName, cr.Name,
+ slogs.ResName, crs[i].Name,
)
- rows = append(rows, parseRules(rbNs, "CR:"+cr.Name, cr.Rules)...)
+ rows = append(rows, parseRules(rbNs, "CR:"+crs[i].Name, crs[i].Rules)...)
}
}
@@ -115,22 +114,22 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) {
if err != nil {
return nil, err
}
- for _, ro := range ros {
- if _, ok := rbsMap["Role:"+ro.Name]; !ok {
+ for i := range ros {
+ if _, ok := rbsMap["Role:"+ros[i].Name]; !ok {
continue
}
slog.Debug("Loading rules for role",
- slogs.Namespace, ro.Namespace,
- slogs.ResName, ro.Name,
+ slogs.Namespace, ros[i].Namespace,
+ slogs.ResName, ros[i].Name,
)
- rows = append(rows, parseRules(ro.Namespace, "RO:"+ro.Name, ro.Rules)...)
+ rows = append(rows, parseRules(ros[i].Namespace, "RO:"+ros[i].Name, ros[i].Rules)...)
}
return rows, nil
}
func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
- oo, err := f.List(crbGVR, client.ClusterScope, false, labels.Everything())
+ oo, err := f.List(client.CrbGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
@@ -148,7 +147,7 @@ func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
}
func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, error) {
- oo, err := f.List(rbGVR, client.ClusterScope, false, labels.Everything())
+ oo, err := f.List(client.RobGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
@@ -173,11 +172,10 @@ func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]strin
ns, n := client.Namespaced(name)
ss := make(map[string]string, len(rbs))
- for _, rb := range rbs {
- for _, s := range rb.Subjects {
- s := s
+ for i := range rbs {
+ for _, s := range rbs[i].Subjects {
if isSameSubject(kind, ns, n, &s) {
- ss[rb.RoleRef.Kind+":"+rb.RoleRef.Name] = rb.Namespace
+ ss[rbs[i].RoleRef.Kind+":"+rbs[i].RoleRef.Name] = rbs[i].Namespace
}
}
}
@@ -200,9 +198,7 @@ func isSameSubject(kind, ns, name string, subject *rbacv1.Subject) bool {
}
func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
- const gvr = "rbac.authorization.k8s.io/v1/clusterroles"
-
- oo, err := p.getFactory().List(gvr, client.ClusterScope, false, labels.Everything())
+ oo, err := p.getFactory().List(client.CrGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
@@ -220,9 +216,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
}
func (p *Policy) fetchRoles() ([]rbacv1.Role, error) {
- const gvr = "rbac.authorization.k8s.io/v1/roles"
-
- oo, err := p.getFactory().List(gvr, client.BlankNamespace, false, labels.Everything())
+ oo, err := p.getFactory().List(client.RoGVR, client.BlankNamespace, false, labels.Everything())
if err != nil {
return nil, err
}
diff --git a/internal/dao/rbac_subject.go b/internal/dao/rbac_subject.go
index 0939b50a..056af41b 100644
--- a/internal/dao/rbac_subject.go
+++ b/internal/dao/rbac_subject.go
@@ -23,7 +23,7 @@ type Subject struct {
}
// List returns a collection of subjects.
-func (s *Subject) List(ctx context.Context, ns string) ([]runtime.Object, error) {
+func (s *Subject) List(ctx context.Context, _ string) ([]runtime.Object, error) {
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
if !ok {
return nil, errors.New("expecting a SubjectKind")
@@ -57,15 +57,15 @@ func (s *Subject) listClusterRoleBindings(kind string) (render.Subjects, error)
}
oo := make(render.Subjects, 0, len(crbs))
- for _, crb := range crbs {
- for _, su := range crb.Subjects {
+ for i := range crbs {
+ for _, su := range crbs[i].Subjects {
if su.Kind != kind {
continue
}
oo = oo.Upsert(render.SubjectRes{
Name: su.Name,
Kind: "ClusterRoleBinding",
- FirstLocation: crb.Name,
+ FirstLocation: crbs[i].Name,
})
}
}
@@ -80,15 +80,15 @@ func (s *Subject) listRoleBindings(kind string) (render.Subjects, error) {
}
oo := make(render.Subjects, 0, len(rbs))
- for _, rb := range rbs {
- for _, su := range rb.Subjects {
+ for i := range rbs {
+ for _, su := range rbs[i].Subjects {
if su.Kind != kind {
continue
}
oo = oo.Upsert(render.SubjectRes{
Name: su.Name,
Kind: "RoleBinding",
- FirstLocation: rb.Name,
+ FirstLocation: rbs[i].Name,
})
}
}
diff --git a/internal/dao/reference.go b/internal/dao/reference.go
index 3ea129f6..ac84015a 100644
--- a/internal/dao/reference.go
+++ b/internal/dao/reference.go
@@ -21,13 +21,13 @@ type Reference struct {
}
// List collects all references.
-func (r *Reference) List(ctx context.Context, ns string) ([]runtime.Object, error) {
- gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
+func (r *Reference) List(ctx context.Context, _ string) ([]runtime.Object, error) {
+ gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, errors.New("no context for gvr found")
}
switch gvr {
- case SaGVR:
+ case client.SaGVR:
return r.ScanSA(ctx)
default:
return r.Scan(ctx)
@@ -35,7 +35,7 @@ func (r *Reference) List(ctx context.Context, ns string) ([]runtime.Object, erro
}
// Get fetch a given reference.
-func (r *Reference) Get(ctx context.Context, path string) (runtime.Object, error) {
+func (*Reference) Get(context.Context, string) (runtime.Object, error) {
panic("NYI")
}
diff --git a/internal/dao/registry.go b/internal/dao/registry.go
index bcc725f7..bd9e5edf 100644
--- a/internal/dao/registry.go
+++ b/internal/dao/registry.go
@@ -6,8 +6,8 @@ package dao
import (
"fmt"
"log/slog"
+ "maps"
"slices"
- "sort"
"strings"
"sync"
@@ -27,12 +27,8 @@ const (
k9sCat = "k9s"
helmCat = "helm"
scaleCat = "scale"
- crdGVR = "apiextensions.k8s.io/v1/customresourcedefinitions"
)
-// MetaAccess tracks resources metadata.
-var MetaAccess = NewMeta()
-
var stdGroups = sets.New[string](
"apps/v1",
"autoscaling/v1",
@@ -47,12 +43,18 @@ var stdGroups = sets.New[string](
"v1",
)
+// ResourceMetas represents a collection of resource metadata.
+type ResourceMetas map[*client.GVR]*metav1.APIResource
+
func (m ResourceMetas) clear() {
for k := range m {
delete(m, k)
}
}
+// MetaAccess tracks resources metadata.
+var MetaAccess = NewMeta()
+
// Meta represents available resource metas.
type Meta struct {
resMetas ResourceMetas
@@ -64,71 +66,40 @@ func NewMeta() *Meta {
return &Meta{resMetas: make(ResourceMetas)}
}
-// AccessorFor returns a client accessor for a resource if registered.
-// Otherwise it returns a generic accessor.
-// Customize here for non resource types or types with metrics or logs.
-func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
- m := Accessors{
- client.NewGVR("workloads"): &Workload{},
- client.NewGVR("contexts"): &Context{},
- client.NewGVR("containers"): &Container{},
- client.NewGVR("scans"): &ImageScan{},
- client.NewGVR("screendumps"): &ScreenDump{},
- client.NewGVR("benchmarks"): &Benchmark{},
- client.NewGVR("portforwards"): &PortForward{},
- client.NewGVR("dir"): &Dir{},
- client.NewGVR("v1/services"): &Service{},
- client.NewGVR("v1/pods"): &Pod{},
- client.NewGVR("v1/nodes"): &Node{},
- client.NewGVR("v1/namespaces"): &Namespace{},
- client.NewGVR("v1/configmaps"): &ConfigMap{},
- client.NewGVR("v1/secrets"): &Secret{},
- client.NewGVR("apps/v1/deployments"): &Deployment{},
- client.NewGVR("apps/v1/daemonsets"): &DaemonSet{},
- client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
- client.NewGVR("apps/v1/replicasets"): &ReplicaSet{},
- client.NewGVR("batch/v1/cronjobs"): &CronJob{},
- client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
- client.NewGVR("batch/v1/jobs"): &Job{},
- client.NewGVR("helm"): &HelmChart{},
- client.NewGVR("helm-history"): &HelmHistory{},
- client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions"): &CustomResourceDefinition{},
+func (m *Meta) Lookup(cmd string) *client.GVR {
+ m.mx.RLock()
+ defer m.mx.RUnlock()
+ for gvr, meta := range m.resMetas {
+ if slices.Contains(meta.ShortNames, cmd) {
+ return gvr
+ }
+ if meta.Name == cmd || meta.SingularName == cmd || meta.Kind == cmd {
+ return gvr
+ }
}
- r, ok := m[gvr]
- if !ok {
- r = new(Scaler)
- slog.Debug("No DAO registry entry. Using generics!", slogs.GVR, gvr)
- }
- r.Init(f, gvr)
-
- return r, nil
+ return client.NoGVR
}
// RegisterMeta registers a new resource meta object.
-func (m *Meta) RegisterMeta(gvr string, res metav1.APIResource) {
+func (m *Meta) RegisterMeta(gvr string, res *metav1.APIResource) {
m.mx.Lock()
defer m.mx.Unlock()
m.resMetas[client.NewGVR(gvr)] = res
}
-// AllGVRs returns all cluster resources.
+// AllGVRs returns all sorted cluster resources.
func (m *Meta) AllGVRs() client.GVRs {
m.mx.RLock()
defer m.mx.RUnlock()
+ kk := slices.Collect(maps.Keys(m.resMetas))
- kk := make(client.GVRs, 0, len(m.resMetas))
- for k := range m.resMetas {
- kk = append(kk, k)
- }
- sort.Sort(kk)
-
- return kk
+ return client.GVRs(kk)
}
// GVK2GVR convert gvk to gvr
-func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool, bool) {
+func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (*client.GVR, bool, bool) {
m.mx.RLock()
defer m.mx.RUnlock()
@@ -142,36 +113,36 @@ func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool, b
}
// MetaFor returns a resource metadata for a given gvr.
-func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) {
+func (m *Meta) MetaFor(gvr *client.GVR) (*metav1.APIResource, error) {
m.mx.RLock()
defer m.mx.RUnlock()
- meta, ok := m.resMetas[gvr]
- if !ok {
- return metav1.APIResource{}, fmt.Errorf("no resource meta defined for %q", gvr)
+ if meta, ok := m.resMetas[gvr]; ok {
+ return meta, nil
}
- return meta, nil
+
+ return new(metav1.APIResource), fmt.Errorf("no resource meta defined for\n %q", gvr)
}
// IsCRD checks if resource represents a CRD
-func IsCRD(r metav1.APIResource) bool {
+func IsCRD(r *metav1.APIResource) bool {
return slices.Contains(r.Categories, crdCat)
}
// IsK8sMeta checks for non resource meta.
-func IsK8sMeta(m metav1.APIResource) bool {
+func IsK8sMeta(m *metav1.APIResource) bool {
return !slices.ContainsFunc(m.Categories, func(category string) bool {
return category == k9sCat || category == helmCat
})
}
// IsK9sMeta checks for non resource meta.
-func IsK9sMeta(m metav1.APIResource) bool {
+func IsK9sMeta(m *metav1.APIResource) bool {
return slices.Contains(m.Categories, k9sCat)
}
// IsScalable check if the resource can be scaled
-func IsScalable(m metav1.APIResource) bool {
+func IsScalable(m *metav1.APIResource) bool {
return slices.Contains(m.Categories, scaleCat)
}
@@ -201,7 +172,7 @@ func loadNonResource(m ResourceMetas) {
}
func loadK9s(m ResourceMetas) {
- m[client.NewGVR("workloads")] = metav1.APIResource{
+ m[client.WkGVR] = &metav1.APIResource{
Name: "workloads",
Kind: "Workload",
SingularName: "workload",
@@ -209,48 +180,48 @@ func loadK9s(m ResourceMetas) {
ShortNames: []string{"wk"},
Categories: []string{k9sCat},
}
- m[client.NewGVR("pulses")] = metav1.APIResource{
+ m[client.PuGVR] = &metav1.APIResource{
Name: "pulses",
Kind: "Pulse",
- SingularName: "pulses",
+ SingularName: "pulse",
ShortNames: []string{"hz", "pu"},
Categories: []string{k9sCat},
}
- m[client.NewGVR("dir")] = metav1.APIResource{
- Name: "dir",
+ m[client.DirGVR] = &metav1.APIResource{
+ Name: "dirs",
Kind: "Dir",
SingularName: "dir",
Categories: []string{k9sCat},
}
- m[client.NewGVR("xrays")] = metav1.APIResource{
- Name: "xray",
+ m[client.XGVR] = &metav1.APIResource{
+ Name: "xrays",
Kind: "XRays",
SingularName: "xray",
Categories: []string{k9sCat},
}
- m[client.NewGVR("references")] = metav1.APIResource{
+ m[client.RefGVR] = &metav1.APIResource{
Name: "references",
Kind: "References",
SingularName: "reference",
Verbs: []string{},
Categories: []string{k9sCat},
}
- m[client.NewGVR("aliases")] = metav1.APIResource{
+ m[client.AliGVR] = &metav1.APIResource{
Name: "aliases",
Kind: "Aliases",
SingularName: "alias",
Verbs: []string{},
Categories: []string{k9sCat},
}
- m[client.NewGVR("contexts")] = metav1.APIResource{
- Name: "contexts",
+ m[client.CtGVR] = &metav1.APIResource{
+ Name: client.CtGVR.String(),
Kind: "Contexts",
SingularName: "context",
ShortNames: []string{"ctx"},
Verbs: []string{},
Categories: []string{k9sCat},
}
- m[client.NewGVR("screendumps")] = metav1.APIResource{
+ m[client.SdGVR] = &metav1.APIResource{
Name: "screendumps",
Kind: "ScreenDumps",
SingularName: "screendump",
@@ -258,7 +229,7 @@ func loadK9s(m ResourceMetas) {
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
- m[client.NewGVR("benchmarks")] = metav1.APIResource{
+ m[client.BeGVR] = &metav1.APIResource{
Name: "benchmarks",
Kind: "Benchmarks",
SingularName: "benchmark",
@@ -266,7 +237,7 @@ func loadK9s(m ResourceMetas) {
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
- m[client.NewGVR("portforwards")] = metav1.APIResource{
+ m[client.PfGVR] = &metav1.APIResource{
Name: "portforwards",
Namespaced: true,
Kind: "PortForwards",
@@ -275,14 +246,14 @@ func loadK9s(m ResourceMetas) {
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
- m[client.NewGVR("containers")] = metav1.APIResource{
+ m[client.CoGVR] = &metav1.APIResource{
Name: "containers",
Kind: "Containers",
SingularName: "container",
Verbs: []string{},
Categories: []string{k9sCat},
}
- m[client.NewGVR("scans")] = metav1.APIResource{
+ m[client.ScnGVR] = &metav1.APIResource{
Name: "scans",
Kind: "Scans",
SingularName: "scan",
@@ -292,14 +263,14 @@ func loadK9s(m ResourceMetas) {
}
func loadHelm(m ResourceMetas) {
- m[client.NewGVR("helm")] = metav1.APIResource{
+ m[client.HmGVR] = &metav1.APIResource{
Name: "helm",
Kind: "Helm",
Namespaced: true,
Verbs: []string{"delete"},
Categories: []string{helmCat},
}
- m[client.NewGVR("helm-history")] = metav1.APIResource{
+ m[client.HmhGVR] = &metav1.APIResource{
Name: "history",
Kind: "History",
Namespaced: true,
@@ -309,23 +280,23 @@ func loadHelm(m ResourceMetas) {
}
func loadRBAC(m ResourceMetas) {
- m[client.NewGVR("rbac")] = metav1.APIResource{
+ m[client.RbacGVR] = &metav1.APIResource{
Name: "rbacs",
Kind: "Rules",
Categories: []string{k9sCat},
}
- m[client.NewGVR("policy")] = metav1.APIResource{
+ m[client.PolGVR] = &metav1.APIResource{
Name: "policies",
Kind: "Rules",
Namespaced: true,
Categories: []string{k9sCat},
}
- m[client.NewGVR("users")] = metav1.APIResource{
+ m[client.UsrGVR] = &metav1.APIResource{
Name: "users",
Kind: "User",
Categories: []string{k9sCat},
}
- m[client.NewGVR("groups")] = metav1.APIResource{
+ m[client.GrpGVR] = &metav1.APIResource{
Name: "groups",
Kind: "Group",
Categories: []string{k9sCat},
@@ -333,7 +304,7 @@ func loadRBAC(m ResourceMetas) {
}
func loadPreferred(f Factory, m ResourceMetas) error {
- if f.Client() == nil || !f.Client().ConnectionOK() {
+ if f == nil || f.Client() == nil || !f.Client().ConnectionOK() {
slog.Error("Load cluster resources - No API server connection")
return nil
}
@@ -347,7 +318,8 @@ func loadPreferred(f Factory, m ResourceMetas) error {
slog.Debug("Failed to load preferred resources", slogs.Error, err)
}
for _, r := range rr {
- for _, res := range r.APIResources {
+ for i := range r.APIResources {
+ res := r.APIResources[i]
gvr := client.FromGVAndR(r.GroupVersion, res.Name)
if isDeprecated(gvr) {
continue
@@ -359,7 +331,7 @@ func loadPreferred(f Factory, m ResourceMetas) error {
if !isStandardGroup(r.GroupVersion) {
res.Categories = append(res.Categories, crdCat)
}
- m[gvr] = res
+ m[gvr] = &res
}
}
@@ -370,21 +342,22 @@ func isStandardGroup(gv string) bool {
return stdGroups.Has(gv) || strings.Contains(gv, ".k8s.io")
}
-var deprecatedGVRs = sets.New[client.GVR](
+var deprecatedGVRs = sets.New(
+ client.NewGVR("v1/events"),
client.NewGVR("extensions/v1beta1/ingresses"),
)
-func isDeprecated(gvr client.GVR) bool {
- return deprecatedGVRs.Has(gvr)
+func isDeprecated(gvr *client.GVR) bool {
+ return deprecatedGVRs.Has(gvr) || gvr.V() == ""
}
// loadCRDs Wait for the cache to synced and then add some additional properties to CRD.
func loadCRDs(f Factory, m ResourceMetas) {
- if f.Client() == nil || !f.Client().ConnectionOK() {
+ if f == nil || f.Client() == nil || !f.Client().ConnectionOK() {
return
}
- oo, err := f.List(crdGVR, client.ClusterScope, true, labels.Everything())
+ oo, err := f.List(client.CrdGVR, client.ClusterScope, true, labels.Everything())
if err != nil {
slog.Warn("CRDs load Fail", slogs.Error, err)
return
@@ -397,125 +370,13 @@ func loadCRDs(f Factory, m ResourceMetas) {
slog.Error("CRD conversion failed", slogs.Error, err)
continue
}
- gvr, version, ok := newGVRFromCRD(&crd)
- if !ok {
- continue
- }
-
- if meta, ok := m[gvr]; ok && version.Subresources != nil && version.Subresources.Scale != nil {
- if !slices.Contains(meta.Categories, scaleCat) {
- meta.Categories = append(meta.Categories, scaleCat)
- m[gvr] = meta
+ for gvr, version := range client.NewGVRFromCRD(&crd) {
+ if meta, ok := m[gvr]; ok && version.Subresources != nil && version.Subresources.Scale != nil {
+ if !slices.Contains(meta.Categories, scaleCat) {
+ meta.Categories = append(meta.Categories, scaleCat)
+ m[gvr] = meta
+ }
}
}
}
}
-
-func newGVRFromCRD(crd *apiext.CustomResourceDefinition) (client.GVR, apiext.CustomResourceDefinitionVersion, bool) {
- for _, v := range crd.Spec.Versions {
- if v.Served && !v.Deprecated {
- return client.NewGVRFromMeta(metav1.APIResource{
- Kind: crd.Spec.Names.Kind,
- Group: crd.Spec.Group,
- Name: crd.Spec.Names.Plural,
- Version: v.Name,
- }), v, true
- }
- }
-
- return client.GVR{}, apiext.CustomResourceDefinitionVersion{}, false
-}
-
-func extractMeta(o runtime.Object) (metav1.APIResource, []error) {
- var (
- m metav1.APIResource
- errs []error
- )
-
- crd, ok := o.(*unstructured.Unstructured)
- if !ok {
- return m, append(errs, fmt.Errorf("expected unstructured, but got %T", o))
- }
-
- var spec map[string]interface{}
- spec, errs = extractMap(crd.Object, "spec", errs)
-
- var meta map[string]interface{}
- meta, errs = extractMap(crd.Object, "metadata", errs)
- m.Name, errs = extractStr(meta, "name", errs)
-
- m.Group, errs = extractStr(spec, "group", errs)
- versions, errs := extractSlice(spec, "versions", errs)
- if len(versions) > 0 {
- m.Version = versions[0]
- }
-
- var scope string
- scope, errs = extractStr(spec, "scope", errs)
-
- m.Namespaced = isNamespaced(scope)
-
- var names map[string]interface{}
- names, errs = extractMap(spec, "names", errs)
- m.Kind, errs = extractStr(names, "kind", errs)
- m.SingularName, errs = extractStr(names, "singular", errs)
- m.Name, errs = extractStr(names, "plural", errs)
- m.ShortNames, errs = extractSlice(names, "shortNames", errs)
-
- return m, errs
-}
-
-func isNamespaced(scope string) bool {
- return scope == "Namespaced"
-}
-
-func extractSlice(m map[string]interface{}, n string, errs []error) ([]string, []error) {
- if m[n] == nil {
- return nil, errs
- }
-
- s, ok := m[n].([]string)
- if ok {
- return s, errs
- }
-
- ii, ok := m[n].([]interface{})
- if !ok {
- return s, append(errs, fmt.Errorf("failed to extract slice %s -- %#v", n, m))
- }
-
- ss := make([]string, 0, len(ii))
- for _, name := range ii {
- switch o := name.(type) {
- case string:
- ss = append(ss, o)
- case map[string]interface{}:
- s, ok := o["name"].(string)
- if ok {
- ss = append(ss, s)
- } else {
- errs = append(errs, fmt.Errorf("unable to find key %q in map", n))
- }
- default:
- errs = append(errs, fmt.Errorf("unknown field type %t for key %q", o, n))
- }
- }
-
- return ss, errs
-}
-
-func extractStr(m map[string]interface{}, n string, errs []error) (string, []error) {
- s, ok := m[n].(string)
- if !ok {
- return s, append(errs, fmt.Errorf("failed to extract string %s", n))
- }
- return s, errs
-}
-
-func extractMap(m map[string]interface{}, n string, errs []error) (map[string]interface{}, []error) {
- v, ok := m[n].(map[string]interface{})
- if !ok {
- return v, append(errs, fmt.Errorf("failed to extract field %s", n))
- }
- return v, errs
-}
diff --git a/internal/dao/registry_test.go b/internal/dao/registry_test.go
index 51a05dba..8d58a44d 100644
--- a/internal/dao/registry_test.go
+++ b/internal/dao/registry_test.go
@@ -4,100 +4,78 @@
package dao
import (
- "encoding/json"
- "fmt"
- "os"
+ "errors"
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
-func TestExtractMeta(t *testing.T) {
- c := load(t, "dr")
- m, ee := extractMeta(c)
-
- assert.Equal(t, 0, len(ee))
- assert.Equal(t, "destinationrules", m.Name)
- assert.Equal(t, "destinationrule", m.SingularName)
- assert.Equal(t, "DestinationRule", m.Kind)
- assert.Equal(t, "networking.istio.io", m.Group)
- assert.Equal(t, "v1alpha3", m.Version)
- assert.Equal(t, true, m.Namespaced)
- assert.Equal(t, []string{"dr"}, m.ShortNames)
- var vv metav1.Verbs
- assert.Equal(t, vv, m.Verbs)
-}
-
-func TestExtractSlice(t *testing.T) {
+func TestMetaFor(t *testing.T) {
uu := map[string]struct {
- m map[string]interface{}
- n string
- nn []string
- ee []error
+ gvr *client.GVR
+ err error
+ e metav1.APIResource
}{
- "plain": {
- m: map[string]interface{}{"shortNames": []string{"a", "b", "c"}},
- n: "shortNames",
- nn: []string{"a", "b", "c"},
+ "xray-gvr": {
+ gvr: client.XGVR,
+ e: metav1.APIResource{
+ Name: "xrays",
+ Kind: "XRays",
+ SingularName: "xray",
+ Categories: []string{k9sCat},
+ },
},
- "empty": {
- m: map[string]interface{}{},
- n: "shortNames",
+
+ "xray": {
+ gvr: client.NewGVR("xrays"),
+ e: metav1.APIResource{
+ Name: "xrays",
+ Kind: "XRays",
+ SingularName: "xray",
+ Categories: []string{k9sCat},
+ },
+ },
+
+ "policy": {
+ gvr: client.NewGVR("policy"),
+ e: metav1.APIResource{
+ Name: "policies",
+ Kind: "Rules",
+ Namespaced: true,
+ Categories: []string{k9sCat},
+ },
+ },
+
+ "helm": {
+ gvr: client.NewGVR("helm"),
+ e: metav1.APIResource{
+ Name: "helm",
+ Kind: "Helm",
+ Namespaced: true,
+ Verbs: []string{"delete"},
+ Categories: []string{helmCat},
+ },
+ },
+
+ "toast": {
+ gvr: client.NewGVR("blah"),
+ err: errors.New("no resource meta defined for\n \"blah\""),
},
}
- var ee []error
+ m := NewMeta()
+ require.NoError(t, m.LoadResources(nil))
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- ss, e := extractSlice(u.m, u.n, ee)
- assert.Equal(t, u.ee, e)
- assert.Equal(t, u.nn, ss)
+ meta, err := m.MetaFor(u.gvr)
+ assert.Equal(t, u.err, err)
+ if err == nil {
+ assert.Equal(t, &u.e, meta)
+ }
})
}
}
-
-func TestExtractString(t *testing.T) {
- uu := map[string]struct {
- m map[string]interface{}
- n string
- s string
- ee []error
- }{
- "plain": {
- m: map[string]interface{}{"blee": "fred"},
- n: "blee",
- s: "fred",
- },
- "missing": {
- m: map[string]interface{}{},
- n: "blee",
- ee: []error{fmt.Errorf("failed to extract string blee")},
- },
- }
-
- var ee []error
- for k := range uu {
- u := uu[k]
- t.Run(k, func(t *testing.T) {
- as, ae := extractStr(u.m, u.n, ee)
- assert.Equal(t, u.ee, ae)
- assert.Equal(t, u.s, as)
- })
- }
-}
-
-// Helpers...
-
-func load(t *testing.T, n string) *unstructured.Unstructured {
- raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
-
- var o unstructured.Unstructured
- err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
-
- return &o
-}
diff --git a/internal/dao/resource.go b/internal/dao/resource.go
index d704c4b5..ea7a4695 100644
--- a/internal/dao/resource.go
+++ b/internal/dao/resource.go
@@ -33,12 +33,12 @@ func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error
}
}
- return r.getFactory().List(r.gvrStr(), ns, false, lsel)
+ return r.getFactory().List(r.gvr, ns, false, lsel)
}
// Get returns a resource instance if found, else an error.
func (r *Resource) Get(_ context.Context, path string) (runtime.Object, error) {
- return r.getFactory().Get(r.gvrStr(), path, true, labels.Everything())
+ return r.getFactory().Get(r.gvr, path, true, labels.Everything())
}
// ToYAML returns a resource yaml.
diff --git a/internal/dao/rest_mapper.go b/internal/dao/rest_mapper.go
index d9e6f8e3..0a7d60c0 100644
--- a/internal/dao/rest_mapper.go
+++ b/internal/dao/rest_mapper.go
@@ -65,11 +65,12 @@ func (r *RestMapper) resourceFor(resourceArg string) (schema.GroupVersionResourc
gvr, err = mapper.ResourceFor(gr.WithVersion(""))
if err != nil {
- if len(gr.Group) == 0 {
+ if gr.Group == "" {
return gvr, fmt.Errorf("the server doesn't have a resource type '%s'", gr.Resource)
}
return gvr, fmt.Errorf("the server doesn't have a resource type '%s' in group '%s'", gr.Resource, gr.Group)
}
+
return gvr, nil
}
diff --git a/internal/dao/rs.go b/internal/dao/rs.go
index 9e7a46e7..6bf691a1 100644
--- a/internal/dao/rs.go
+++ b/internal/dao/rs.go
@@ -31,7 +31,7 @@ type ReplicaSet struct {
}
// ListImages lists container images.
-func (r *ReplicaSet) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (r *ReplicaSet) ListImages(_ context.Context, fqn string) ([]string, error) {
rs, err := r.Load(r.Factory, fqn)
if err != nil {
return nil, err
@@ -41,8 +41,8 @@ func (r *ReplicaSet) ListImages(ctx context.Context, fqn string) ([]string, erro
}
// Load returns a given instance.
-func (r *ReplicaSet) Load(f Factory, path string) (*appsv1.ReplicaSet, error) {
- o, err := f.Get("apps/v1/replicasets", path, true, labels.Everything())
+func (*ReplicaSet) Load(f Factory, path string) (*appsv1.ReplicaSet, error) {
+ o, err := f.Get(client.RsGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -69,7 +69,7 @@ func getRSRevision(rs *appsv1.ReplicaSet) (int64, error) {
return int64(vers), nil
}
-func controllerInfo(rs *appsv1.ReplicaSet) (string, string, string, error) {
+func controllerInfo(rs *appsv1.ReplicaSet) (name, kind, group string, err error) {
for _, ref := range rs.OwnerReferences {
if ref.Controller == nil {
continue
@@ -80,6 +80,7 @@ func controllerInfo(rs *appsv1.ReplicaSet) (string, string, string, error) {
}
return ref.Name, ref.Kind, group, nil
}
+
return "", "", "", fmt.Errorf("unable to find controller for replicaset: %s", rs.Name)
}
@@ -114,7 +115,7 @@ func (r *ReplicaSet) Rollback(fqn string) error {
}
var ddp Deployment
- ddp.Init(r.Factory, client.NewGVR("apps/v1/deployments"))
+ ddp.Init(r.Factory, client.DpGVR)
dp, err := ddp.GetInstance(client.FQN(rs.Namespace, name))
if err != nil {
return err
diff --git a/internal/dao/screen_dump.go b/internal/dao/screen_dump.go
index 18b304d3..ea5013d5 100644
--- a/internal/dao/screen_dump.go
+++ b/internal/dao/screen_dump.go
@@ -25,12 +25,12 @@ type ScreenDump struct {
}
// Delete a ScreenDump.
-func (d *ScreenDump) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
+func (*ScreenDump) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
return os.Remove(path)
}
// List returns a collection of screen dumps.
-func (d *ScreenDump) List(ctx context.Context, _ string) ([]runtime.Object, error) {
+func (*ScreenDump) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyDir).(string)
if !ok {
return nil, errors.New("no screendump dir found in context")
diff --git a/internal/dao/secret.go b/internal/dao/secret.go
index 986746fb..298b804b 100644
--- a/internal/dao/secret.go
+++ b/internal/dao/secret.go
@@ -40,7 +40,7 @@ func (s *Secret) SetDecodeData(b bool) {
// Decode removes the encoded part from the secret's description and appends the
// secret's decoded data.
func (s *Secret) Decode(encodedDescription, path string) (string, error) {
- o, err := s.getFactory().Get(s.GVR(), path, true, labels.Everything())
+ o, err := s.getFactory().Get(s.gvr, path, true, labels.Everything())
if err != nil {
return "", err
}
@@ -71,7 +71,6 @@ func (s *Secret) Decode(encodedDescription, path string) (string, error) {
}
return body + "\n" + strings.Join(decodedSecrets, "\n"), nil
-
}
// ExtractSecrets takes an unstructured object and attempts to convert it into a
diff --git a/internal/dao/secret_test.go b/internal/dao/secret_test.go
index 28240210..be791697 100644
--- a/internal/dao/secret_test.go
+++ b/internal/dao/secret_test.go
@@ -9,11 +9,12 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestEncodedSecretDescribe(t *testing.T) {
var s dao.Secret
- s.Init(makeFactory(), client.NewGVR("v1/secrets"))
+ s.Init(makeFactory(), client.SecGVR)
encodedString := `
Name: bootstrap-token-abcdef
@@ -39,6 +40,6 @@ token-secret: 24 bytes`
"token-secret: 0123456789abcdef"
decodedDescription, err := s.Decode(encodedString, "kube-system/bootstrap-token-abcdef")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, expected, decodedDescription)
}
diff --git a/internal/dao/sts.go b/internal/dao/sts.go
index 153d8296..c6f7d9f0 100644
--- a/internal/dao/sts.go
+++ b/internal/dao/sts.go
@@ -20,9 +20,6 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/strategicpatch"
- "k8s.io/kubectl/pkg/polymorphichelpers"
- "k8s.io/kubectl/pkg/scheme"
)
var (
@@ -42,7 +39,7 @@ type StatefulSet struct {
}
// ListImages lists container images.
-func (s *StatefulSet) ListImages(ctx context.Context, fqn string) ([]string, error) {
+func (s *StatefulSet) ListImages(_ context.Context, fqn string) ([]string, error) {
sts, err := s.GetInstance(s.Factory, fqn)
if err != nil {
return nil, err
@@ -53,86 +50,17 @@ func (s *StatefulSet) ListImages(ctx context.Context, fqn string) ([]string, err
// Scale a StatefulSet.
func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) error {
- ns, n := client.Namespaced(path)
- auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", n, []string{client.GetVerb, client.UpdateVerb})
- if err != nil {
- return err
- }
- if !auth {
- return fmt.Errorf("user is not authorized to scale statefulsets")
- }
-
- dial, err := s.Client().Dial()
- if err != nil {
- return err
- }
- scale, err := dial.AppsV1().StatefulSets(ns).GetScale(ctx, n, metav1.GetOptions{})
- if err != nil {
- return err
- }
- scale.Spec.Replicas = replicas
- _, err = dial.AppsV1().StatefulSets(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
-
- return err
+ return scaleRes(ctx, s.getFactory(), client.StsGVR, path, replicas)
}
// Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(ctx context.Context, path string) error {
- sts, err := s.GetInstance(s.Factory, path)
- if err != nil {
- return err
- }
-
- ns, n := client.Namespaced(path)
- pp, err := podsFromSelector(s.Factory, ns, sts.Spec.Selector.MatchLabels)
- if err != nil {
- return err
- }
- for _, p := range pp {
- s.Forwarders().Kill(client.FQN(p.Namespace, p.Name))
- }
-
- auth, err := s.Client().CanI(sts.Namespace, "apps/v1/statefulsets", n, client.PatchAccess)
- if err != nil {
- return err
- }
- if !auth {
- return fmt.Errorf("user is not authorized to restart a statefulset")
- }
-
- dial, err := s.Client().Dial()
- if err != nil {
- return err
- }
-
- before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), sts)
- if err != nil {
- return err
- }
-
- after, err := polymorphichelpers.ObjectRestarterFn(sts)
- if err != nil {
- return err
- }
- diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, sts)
- if err != nil {
- return err
- }
- _, err = dial.AppsV1().StatefulSets(sts.Namespace).Patch(
- ctx,
- sts.Name,
- types.StrategicMergePatchType,
- diff,
- metav1.PatchOptions{},
- )
-
- return err
-
+ return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path)
}
// GetInstance returns a statefulset instance.
func (*StatefulSet) GetInstance(f Factory, fqn string) (*appsv1.StatefulSet, error) {
- o, err := f.Get("apps/v1/statefulsets", fqn, true, labels.Everything())
+ o, err := f.Get(client.StsGVR, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -170,7 +98,7 @@ func (s *StatefulSet) Pod(fqn string) (string, error) {
}
func (s *StatefulSet) getStatefulSet(fqn string) (*appsv1.StatefulSet, error) {
- o, err := s.getFactory().Get(s.gvrStr(), fqn, true, labels.Everything())
+ o, err := s.getFactory().Get(s.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -185,9 +113,9 @@ func (s *StatefulSet) getStatefulSet(fqn string) (*appsv1.StatefulSet, error) {
}
// ScanSA scans for serviceaccount refs.
-func (s *StatefulSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
+func (s *StatefulSet) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := s.getFactory().List(s.GVR(), ns, wait, labels.Everything())
+ oo, err := s.getFactory().List(s.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -211,9 +139,9 @@ func (s *StatefulSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs,
}
// Scan scans for cluster resource refs.
-func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) {
+func (s *StatefulSet) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
- oo, err := s.getFactory().List(s.GVR(), ns, wait, labels.Everything())
+ oo, err := s.getFactory().List(s.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
@@ -226,7 +154,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
return nil, errors.New("expecting StatefulSet resource")
}
switch gvr {
- case CmGVR:
+ case client.CmGVR:
if !hasConfigMap(&sts.Spec.Template.Spec, n) {
continue
}
@@ -234,7 +162,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: s.GVR(),
FQN: client.FQN(sts.Namespace, sts.Name),
})
- case SecGVR:
+ case client.SecGVR:
found, err := hasSecret(s.Factory, &sts.Spec.Template.Spec, sts.Namespace, n, wait)
if err != nil {
slog.Warn("Locate secret failed",
@@ -250,9 +178,9 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: s.GVR(),
FQN: client.FQN(sts.Namespace, sts.Name),
})
- case PvcGVR:
- for _, v := range sts.Spec.VolumeClaimTemplates {
- if !strings.HasPrefix(n, v.Name+"-"+sts.Name) {
+ case client.PvcGVR:
+ for i := range sts.Spec.VolumeClaimTemplates {
+ if !strings.HasPrefix(n, sts.Spec.VolumeClaimTemplates[i].Name+"-"+sts.Name) {
continue
}
refs = append(refs, Ref{
@@ -267,7 +195,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: s.GVR(),
FQN: client.FQN(sts.Namespace, sts.Name),
})
- case PcGVR:
+ case client.PcGVR:
if !hasPC(&sts.Spec.Template.Spec, n) {
continue
}
@@ -275,7 +203,6 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
GVR: s.GVR(),
FQN: client.FQN(sts.Namespace, sts.Name),
})
-
}
}
@@ -295,7 +222,7 @@ func (s *StatefulSet) GetPodSpec(path string) (*v1.PodSpec, error) {
// SetImages sets container images.
func (s *StatefulSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
- auth, err := s.Client().CanI(ns, "apps/v1/statefulset", n, client.PatchAccess)
+ auth, err := s.Client().CanI(ns, client.StsGVR, n, client.PatchAccess)
if err != nil {
return err
}
@@ -319,26 +246,3 @@ func (s *StatefulSet) SetImages(ctx context.Context, path string, imageSpecs Ima
)
return err
}
-
-func podsFromSelector(f Factory, ns string, sel map[string]string) ([]*v1.Pod, error) {
- oo, err := f.List("v1/pods", ns, true, labels.Set(sel).AsSelector())
- if err != nil {
- return nil, err
- }
-
- if len(oo) == 0 {
- return nil, fmt.Errorf("no matching pods for %v", sel)
- }
-
- pp := make([]*v1.Pod, 0, len(oo))
- for _, o := range oo {
- pod := new(v1.Pod)
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, pod)
- if err != nil {
- return nil, err
- }
- pp = append(pp, pod)
- }
-
- return pp, nil
-}
diff --git a/internal/dao/svc.go b/internal/dao/svc.go
index cee13178..64d84e2c 100644
--- a/internal/dao/svc.go
+++ b/internal/dao/svc.go
@@ -51,7 +51,7 @@ func (s *Service) Pod(fqn string) (string, error) {
// GetInstance returns a service instance.
func (s *Service) GetInstance(fqn string) (*v1.Service, error) {
- o, err := s.getFactory().Get(s.gvrStr(), fqn, true, labels.Everything())
+ o, err := s.getFactory().Get(s.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -69,7 +69,7 @@ func (s *Service) GetInstance(fqn string) (*v1.Service, error) {
// Helpers...
func podFromSelector(f Factory, ns string, sel map[string]string) (string, error) {
- oo, err := f.List("v1/pods", ns, true, labels.Set(sel).AsSelector())
+ oo, err := f.List(client.PodGVR, ns, true, labels.Set(sel).AsSelector())
if err != nil {
return "", err
}
diff --git a/internal/dao/table.go b/internal/dao/table.go
index 362ce0c2..0952dc89 100644
--- a/internal/dao/table.go
+++ b/internal/dao/table.go
@@ -6,16 +6,27 @@ package dao
import (
"context"
"fmt"
+ "log/slog"
+ "time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
+ "github.com/derailed/k9s/internal/slogs"
+ "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/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/rest"
)
-const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json"
+const (
+ gvFmt = "application/json;as=Table;v=%s;g=%s, application/json"
+ includeMeta = "Metadata"
+ includeObj = "Object"
+ includeNone = "None"
+ header = "application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json"
+)
var genScheme = runtime.NewScheme()
@@ -51,33 +62,85 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, _ := ctx.Value(internal.KeyLabels).(string)
fieldSel, _ := ctx.Value(internal.KeyFields).(string)
- f, p := t.codec()
+ includeObject := includeMeta
+ if t.includeObj {
+ includeObject = includeObj
+ }
+
+ f, _ := t.codec()
c, err := t.getClient(f)
if err != nil {
return nil, err
}
- a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName)
+ ti := time.Now()
o, err := c.Get().
- SetHeader("Accept", a).
+ SetHeader("Accept", header).
+ Param("includeObject", includeObject).
Namespace(ns).
Resource(t.gvr.R()).
VersionedParams(&metav1.ListOptions{
- LabelSelector: labelSel,
- FieldSelector: fieldSel,
- ResourceVersion: "0",
- ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
- }, p).
+ LabelSelector: labelSel,
+ FieldSelector: fieldSel,
+ }, metav1.ParameterCodec).
Do(ctx).Get()
if err != nil {
return nil, err
}
+ slog.Debug("Q Time", slogs.Elapsed, time.Since(ti))
- return []runtime.Object{o}, nil
+ namespaced := true
+ if res, e := MetaAccess.MetaFor(t.gvr); e == nil && !res.Namespaced {
+ namespaced = false
+ }
+ ta, err := decodeTable(ctx, o.(*metav1.Table), namespaced)
+ if err != nil {
+ return nil, err
+ }
+
+ return []runtime.Object{ta}, nil
}
// ----------------------------------------------------------------------------
// Helpers...
+func decodeTable(ctx context.Context, table *metav1.Table, namespaced bool) (runtime.Object, error) {
+ if namespaced {
+ table.ColumnDefinitions = append([]metav1.TableColumnDefinition{{Name: "Namespace", Type: "string"}}, table.ColumnDefinitions...)
+ }
+ pool := internal.NewWorkerPool(ctx, internal.DefaultPoolSize)
+ for i := range table.Rows {
+ pool.Add(func(_ context.Context) error {
+ row := &table.Rows[i]
+ if row.Object.Raw == nil || row.Object.Object != nil {
+ return nil
+ }
+ converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw)
+ if err != nil {
+ return err
+ }
+ row.Object.Object = converted
+ var m metav1.Object
+ if obj := row.Object.Object; obj != nil {
+ m, _ = meta.Accessor(obj)
+ }
+ var ns string
+ if m != nil {
+ ns = m.GetNamespace()
+ }
+ if namespaced {
+ row.Cells = append([]any{ns}, row.Cells...)
+ }
+ return nil
+ })
+ }
+ errs := pool.Drain()
+ if len(errs) > 0 {
+ return nil, fmt.Errorf("failed to decode table rows: %w", errs[0])
+ }
+
+ return table, nil
+}
+
func (t *Table) getClient(f serializer.CodecFactory) (*rest.RESTClient, error) {
cfg, err := t.Client().RestConfig()
if err != nil {
@@ -90,7 +153,6 @@ func (t *Table) getClient(f serializer.CodecFactory) (*rest.RESTClient, error) {
cfg.APIPath = "/api"
}
cfg.NegotiatedSerializer = f.WithoutConversion()
-
crRestClient, err := rest.RESTClientFor(cfg)
if err != nil {
return nil, err
diff --git a/internal/dao/types.go b/internal/dao/types.go
index fa7d833b..08f1d906 100644
--- a/internal/dao/types.go
+++ b/internal/dao/types.go
@@ -18,28 +18,22 @@ import (
restclient "k8s.io/client-go/rest"
)
-// ResourceMetas represents a collection of resource metadata.
-type ResourceMetas map[client.GVR]metav1.APIResource
-
-// Accessors represents a collection of dao accessors.
-type Accessors map[client.GVR]Accessor
-
// Factory represents a resource factory.
type Factory interface {
// Client retrieves an api client.
Client() client.Connection
// Get fetch a given resource.
- Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error)
+ Get(gvr *client.GVR, path string, wait bool, sel labels.Selector) (runtime.Object, error)
// List fetch a collection of resources.
- List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error)
+ List(gvr *client.GVR, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error)
// ForResource fetch an informer for a given resource.
- ForResource(ns, gvr string) (informers.GenericInformer, error)
+ ForResource(ns string, gvr *client.GVR) (informers.GenericInformer, error)
// CanForResource fetch an informer for a given resource if authorized
- CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error)
+ CanForResource(ns string, gvr *client.GVR, verbs []string) (informers.GenericInformer, error)
// WaitForCacheSync synchronize the cache.
WaitForCacheSync()
@@ -75,7 +69,7 @@ type Accessor interface {
Getter
// Init the resource with a factory object.
- Init(Factory, client.GVR)
+ Init(Factory, *client.GVR)
// GVR returns a gvr a string.
GVR() string
diff --git a/internal/dao/utils_test.go b/internal/dao/utils_test.go
index 1984f63a..e98db88b 100644
--- a/internal/dao/utils_test.go
+++ b/internal/dao/utils_test.go
@@ -20,14 +20,14 @@ import (
)
type testFactory struct {
- inventory map[string]map[string][]runtime.Object
+ inventory map[string]map[*client.GVR][]runtime.Object
}
func makeFactory() dao.Factory {
return &testFactory{
- inventory: map[string]map[string][]runtime.Object{
+ inventory: map[string]map[*client.GVR][]runtime.Object{
"kube-system": {
- "v1/secrets": {
+ client.SecGVR: {
load("secret"),
},
},
@@ -37,10 +37,10 @@ func makeFactory() dao.Factory {
var _ dao.Factory = &testFactory{}
-func (f *testFactory) Client() client.Connection {
+func (*testFactory) Client() client.Connection {
return nil
}
-func (f *testFactory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (f *testFactory) Get(gvr *client.GVR, fqn string, _ bool, _ labels.Selector) (runtime.Object, error) {
ns, po := path.Split(fqn)
ns = strings.Trim(ns, "/")
@@ -52,21 +52,21 @@ func (f *testFactory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runt
return nil, nil
}
-func (f *testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (f *testFactory) List(gvr *client.GVR, ns string, _ bool, _ labels.Selector) ([]runtime.Object, error) {
return f.inventory[ns][gvr], nil
}
-func (f *testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (*testFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-func (f *testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (*testFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
-func (f *testFactory) WaitForCacheSync() {}
-func (f *testFactory) Forwarders() watch.Forwarders {
+func (*testFactory) WaitForCacheSync() {}
+func (*testFactory) Forwarders() watch.Forwarders {
return nil
}
-func (f *testFactory) DeleteForwarder(string) {}
+func (*testFactory) DeleteForwarder(string) {}
func load(n string) *unstructured.Unstructured {
raw, _ := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
diff --git a/internal/dao/workload.go b/internal/dao/workload.go
index 9d91aa87..37086c0d 100644
--- a/internal/dao/workload.go
+++ b/internal/dao/workload.go
@@ -26,20 +26,14 @@ const (
DegradedStatus = "DEGRADED"
)
-var (
- SaGVR = client.NewGVR("v1/serviceaccounts")
- PvcGVR = client.NewGVR("v1/persistentvolumeclaims")
- PcGVR = client.NewGVR("scheduling.k8s.io/v1/priorityclasses")
- CmGVR = client.NewGVR("v1/configmaps")
- SecGVR = client.NewGVR("v1/secrets")
- PodGVR = client.NewGVR("v1/pods")
- SvcGVR = client.NewGVR("v1/services")
- DsGVR = client.NewGVR("apps/v1/daemonsets")
- StsGVR = client.NewGVR("apps/v1/statefulSets")
- DpGVR = client.NewGVR("apps/v1/deployments")
- RsGVR = client.NewGVR("apps/v1/replicasets")
- resList = []client.GVR{PodGVR, SvcGVR, DsGVR, StsGVR, DpGVR, RsGVR}
-)
+var resList = []*client.GVR{
+ client.PodGVR,
+ client.SvcGVR,
+ client.DsGVR,
+ client.StsGVR,
+ client.DpGVR,
+ client.RsGVR,
+}
// Workload tracks a select set of resources in a given namespace.
type Workload struct {
@@ -47,9 +41,9 @@ type Workload struct {
}
func (w *Workload) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
- gvr, _ := ctx.Value(internal.KeyGVR).(client.GVR)
+ gvr, _ := ctx.Value(internal.KeyGVR).(*client.GVR)
ns, n := client.Namespaced(path)
- auth, err := w.Client().CanI(ns, gvr.String(), n, []string{client.DeleteVerb})
+ auth, err := w.Client().CanI(ns, gvr, n, []string{client.DeleteVerb})
if err != nil {
return err
}
@@ -81,7 +75,7 @@ func (w *Workload) Delete(ctx context.Context, path string, propagation *metav1.
return dial.Namespace(ns).Delete(ctx, n, opts)
}
-func (a *Workload) fetch(ctx context.Context, gvr client.GVR, ns string) (*metav1.Table, error) {
+func (a *Workload) fetch(ctx context.Context, gvr *client.GVR, ns string) (*metav1.Table, error) {
a.gvr = gvr
oo, err := a.Table.List(ctx, ns)
if err != nil {
@@ -121,13 +115,13 @@ func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error
ns, ts = m.GetNamespace(), m.CreationTimestamp
}
}
- stat := status(gvr, r, table.ColumnDefinitions)
- oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []interface{}{
+ stat := status(gvr, &r, table.ColumnDefinitions)
+ oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []any{
gvr.String(),
ns,
r.Cells[indexOf("Name", table.ColumnDefinitions)],
stat,
- readiness(gvr, r, table.ColumnDefinitions),
+ readiness(gvr, &r, table.ColumnDefinitions),
validity(stat),
ts,
}}})
@@ -139,34 +133,34 @@ func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error
// Helpers...
-func readiness(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string {
+func readiness(gvr *client.GVR, r *metav1.TableRow, h []metav1.TableColumnDefinition) string {
switch gvr {
- case PodGVR, DpGVR, StsGVR:
+ case client.PodGVR, client.DpGVR, client.StsGVR:
return r.Cells[indexOf("Ready", h)].(string)
- case RsGVR, DsGVR:
+ case client.RsGVR, client.DsGVR:
c := r.Cells[indexOf("Ready", h)].(int64)
d := r.Cells[indexOf("Desired", h)].(int64)
return fmt.Sprintf("%d/%d", c, d)
- case SvcGVR:
+ case client.SvcGVR:
return ""
}
return render.NAValue
}
-func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string {
+func status(gvr *client.GVR, r *metav1.TableRow, h []metav1.TableColumnDefinition) string {
switch gvr {
- case PodGVR:
+ case client.PodGVR:
if status := r.Cells[indexOf("Status", h)]; status == render.PhaseCompleted {
return StatusOK
} else if !isReady(r.Cells[indexOf("Ready", h)].(string)) || status != render.PhaseRunning {
return DegradedStatus
}
- case DpGVR, StsGVR:
+ case client.DpGVR, client.StsGVR:
if !isReady(r.Cells[indexOf("Ready", h)].(string)) {
return DegradedStatus
}
- case RsGVR, DsGVR:
+ case client.RsGVR, client.DsGVR:
rd, ok1 := r.Cells[indexOf("Ready", h)].(int64)
de, ok2 := r.Cells[indexOf("Desired", h)].(int64)
if ok1 && ok2 {
@@ -182,7 +176,7 @@ func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition)
return DegradedStatus
}
}
- case SvcGVR:
+ case client.SvcGVR:
default:
return render.MissingValue
}
diff --git a/internal/health/check.go b/internal/health/check.go
index 745c6e2a..052db7ba 100644
--- a/internal/health/check.go
+++ b/internal/health/check.go
@@ -4,6 +4,7 @@
package health
import (
+ "github.com/derailed/k9s/internal/client"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -12,14 +13,14 @@ import (
type Check struct {
Counts
- GVR string
+ GVR *client.GVR
}
// Checks represents a collection of health checks.
type Checks []*Check
// NewCheck returns a new health check.
-func NewCheck(gvr string) *Check {
+func NewCheck(gvr *client.GVR) *Check {
return &Check{
GVR: gvr,
Counts: make(Counts),
diff --git a/internal/health/check_test.go b/internal/health/check_test.go
index 1b6f1126..3f688c6f 100644
--- a/internal/health/check_test.go
+++ b/internal/health/check_test.go
@@ -6,6 +6,7 @@ package health_test
import (
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/health"
"github.com/stretchr/testify/assert"
)
@@ -13,16 +14,16 @@ import (
func TestCheck(t *testing.T) {
var cc health.Checks
- c := health.NewCheck("test")
+ c := health.NewCheck(client.NewGVR("test"))
n := 0
- for i := 0; i < 10; i++ {
+ for range 10 {
c.Inc(health.S1)
cc = append(cc, c)
n++
}
c.Total(int64(n))
- assert.Equal(t, 10, len(cc))
+ assert.Len(t, cc, 10)
assert.Equal(t, int64(10), c.Tally(health.Corpus))
assert.Equal(t, int64(10), c.Tally(health.S1))
assert.Equal(t, int64(0), c.Tally(health.S2))
diff --git a/internal/helpers.go b/internal/helpers.go
index aa1d43e5..66fe9f01 100644
--- a/internal/helpers.go
+++ b/internal/helpers.go
@@ -11,9 +11,8 @@ import (
)
var (
- inverseRx = regexp.MustCompile(`\A\!`)
- fuzzyRx = regexp.MustCompile(`\A-f\s?([\w-]+)\b`)
- labelRx = regexp.MustCompile(`\A\-l`)
+ fuzzyRx = regexp.MustCompile(`\A-f\s?([\w-]+)\b`)
+ labelRx = regexp.MustCompile(`\A\-l`)
)
// Helpers...
@@ -23,7 +22,7 @@ func IsInverseSelector(s string) bool {
if s == "" {
return false
}
- return inverseRx.MatchString(s)
+ return s[0] == '!'
}
// IsLabelSelector checks if query is a label query.
diff --git a/internal/model/cluster_info.go b/internal/model/cluster_info.go
index 689197a2..bc2c7180 100644
--- a/internal/model/cluster_info.go
+++ b/internal/model/cluster_info.go
@@ -30,10 +30,10 @@ const (
// ClusterInfoListener registers a listener for model changes.
type ClusterInfoListener interface {
// ClusterInfoChanged notifies the cluster meta was changed.
- ClusterInfoChanged(prev, curr ClusterMeta)
+ ClusterInfoChanged(prev, curr *ClusterMeta)
// ClusterInfoUpdated notifies the cluster meta was updated.
- ClusterInfoUpdated(ClusterMeta)
+ ClusterInfoUpdated(*ClusterMeta)
}
// ClusterMeta represents cluster meta data.
@@ -46,8 +46,8 @@ type ClusterMeta struct {
}
// NewClusterMeta returns a new instance.
-func NewClusterMeta() ClusterMeta {
- return ClusterMeta{
+func NewClusterMeta() *ClusterMeta {
+ return &ClusterMeta{
Context: client.NA,
Cluster: client.NA,
User: client.NA,
@@ -60,7 +60,7 @@ func NewClusterMeta() ClusterMeta {
}
// Deltas diffs cluster meta return true if different, false otherwise.
-func (c ClusterMeta) Deltas(n ClusterMeta) bool {
+func (c *ClusterMeta) Deltas(n *ClusterMeta) bool {
if c.Cpu != n.Cpu || c.Mem != n.Mem || c.Ephemeral != n.Ephemeral {
return true
}
@@ -77,7 +77,7 @@ func (c ClusterMeta) Deltas(n ClusterMeta) bool {
type ClusterInfo struct {
cluster *Cluster
factory dao.Factory
- data ClusterMeta
+ data *ClusterMeta
version string
cfg *config.K9s
listeners []ClusterInfoListener
@@ -122,9 +122,7 @@ func (c *ClusterInfo) Reset(f dao.Factory) {
}
c.mx.Lock()
- {
- c.cluster, c.data = NewCluster(f), NewClusterMeta()
- }
+ c.cluster, c.data = NewCluster(f), NewClusterMeta()
c.mx.Unlock()
c.Refresh()
@@ -165,9 +163,7 @@ func (c *ClusterInfo) Refresh() {
c.fireNoMetaChanged(data)
}
c.mx.Lock()
- {
- c.data = data
- }
+ c.data = data
c.mx.Unlock()
}
@@ -191,13 +187,13 @@ func (c *ClusterInfo) RemoveListener(l ClusterInfoListener) {
}
}
-func (c *ClusterInfo) fireMetaChanged(prev, cur ClusterMeta) {
+func (c *ClusterInfo) fireMetaChanged(prev, cur *ClusterMeta) {
for _, l := range c.listeners {
l.ClusterInfoChanged(prev, cur)
}
}
-func (c *ClusterInfo) fireNoMetaChanged(data ClusterMeta) {
+func (c *ClusterInfo) fireNoMetaChanged(data *ClusterMeta) {
for _, l := range c.listeners {
l.ClusterInfoUpdated(data)
}
@@ -210,7 +206,7 @@ func fetchLatestRev() (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, k9sGitURL, nil)
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, k9sGitURL, http.NoBody)
if err != nil {
return "", err
}
@@ -228,7 +224,7 @@ func fetchLatestRev() (string, error) {
if err != nil {
return "", err
}
- m := make(map[string]interface{}, 20)
+ m := make(map[string]any, 20)
if err := json.Unmarshal(b, &m); err != nil {
return "", err
}
diff --git a/internal/model/cluster_info_test.go b/internal/model/cluster_info_test.go
index ca0c637e..95c320d0 100644
--- a/internal/model/cluster_info_test.go
+++ b/internal/model/cluster_info_test.go
@@ -17,7 +17,7 @@ func init() {
func TestClusterMetaDelta(t *testing.T) {
uu := map[string]struct {
- o, n model.ClusterMeta
+ o, n *model.ClusterMeta
e bool
}{
"empty": {
@@ -45,7 +45,7 @@ func TestClusterMetaDelta(t *testing.T) {
// Helpers...
-func makeClusterMeta(cluster string) model.ClusterMeta {
+func makeClusterMeta(cluster string) *model.ClusterMeta {
m := model.NewClusterMeta()
m.Cluster = cluster
m.Cpu, m.Mem = 10, 20
diff --git a/internal/model/cmd_buff.go b/internal/model/cmd_buff.go
index f62ca799..315526bb 100644
--- a/internal/model/cmd_buff.go
+++ b/internal/model/cmd_buff.go
@@ -82,9 +82,7 @@ func (c *CmdBuff) IsActive() bool {
// SetActive toggles cmd buffer active state.
func (c *CmdBuff) SetActive(b bool) {
c.mx.Lock()
- {
- c.active = b
- }
+ c.active = b
c.mx.Unlock()
c.fireActive(c.active)
@@ -123,26 +121,20 @@ func (c *CmdBuff) hasCancel() bool {
func (c *CmdBuff) setCancel(f context.CancelFunc) {
c.mx.Lock()
- {
- c.cancel = f
- }
+ c.cancel = f
c.mx.Unlock()
}
func (c *CmdBuff) resetCancel() {
c.mx.Lock()
- {
- c.cancel = nil
- }
+ c.cancel = nil
c.mx.Unlock()
}
// SetText initializes the buffer with a command.
func (c *CmdBuff) SetText(text, suggestion string) {
c.mx.Lock()
- {
- c.buff, c.suggestion = []rune(text), suggestion
- }
+ c.buff, c.suggestion = []rune(text), suggestion
c.mx.Unlock()
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
}
@@ -150,9 +142,7 @@ func (c *CmdBuff) SetText(text, suggestion string) {
// Add adds a new character to the buffer.
func (c *CmdBuff) Add(r rune) {
c.mx.Lock()
- {
- c.buff = append(c.buff, r)
- }
+ c.buff = append(c.buff, r)
c.mx.Unlock()
c.fireBufferChanged(c.GetText(), c.GetSuggestion())
if c.hasCancel() {
@@ -192,9 +182,7 @@ func (c *CmdBuff) Delete() {
// ClearText clears out command buffer.
func (c *CmdBuff) ClearText(fire bool) {
c.mx.Lock()
- {
- c.buff, c.suggestion = c.buff[:0], ""
- }
+ c.buff, c.suggestion = c.buff[:0], ""
c.mx.Unlock()
if fire {
@@ -223,9 +211,7 @@ func (c *CmdBuff) Empty() bool {
// AddListener registers a cmd buffer listener.
func (c *CmdBuff) AddListener(w BuffWatcher) {
c.mx.Lock()
- {
- c.listeners[w] = struct{}{}
- }
+ c.listeners[w] = struct{}{}
c.mx.Unlock()
}
diff --git a/internal/model/cmd_buff_test.go b/internal/model/cmd_buff_test.go
index 924470ad..00606425 100644
--- a/internal/model/cmd_buff_test.go
+++ b/internal/model/cmd_buff_test.go
@@ -65,22 +65,22 @@ func TestCmdBuffChanged(t *testing.T) {
b.Delete()
assert.Equal(t, 0, l.act)
assert.Equal(t, 0, l.inact)
- assert.Equal(t, "", l.text)
- assert.Equal(t, "", b.GetText())
+ assert.Empty(t, l.text)
+ assert.Empty(t, b.GetText())
b.Add('c')
b.ClearText(true)
assert.Equal(t, 0, l.act)
assert.Equal(t, 0, l.inact)
- assert.Equal(t, "", l.text)
- assert.Equal(t, "", b.GetText())
+ assert.Empty(t, l.text)
+ assert.Empty(t, b.GetText())
b.Add('c')
b.Reset()
assert.Equal(t, 0, l.act)
assert.Equal(t, 1, l.inact)
- assert.Equal(t, "", l.text)
- assert.Equal(t, "", b.GetText())
+ assert.Empty(t, l.text)
+ assert.Empty(t, b.GetText())
assert.True(t, b.Empty())
}
diff --git a/internal/model/describe.go b/internal/model/describe.go
index aab755b0..20edb19c 100644
--- a/internal/model/describe.go
+++ b/internal/model/describe.go
@@ -22,7 +22,7 @@ import (
// Describe tracks describable resources.
type Describe struct {
- gvr client.GVR
+ gvr *client.GVR
inUpdate int32
path string
query string
@@ -33,7 +33,7 @@ type Describe struct {
}
// NewDescribe returns a new describe resource model.
-func NewDescribe(gvr client.GVR, path string) *Describe {
+func NewDescribe(gvr *client.GVR, path string) *Describe {
return &Describe{
gvr: gvr,
path: path,
@@ -42,7 +42,7 @@ func NewDescribe(gvr client.GVR, path string) *Describe {
}
// GVR returns the resource gvr.
-func (d *Describe) GVR() client.GVR {
+func (d *Describe) GVR() *client.GVR {
return d.gvr
}
@@ -52,7 +52,7 @@ func (d *Describe) GetPath() string {
}
// SetOptions toggle model options.
-func (d *Describe) SetOptions(context.Context, ViewerToggleOpts) {}
+func (*Describe) SetOptions(context.Context, ViewerToggleOpts) {}
// Filter filters the model.
func (d *Describe) Filter(q string) {
@@ -91,7 +91,7 @@ func (d *Describe) fireResourceFailed(err error) {
}
// ClearFilter clear out the filter.
-func (d *Describe) ClearFilter() {
+func (*Describe) ClearFilter() {
}
// Peek returns current model state.
@@ -172,11 +172,7 @@ func (d *Describe) reconcile(ctx context.Context) error {
}
// Describe describes a given resource.
-func (d *Describe) describe(ctx context.Context, gvr client.GVR, path string) (string, error) {
- defer func(t time.Time) {
- slog.Debug("Describe model elapsed", slogs.Elapsed, time.Since(t))
- }(time.Now())
-
+func (d *Describe) describe(ctx context.Context, gvr *client.GVR, path string) (string, error) {
meta, err := getMeta(ctx, gvr)
if err != nil {
return "", err
diff --git a/internal/model/fish_buff.go b/internal/model/fish_buff.go
index 1bfb0ae0..ff2233cd 100644
--- a/internal/model/fish_buff.go
+++ b/internal/model/fish_buff.go
@@ -89,7 +89,7 @@ func (f *FishBuff) CurrentSuggestion() (string, bool) {
}
// AutoSuggests returns true if model implements auto suggestions.
-func (f *FishBuff) AutoSuggests() bool {
+func (*FishBuff) AutoSuggests() bool {
return true
}
@@ -107,7 +107,7 @@ func (f *FishBuff) SetSuggestionFn(fn SuggestionFunc) {
}
// Notify publish suggestions to all listeners.
-func (f *FishBuff) Notify(delete bool) {
+func (f *FishBuff) Notify(_ bool) {
if f.suggestionFn == nil {
return
}
diff --git a/internal/model/fish_buff_test.go b/internal/model/fish_buff_test.go
index ae8abe3c..b51ff49f 100644
--- a/internal/model/fish_buff_test.go
+++ b/internal/model/fish_buff_test.go
@@ -16,7 +16,7 @@ func TestFishAdd(t *testing.T) {
f := model.NewFishBuff(' ', model.FilterBuffer)
f.AddListener(&m)
- f.SetSuggestionFn(func(text string) sort.StringSlice {
+ f.SetSuggestionFn(func(string) sort.StringSlice {
return sort.StringSlice{"blee", "brew"}
})
f.Add('b')
@@ -45,7 +45,7 @@ func TestFishDelete(t *testing.T) {
f := model.NewFishBuff(' ', model.FilterBuffer)
f.AddListener(&m)
- f.SetSuggestionFn(func(text string) sort.StringSlice {
+ f.SetSuggestionFn(func(string) sort.StringSlice {
return sort.StringSlice{"blee", "duh"}
})
f.Add('a')
@@ -89,11 +89,11 @@ func (m *mockSuggestionListener) BufferCompleted(text, suggest string) {
m.text, m.suggestion = text, suggest
}
-func (m *mockSuggestionListener) BufferActive(state bool, kind model.BufferKind) {
+func (m *mockSuggestionListener) BufferActive(state bool, _ model.BufferKind) {
m.active = state
}
-func (m *mockSuggestionListener) SuggestionChanged(text, sugg string) {
+func (m *mockSuggestionListener) SuggestionChanged(_, sugg string) {
m.suggestion = sugg
m.suggCount++
}
diff --git a/internal/model/flash.go b/internal/model/flash.go
index 727af7f2..25150b00 100644
--- a/internal/model/flash.go
+++ b/internal/model/flash.go
@@ -81,7 +81,7 @@ func (f *Flash) Info(msg string) {
}
// Infof displays a formatted info flash message.
-func (f *Flash) Infof(fmat string, args ...interface{}) {
+func (f *Flash) Infof(fmat string, args ...any) {
f.Info(fmt.Sprintf(fmat, args...))
}
@@ -92,7 +92,7 @@ func (f *Flash) Warn(msg string) {
}
// Warnf displays a formatted warning flash message.
-func (f *Flash) Warnf(fmat string, args ...interface{}) {
+func (f *Flash) Warnf(fmat string, args ...any) {
f.Warn(fmt.Sprintf(fmat, args...))
}
@@ -103,11 +103,10 @@ func (f *Flash) Err(err error) {
}
// Errf displays a formatted error flash message.
-func (f *Flash) Errf(fmat string, args ...interface{}) {
+func (f *Flash) Errf(fmat string, args ...any) {
var err error
for _, a := range args {
- switch e := a.(type) {
- case error:
+ if e, ok := a.(error); ok {
err = e
}
}
diff --git a/internal/model/flash_test.go b/internal/model/flash_test.go
index 2484fdef..d2f5a96d 100644
--- a/internal/model/flash_test.go
+++ b/internal/model/flash_test.go
@@ -80,7 +80,7 @@ func newFlash() *flash {
return &flash{}
}
-func (f *flash) getMetrics() (int, model.FlashLevel, string) {
+func (f *flash) getMetrics() (val int, lvl model.FlashLevel, msg string) {
return f.set, f.level, f.msg
}
diff --git a/internal/model/helpers.go b/internal/model/helpers.go
index 3d7efbcf..a2354eb6 100644
--- a/internal/model/helpers.go
+++ b/internal/model/helpers.go
@@ -18,7 +18,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
-func getMeta(ctx context.Context, gvr client.GVR) (ResourceMeta, error) {
+func getMeta(ctx context.Context, gvr *client.GVR) (ResourceMeta, error) {
meta := resourceMeta(gvr)
factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
if !ok {
@@ -29,11 +29,11 @@ func getMeta(ctx context.Context, gvr client.GVR) (ResourceMeta, error) {
return meta, nil
}
-func resourceMeta(gvr client.GVR) ResourceMeta {
+func resourceMeta(gvr *client.GVR) ResourceMeta {
meta, ok := Registry[gvr.String()]
if !ok {
meta = ResourceMeta{
- DAO: new(dao.Dynamic),
+ DAO: new(dao.Table),
Renderer: new(render.Table),
}
}
@@ -45,7 +45,7 @@ func resourceMeta(gvr client.GVR) ResourceMeta {
}
// MetaFQN returns a fully qualified resource name.
-func MetaFQN(m metav1.ObjectMeta) string {
+func MetaFQN(m *metav1.ObjectMeta) string {
return FQN(m.Namespace, m.Name)
}
@@ -58,9 +58,9 @@ func FQN(ns, n string) string {
}
// NewExpBackOff returns a new exponential backoff timer.
-func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOffContext {
+func NewExpBackOff(ctx context.Context, start, maxVal time.Duration) backoff.BackOffContext {
bf := backoff.NewExponentialBackOff()
- bf.InitialInterval, bf.MaxElapsedTime = start, max
+ bf.InitialInterval, bf.MaxElapsedTime = start, maxVal
return backoff.WithContext(bf, ctx)
}
diff --git a/internal/model/helpers_test.go b/internal/model/helpers_test.go
index 69ed2529..59226b5f 100644
--- a/internal/model/helpers_test.go
+++ b/internal/model/helpers_test.go
@@ -29,7 +29,7 @@ func TestMetaFQN(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, model.MetaFQN(u.meta))
+ assert.Equal(t, u.e, model.MetaFQN(&u.meta))
})
}
}
diff --git a/internal/model/hint_test.go b/internal/model/hint_test.go
index 383b2c50..f0de29ed 100644
--- a/internal/model/hint_test.go
+++ b/internal/model/hint_test.go
@@ -37,7 +37,7 @@ func TestHint(t *testing.T) {
h.SetHints(u.hh)
assert.Equal(t, u.e, l.count)
- assert.Equal(t, u.e, len(h.Peek()))
+ assert.Len(t, h.Peek(), u.e)
})
}
}
diff --git a/internal/model/history.go b/internal/model/history.go
index 37c4c5de..4d6ce114 100644
--- a/internal/model/history.go
+++ b/internal/model/history.go
@@ -48,7 +48,7 @@ func (h *History) Back() bool {
}
h.previousCommandIndex = h.activeCommandIndex
- h.activeCommandIndex = h.activeCommandIndex - 1
+ h.activeCommandIndex--
return true
}
@@ -64,7 +64,7 @@ func (h *History) Forward() bool {
}
h.previousCommandIndex = h.activeCommandIndex
- h.activeCommandIndex = h.activeCommandIndex + 1
+ h.activeCommandIndex++
return true
}
diff --git a/internal/model/log.go b/internal/model/log.go
index 19495847..c387aa3b 100644
--- a/internal/model/log.go
+++ b/internal/model/log.go
@@ -44,7 +44,7 @@ type Log struct {
factory dao.Factory
lines *dao.LogItems
listeners []LogsListener
- gvr client.GVR
+ gvr *client.GVR
logOptions *dao.LogOptions
cancelFn context.CancelFunc
mx sync.RWMutex
@@ -54,7 +54,7 @@ type Log struct {
}
// NewLog returns a new model.
-func NewLog(gvr client.GVR, opts *dao.LogOptions, flushTimeout time.Duration) *Log {
+func NewLog(gvr *client.GVR, opts *dao.LogOptions, flushTimeout time.Duration) *Log {
return &Log{
gvr: gvr,
logOptions: opts,
@@ -63,7 +63,7 @@ func NewLog(gvr client.GVR, opts *dao.LogOptions, flushTimeout time.Duration) *L
}
}
-func (l *Log) GVR() client.GVR {
+func (l *Log) GVR() *client.GVR {
return l.gvr
}
@@ -95,9 +95,7 @@ func (l *Log) ToggleShowTimestamp(b bool) {
func (l *Log) Head(ctx context.Context) {
l.mx.Lock()
- {
- l.logOptions.Head = true
- }
+ l.logOptions.Head = true
l.mx.Unlock()
l.Restart(ctx)
}
@@ -110,7 +108,7 @@ func (l *Log) SetSinceSeconds(ctx context.Context, i int64) {
// Configure sets logger configuration.
func (l *Log) Configure(opts config.Logger) {
- l.logOptions.Lines = int64(opts.TailCount)
+ l.logOptions.Lines = opts.TailCount
l.logOptions.SinceSeconds = opts.SinceSeconds
}
@@ -137,10 +135,8 @@ func (l *Log) Init(f dao.Factory) {
// Clear the logs.
func (l *Log) Clear() {
l.mx.Lock()
- {
- l.lines.Clear()
- l.lastSent = 0
- }
+ l.lines.Clear()
+ l.lastSent = 0
l.mx.Unlock()
l.fireLogCleared()
@@ -178,9 +174,7 @@ func (l *Log) Stop() {
// Set sets the log lines (for testing only!)
func (l *Log) Set(lines *dao.LogItems) {
l.mx.Lock()
- {
- l.lines.Merge(lines)
- }
+ l.lines.Merge(lines)
l.mx.Unlock()
l.fireLogCleared()
@@ -192,9 +186,7 @@ func (l *Log) Set(lines *dao.LogItems) {
// ClearFilter resets the log filter if any.
func (l *Log) ClearFilter() {
l.mx.Lock()
- {
- l.filter = ""
- }
+ l.filter = ""
l.mx.Unlock()
l.fireLogCleared()
@@ -206,9 +198,7 @@ func (l *Log) ClearFilter() {
// Filter filters the model using either fuzzy or regexp.
func (l *Log) Filter(q string) {
l.mx.Lock()
- {
- l.filter = q
- }
+ l.filter = q
l.mx.Unlock()
l.fireLogCleared()
@@ -303,9 +293,7 @@ func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) {
l.Append(item)
var overflow bool
l.mx.RLock()
- {
- overflow = int64(l.lines.Len()-l.lastSent) > l.logOptions.Lines
- }
+ overflow = int64(l.lines.Len()-l.lastSent) > l.logOptions.Lines
l.mx.RUnlock()
if overflow {
l.Notify()
@@ -418,9 +406,7 @@ func (l *Log) fireLogChanged(lines [][]byte) {
func (l *Log) fireLogCleared() {
var ll []LogsListener
l.mx.RLock()
- {
- ll = l.listeners
- }
+ ll = l.listeners
l.mx.RUnlock()
for _, lis := range ll {
lis.LogCleared()
diff --git a/internal/model/log_int_test.go b/internal/model/log_int_test.go
index 57dd9520..2cdf5b86 100644
--- a/internal/model/log_int_test.go
+++ b/internal/model/log_int_test.go
@@ -27,10 +27,9 @@ func TestUpdateLogs(t *testing.T) {
defer cancel()
go m.updateLogs(ctx, c)
- for i := 0; i < 2*size; i++ {
+ for i := range 2 * size {
c <- dao.NewLogItemFromString("line" + strconv.Itoa(i))
}
-
time.Sleep(2 * time.Second)
assert.Equal(t, size, v.count)
}
@@ -51,7 +50,7 @@ func BenchmarkUpdateLogs(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
c <- item
}
close(c)
@@ -78,8 +77,8 @@ func newMockLogView() *mockLogView {
func (t *mockLogView) LogChanged(ll [][]byte) {
t.count += len(ll)
}
-func (t *mockLogView) LogStop() {}
-func (t *mockLogView) LogCanceled() {}
-func (t *mockLogView) LogResume() {}
-func (t *mockLogView) LogCleared() {}
-func (t *mockLogView) LogFailed(err error) {}
+func (*mockLogView) LogStop() {}
+func (*mockLogView) LogCanceled() {}
+func (*mockLogView) LogResume() {}
+func (*mockLogView) LogCleared() {}
+func (*mockLogView) LogFailed(error) {}
diff --git a/internal/model/log_test.go b/internal/model/log_test.go
index b3fd501c..6296d7b8 100644
--- a/internal/model/log_test.go
+++ b/internal/model/log_test.go
@@ -29,7 +29,7 @@ func TestLogFullBuffer(t *testing.T) {
m.AddListener(v)
data := dao.NewLogItems()
- for i := 0; i < 2*size; i++ {
+ for i := range 2 * size {
data.Add(dao.NewLogItemFromString("line" + strconv.Itoa(i)))
m.Append(data.Items()[i])
}
@@ -75,7 +75,7 @@ func TestLogFilter(t *testing.T) {
m.Filter(u.q)
data := dao.NewLogItems()
- for i := 0; i < size; i++ {
+ for i := range size {
data.Add(dao.NewLogItemFromString(fmt.Sprintf("pod-line-%d", i+1)))
m.Append(data.Items()[i])
}
@@ -84,13 +84,13 @@ func TestLogFilter(t *testing.T) {
assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 1, v.clearCalled)
assert.Equal(t, 0, v.errCalled)
- assert.Equal(t, u.e, len(v.data))
+ assert.Len(t, v.data, u.e)
m.ClearFilter()
assert.Equal(t, 2, v.dataCalled)
assert.Equal(t, 2, v.clearCalled)
assert.Equal(t, 0, v.errCalled)
- assert.Equal(t, size, len(v.data))
+ assert.Len(t, v.data, size)
})
}
}
@@ -116,7 +116,7 @@ func TestLogStartStop(t *testing.T) {
assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 0, v.clearCalled)
assert.Equal(t, 1, v.errCalled)
- assert.Equal(t, 2, len(v.data))
+ assert.Len(t, v.data, 2)
}
func TestLogClear(t *testing.T) {
@@ -139,7 +139,7 @@ func TestLogClear(t *testing.T) {
assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 1, v.clearCalled)
assert.Equal(t, 0, v.errCalled)
- assert.Equal(t, 0, len(v.data))
+ assert.Empty(t, v.data)
}
func TestLogBasic(t *testing.T) {
@@ -191,7 +191,6 @@ func TestLogAppend(t *testing.T) {
assert.Equal(t, 2, v.dataCalled)
assert.Equal(t, 1, v.clearCalled)
assert.Equal(t, 0, v.errCalled)
- // assert.Equal(t, append(items, data...).Lines(false), v.data)
}
func TestLogTimedout(t *testing.T) {
@@ -230,7 +229,7 @@ func TestToggleAllContainers(t *testing.T) {
defer cancel()
m.ToggleAllContainers(ctx)
- assert.Equal(t, "", m.GetContainer())
+ assert.Empty(t, m.GetContainer())
m.ToggleAllContainers(ctx)
assert.Equal(t, "blee", m.GetContainer())
}
@@ -259,9 +258,9 @@ func newTestView() *testView {
return &testView{}
}
-func (t *testView) LogCanceled() {}
-func (t *testView) LogStop() {}
-func (t *testView) LogResume() {}
+func (*testView) LogCanceled() {}
+func (*testView) LogStop() {}
+func (*testView) LogResume() {}
func (t *testView) LogChanged(ll [][]byte) {
t.data = ll
@@ -284,30 +283,26 @@ type testFactory struct{}
var _ dao.Factory = testFactory{}
-func (f testFactory) Client() client.Connection {
+func (testFactory) Client() client.Connection {
return nil
}
-
-func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (testFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
return nil, nil
}
-
-func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (testFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
-
-func (f testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (testFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-
-func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (testFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
-func (f testFactory) WaitForCacheSync() {}
-func (f testFactory) Forwarders() watch.Forwarders {
+func (testFactory) WaitForCacheSync() {}
+func (testFactory) Forwarders() watch.Forwarders {
return nil
}
-func (f testFactory) DeleteForwarder(string) {}
+func (testFactory) DeleteForwarder(string) {}
func makeFactory() dao.Factory {
return testFactory{}
diff --git a/internal/model/menu_hint.go b/internal/model/menu_hint.go
index 2ac0ffc9..bd6c3aff 100644
--- a/internal/model/menu_hint.go
+++ b/internal/model/menu_hint.go
@@ -5,7 +5,6 @@ package model
import (
"strconv"
- "strings"
)
// MenuHint represents keyboard mnemonic.
@@ -51,5 +50,5 @@ func (h MenuHints) Less(i, j int) bool {
if err1 != nil && err2 == nil {
return false
}
- return strings.Compare(h[i].Description, h[j].Description) < 0
+ return h[i].Description < h[j].Description
}
diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go
index 8e2027e8..2929d329 100644
--- a/internal/model/pulse_health.go
+++ b/internal/model/pulse_health.go
@@ -32,15 +32,15 @@ func NewPulseHealth(f dao.Factory) *PulseHealth {
// List returns a canned collection of resources health.
func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) {
- gvrs := []string{
- "v1/pods",
- "v1/events",
- "apps/v1/replicasets",
- "apps/v1/deployments",
- "apps/v1/statefulsets",
- "apps/v1/daemonsets",
- "batch/v1/jobs",
- "v1/persistentvolumes",
+ gvrs := []*client.GVR{
+ client.PodGVR,
+ client.EvGVR,
+ client.RsGVR,
+ client.DpGVR,
+ client.StsGVR,
+ client.DsGVR,
+ client.CjGVR,
+ client.PcGVR,
}
hh := make([]runtime.Object, 0, 10)
@@ -89,11 +89,11 @@ func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) {
tcpu += m.TotalCPU
tmem += m.TotalMEM
}
- c1 := health.NewCheck("cpu")
+ c1 := health.NewCheck(client.CpuGVR)
c1.Set(health.S1, ccpu)
c1.Set(health.S2, acpu)
c1.Set(health.S3, tcpu)
- c2 := health.NewCheck("mem")
+ c2 := health.NewCheck(client.MemGVR)
c2.Set(health.S1, cmem)
c2.Set(health.S2, amem)
c2.Set(health.S3, tmem)
@@ -101,19 +101,19 @@ func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) {
return health.Checks{c1, c2}, nil
}
-func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, error) {
- meta, ok := Registry[gvr]
+func (h *PulseHealth) check(ctx context.Context, ns string, gvr *client.GVR) (*health.Check, error) {
+ meta, ok := Registry[gvr.String()]
if !ok {
meta = ResourceMeta{
- DAO: &dao.Table{},
- Renderer: &render.Table{},
+ DAO: new(dao.Table),
+ Renderer: new(render.Table),
}
}
if meta.DAO == nil {
meta.DAO = &dao.Resource{}
}
- meta.DAO.Init(h.factory, client.NewGVR(gvr))
+ meta.DAO.Init(h.factory, gvr)
oo, err := meta.DAO.List(ctx, ns)
if err != nil {
return nil, err
diff --git a/internal/model/registry.go b/internal/model/registry.go
index 6495ad17..d9bb7682 100644
--- a/internal/model/registry.go
+++ b/internal/model/registry.go
@@ -4,6 +4,7 @@
package model
import (
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/render/helm"
@@ -13,180 +14,180 @@ import (
// Registry tracks resources metadata.
// BOZO!! Break up deps and merge into single registrar.
var Registry = map[string]ResourceMeta{
- "workloads": {
- DAO: &dao.Workload{},
- Renderer: &render.Workload{},
- },
// Custom...
- "references": {
- DAO: &dao.Reference{},
- Renderer: &render.Reference{},
+ client.WkGVR.String(): {
+ DAO: new(dao.Workload),
+ Renderer: new(render.Workload),
},
- "dir": {
- DAO: &dao.Dir{},
- Renderer: &render.Dir{},
+ client.RefGVR.String(): {
+ DAO: new(dao.Reference),
+ Renderer: new(render.Reference),
},
- "pulses": {
- DAO: &dao.Pulse{},
+ client.DirGVR.String(): {
+ DAO: new(dao.Dir),
+ Renderer: new(render.Dir),
},
- "helm": {
- DAO: &dao.HelmChart{},
- Renderer: &helm.Chart{},
+ client.PuGVR.String(): {
+ DAO: new(dao.Pulse),
},
- "helm-history": {
- DAO: &dao.HelmHistory{},
- Renderer: &helm.History{},
+ client.HmGVR.String(): {
+ DAO: new(dao.HelmChart),
+ Renderer: new(helm.Chart),
},
- "containers": {
- DAO: &dao.Container{},
- Renderer: &render.Container{},
- TreeRenderer: &xray.Container{},
+ client.HmhGVR.String(): {
+ DAO: new(dao.HelmHistory),
+ Renderer: new(helm.History),
},
- "scans": {
- DAO: &dao.ImageScan{},
- Renderer: &render.ImageScan{},
+ client.CoGVR.String(): {
+ DAO: new(dao.Container),
+ Renderer: new(render.Container),
+ TreeRenderer: new(xray.Container),
},
- "contexts": {
- DAO: &dao.Context{},
- Renderer: &render.Context{},
+ client.ScGVR.String(): {
+ DAO: new(dao.ImageScan),
+ Renderer: new(render.ImageScan),
},
- "screendumps": {
- DAO: &dao.ScreenDump{},
- Renderer: &render.ScreenDump{},
+ client.CtGVR.String(): {
+ DAO: new(dao.Context),
+ Renderer: new(render.Context),
},
- "rbac": {
- DAO: &dao.Rbac{},
- Renderer: &render.Rbac{},
+ client.SdGVR.String(): {
+ DAO: new(dao.ScreenDump),
+ Renderer: new(render.ScreenDump),
},
- "policy": {
- DAO: &dao.Policy{},
- Renderer: &render.Policy{},
+ client.RbacGVR.String(): {
+ DAO: new(dao.Rbac),
+ Renderer: new(render.Rbac),
},
- "users": {
- DAO: &dao.Subject{},
- Renderer: &render.Subject{},
+ client.PolGVR.String(): {
+ DAO: new(dao.Policy),
+ Renderer: new(render.Policy),
},
- "groups": {
- DAO: &dao.Subject{},
- Renderer: &render.Subject{},
+ client.UsrGVR.String(): {
+ DAO: new(dao.Subject),
+ Renderer: new(render.Subject),
},
- "portforwards": {
- DAO: &dao.PortForward{},
- Renderer: &render.PortForward{},
+ client.GrpGVR.String(): {
+ DAO: new(dao.Subject),
+ Renderer: new(render.Subject),
},
- "benchmarks": {
- DAO: &dao.Benchmark{},
- Renderer: &render.Benchmark{},
+ client.PfGVR.String(): {
+ DAO: new(dao.PortForward),
+ Renderer: new(render.PortForward),
},
- "aliases": {
- DAO: &dao.Alias{},
- Renderer: &render.Alias{},
+ client.BeGVR.String(): {
+ DAO: new(dao.Benchmark),
+ Renderer: new(render.Benchmark),
+ },
+ client.AliGVR.String(): {
+ DAO: new(dao.Alias),
+ Renderer: new(render.Alias),
},
// Core...
- "v1/endpoints": {
- Renderer: &render.Endpoints{},
+ client.EpGVR.String(): {
+ Renderer: new(render.Endpoints),
},
- "v1/pods": {
- DAO: &dao.Pod{},
+ client.PodGVR.String(): {
+ DAO: new(dao.Pod),
Renderer: render.NewPod(),
- TreeRenderer: &xray.Pod{},
+ TreeRenderer: new(xray.Pod),
},
- "v1/namespaces": {
- DAO: &dao.Namespace{},
- Renderer: &render.Namespace{},
+ client.NsGVR.String(): {
+ DAO: new(dao.Namespace),
+ Renderer: new(render.Namespace),
},
- "v1/secrets": {
- DAO: &dao.Secret{},
- Renderer: &render.Secret{},
+ client.SecGVR.String(): {
+ DAO: new(dao.Secret),
+ Renderer: new(render.Secret),
},
- "v1/configmaps": {
- DAO: &dao.ConfigMap{},
- Renderer: &render.ConfigMap{},
+ client.CmGVR.String(): {
+ DAO: new(dao.ConfigMap),
+ Renderer: new(render.ConfigMap),
},
- "v1/nodes": {
- DAO: &dao.Node{},
- Renderer: &render.Node{},
+ client.NodeGVR.String(): {
+ DAO: new(dao.Node),
+ Renderer: new(render.Node),
},
- "v1/services": {
- DAO: &dao.Service{},
- Renderer: &render.Service{},
- TreeRenderer: &xray.Service{},
+ client.SvcGVR.String(): {
+ DAO: new(dao.Service),
+ Renderer: new(render.Service),
+ TreeRenderer: new(xray.Service),
},
- "v1/serviceaccounts": {
- Renderer: &render.ServiceAccount{},
+ client.SaGVR.String(): {
+ Renderer: new(render.ServiceAccount),
},
- "v1/persistentvolumes": {
- Renderer: &render.PersistentVolume{},
+ client.PvGVR.String(): {
+ Renderer: new(render.PersistentVolume),
},
- "v1/persistentvolumeclaims": {
- Renderer: &render.PersistentVolumeClaim{},
+ client.PvcGVR.String(): {
+ Renderer: new(render.PersistentVolumeClaim),
},
// Apps...
- "apps/v1/deployments": {
- DAO: &dao.Deployment{},
- Renderer: &render.Deployment{},
- TreeRenderer: &xray.Deployment{},
+ client.DpGVR.String(): {
+ DAO: new(dao.Deployment),
+ Renderer: new(render.Deployment),
+ TreeRenderer: new(xray.Deployment),
},
- "apps/v1/replicasets": {
- Renderer: &render.ReplicaSet{},
- TreeRenderer: &xray.ReplicaSet{},
+ client.RsGVR.String(): {
+ Renderer: new(render.ReplicaSet),
+ TreeRenderer: new(xray.ReplicaSet),
},
- "apps/v1/statefulsets": {
- DAO: &dao.StatefulSet{},
- Renderer: &render.StatefulSet{},
- TreeRenderer: &xray.StatefulSet{},
+ client.StsGVR.String(): {
+ DAO: new(dao.StatefulSet),
+ Renderer: new(render.StatefulSet),
+ TreeRenderer: new(xray.StatefulSet),
},
- "apps/v1/daemonsets": {
- DAO: &dao.DaemonSet{},
- Renderer: &render.DaemonSet{},
- TreeRenderer: &xray.DaemonSet{},
+ client.DsGVR.String(): {
+ DAO: new(dao.DaemonSet),
+ Renderer: new(render.DaemonSet),
+ TreeRenderer: new(xray.DaemonSet),
},
// Extensions...
- "networking.k8s.io/v1/networkpolicies": {
+ client.NpGVR.String(): {
Renderer: &render.NetworkPolicy{},
},
// Batch...
- "batch/v1/cronjobs": {
- DAO: &dao.CronJob{},
- Renderer: &render.CronJob{},
+ client.CjGVR.String(): {
+ DAO: new(dao.CronJob),
+ Renderer: new(render.CronJob),
},
- "batch/v1/jobs": {
- DAO: &dao.Job{},
- Renderer: &render.Job{},
+ client.JobGVR.String(): {
+ DAO: new(dao.Job),
+ Renderer: new(render.Job),
},
// CRDs...
- "apiextensions.k8s.io/v1/customresourcedefinitions": {
- DAO: &dao.CustomResourceDefinition{},
- Renderer: &render.CustomResourceDefinition{},
+ client.CrdGVR.String(): {
+ DAO: new(dao.CustomResourceDefinition),
+ Renderer: new(render.CustomResourceDefinition),
},
// Storage...
- "storage.k8s.io/v1/storageclasses": {
+ client.ScGVR.String(): {
Renderer: &render.StorageClass{},
},
// Policy...
- "policy/v1/poddisruptionbudgets": {
+ client.PdbGVR.String(): {
Renderer: &render.PodDisruptionBudget{},
},
// RBAC...
- "rbac.authorization.k8s.io/v1/clusterroles": {
- DAO: &dao.Rbac{},
- Renderer: &render.ClusterRole{},
+ client.CrGVR.String(): {
+ DAO: new(dao.Rbac),
+ Renderer: new(render.ClusterRole),
},
- "rbac.authorization.k8s.io/v1/clusterrolebindings": {
- Renderer: &render.ClusterRoleBinding{},
+ client.CrbGVR.String(): {
+ Renderer: new(render.ClusterRoleBinding),
},
- "rbac.authorization.k8s.io/v1/roles": {
- Renderer: &render.Role{},
+ client.RoGVR.String(): {
+ Renderer: new(render.Role),
},
- "rbac.authorization.k8s.io/v1/rolebindings": {
- Renderer: &render.RoleBinding{},
+ client.RobGVR.String(): {
+ Renderer: new(render.RoleBinding),
},
}
diff --git a/internal/model/rev_values.go b/internal/model/rev_values.go
index 059fa0ef..e41bd599 100644
--- a/internal/model/rev_values.go
+++ b/internal/model/rev_values.go
@@ -5,6 +5,7 @@ package model
import (
"context"
+ "errors"
"log/slog"
"strings"
"sync/atomic"
@@ -20,7 +21,7 @@ import (
// RevValues tracks Helm values representations.
type RevValues struct {
- gvr client.GVR
+ gvr *client.GVR
inUpdate int32
path string
rev string
@@ -32,7 +33,7 @@ type RevValues struct {
}
// NewRevValues return a new Helm values resource model.
-func NewRevValues(gvr client.GVR, path, rev string) *RevValues {
+func NewRevValues(gvr *client.GVR, path, rev string) *RevValues {
return &RevValues{
gvr: gvr,
path: path,
@@ -43,10 +44,10 @@ func NewRevValues(gvr client.GVR, path, rev string) *RevValues {
}
func getHelmHistDao() *dao.HelmHistory {
- return Registry["helm-history"].DAO.(*dao.HelmHistory)
+ return Registry[client.HmhGVR.String()].DAO.(*dao.HelmHistory)
}
-func getRevValues(path, rev string) []string {
+func getRevValues(path, _ string) []string {
vals, err := getHelmHistDao().GetValues(path, true)
if err != nil {
slog.Error("Failed to get Helm values", slogs.Error, err)
@@ -55,7 +56,7 @@ func getRevValues(path, rev string) []string {
}
// GVR returns the resource gvr.
-func (v *RevValues) GVR() client.GVR {
+func (v *RevValues) GVR() *client.GVR {
return v.gvr
}
@@ -157,24 +158,20 @@ func (v *RevValues) updater(ctx context.Context) {
}
}
-func (v *RevValues) refresh(ctx context.Context) error {
+func (v *RevValues) refresh(context.Context) error {
if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) {
slog.Debug("Dropping update...")
- return nil
+ return errors.New("refresh in progress, dropping")
}
defer atomic.StoreInt32(&v.inUpdate, 0)
- if err := v.reconcile(ctx); err != nil {
- return err
- }
+ v.reconcile()
return nil
}
-func (v *RevValues) reconcile(_ context.Context) error {
+func (v *RevValues) reconcile() {
v.fireResourceChanged(v.lines, v.filter(v.query, v.lines))
-
- return nil
}
// AddListener adds a new model listener.
diff --git a/internal/model/stack.go b/internal/model/stack.go
index d3d8446c..b48862ce 100644
--- a/internal/model/stack.go
+++ b/internal/model/stack.go
@@ -97,9 +97,7 @@ func (s *Stack) Push(c Component) {
}
s.mx.Lock()
- {
- s.components = append(s.components, c)
- }
+ s.components = append(s.components, c)
s.mx.Unlock()
s.notify(StackPush, c)
}
@@ -112,12 +110,11 @@ func (s *Stack) Pop() (Component, bool) {
var c Component
s.mx.Lock()
- {
- c = s.components[len(s.components)-1]
- c.Stop()
- s.components = s.components[:len(s.components)-1]
- }
+ c = s.components[len(s.components)-1]
+ c.Stop()
+ s.components = s.components[:len(s.components)-1]
s.mx.Unlock()
+
s.notify(StackPop, c)
return c, true
diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go
index 4f4f73e3..15a21d85 100644
--- a/internal/model/stack_test.go
+++ b/internal/model/stack_test.go
@@ -76,7 +76,7 @@ func TestStackPrevious(t *testing.T) {
for _, c := range u.items {
s.Push(c)
}
- for i := 0; i < u.pops; i++ {
+ for range u.pops {
s.Pop()
}
assert.Equal(t, u.e, s.Previous())
@@ -111,7 +111,7 @@ func TestStackIsLast(t *testing.T) {
for _, c := range u.items {
s.Push(c)
}
- for i := 0; i < u.pops; i++ {
+ for range u.pops {
s.Pop()
}
assert.Equal(t, u.e, s.IsLast())
@@ -142,7 +142,7 @@ func TestStackFlatten(t *testing.T) {
s.Push(c)
}
assert.Equal(t, u.e, s.Flatten())
- assert.Equal(t, len(u.e), len(s.Peek()))
+ assert.Len(t, s.Peek(), len(u.e))
})
}
}
@@ -180,7 +180,7 @@ func TestStackPush(t *testing.T) {
for _, c := range u.items {
s.Push(c)
}
- for i := 0; i < u.pop; i++ {
+ for range u.pop {
s.Pop()
}
assert.Equal(t, u.e, s.Empty())
@@ -279,9 +279,10 @@ func (s *stackL) StackPushed(model.Component) {
s.count++
}
-func (s *stackL) StackPopped(c, top model.Component) {
+func (s *stackL) StackPopped(_, _ model.Component) {
s.count--
}
+
func (s *stackL) StackTop(model.Component) { s.tops++ }
type c struct {
@@ -303,13 +304,13 @@ func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return ni
func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
return nil
}
-func (c c) SetRect(int, int, int, int) {}
-func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 }
-func (c c) GetFocusable() tview.Focusable { return nil }
-func (c c) Focus(func(tview.Primitive)) {}
-func (c c) Blur() {}
-func (c c) Start() {}
-func (c c) Stop() {}
-func (c c) Init(context.Context) error { return nil }
-func (c c) SetFilter(string) {}
-func (c c) SetLabelFilter(map[string]string) {}
+func (c) SetRect(int, int, int, int) {}
+func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 }
+func (c) GetFocusable() tview.Focusable { return nil }
+func (c) Focus(func(tview.Primitive)) {}
+func (c) Blur() {}
+func (c) Start() {}
+func (c) Stop() {}
+func (c) Init(context.Context) error { return nil }
+func (c) SetFilter(string) {}
+func (c) SetLabelFilter(map[string]string) {}
diff --git a/internal/model/table.go b/internal/model/table.go
index 2c0c8dc3..06a9827a 100644
--- a/internal/model/table.go
+++ b/internal/model/table.go
@@ -35,7 +35,7 @@ type TableListener interface {
// Table represents a table model.
type Table struct {
- gvr client.GVR
+ gvr *client.GVR
data *model1.TableData
listeners []TableListener
inUpdate int32
@@ -47,7 +47,7 @@ type Table struct {
}
// NewTable returns a new table model.
-func NewTable(gvr client.GVR) *Table {
+func NewTable(gvr *client.GVR) *Table {
return &Table{
gvr: gvr,
data: model1.NewTableData(gvr),
@@ -57,9 +57,7 @@ func NewTable(gvr client.GVR) *Table {
func (t *Table) SetViewSetting(ctx context.Context, vs *config.ViewSetting) {
t.mx.Lock()
- {
- t.vs = vs
- }
+ t.vs = vs
t.mx.Unlock()
if ctx != context.Background() {
@@ -264,7 +262,9 @@ func (t *Table) reconcile(ctx context.Context) error {
err error
)
meta := resourceMeta(t.gvr)
- meta.DAO.SetIncludeObject(true)
+ if t.vs != nil {
+ meta.DAO.SetIncludeObject(true)
+ }
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter)
if t.instance == "" {
oo, err = t.list(ctx, meta.DAO)
@@ -278,7 +278,7 @@ func (t *Table) reconcile(ctx context.Context) error {
r := meta.Renderer
r.SetViewSetting(t.vs)
- return t.data.Reconcile(ctx, meta.Renderer, oo)
+ return t.data.Render(ctx, meta.Renderer, oo)
}
func (t *Table) fireTableChanged(data *model1.TableData) {
diff --git a/internal/model/table_int_test.go b/internal/model/table_int_test.go
index ee522a49..19677f5f 100644
--- a/internal/model/table_int_test.go
+++ b/internal/model/table_int_test.go
@@ -17,6 +17,7 @@ import (
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/watch"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@@ -24,7 +25,7 @@ import (
)
func TestTableReconcile(t *testing.T) {
- ta := NewTable(client.NewGVR("v1/pods"))
+ ta := NewTable(client.PodGVR)
ta.SetNamespace(client.NamespaceAll)
f := makeFactory()
@@ -33,7 +34,7 @@ func TestTableReconcile(t *testing.T) {
ctx = context.WithValue(ctx, internal.KeyFields, "")
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
err := ta.reconcile(ctx)
- assert.Nil(t, err)
+ require.NoError(t, err)
data := ta.Peek()
assert.Equal(t, 25, data.HeaderCount())
assert.Equal(t, 1, data.RowCount())
@@ -41,18 +42,18 @@ func TestTableReconcile(t *testing.T) {
}
func TestTableList(t *testing.T) {
- ta := NewTable(client.NewGVR("v1/pods"))
+ ta := NewTable(client.PodGVR)
ta.SetNamespace("blee")
acc := accessor{}
ctx := context.WithValue(context.Background(), internal.KeyFactory, makeFactory())
rows, err := ta.list(ctx, &acc)
- assert.Nil(t, err)
- assert.Equal(t, 1, len(rows))
+ require.NoError(t, err)
+ assert.Len(t, rows, 1)
}
func TestTableGet(t *testing.T) {
- ta := NewTable(client.NewGVR("v1/pods"))
+ ta := NewTable(client.PodGVR)
ta.SetNamespace("blee")
f := makeFactory()
@@ -60,24 +61,24 @@ func TestTableGet(t *testing.T) {
ctx := context.WithValue(context.Background(), internal.KeyFactory, f)
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
row, err := ta.Get(ctx, "fred")
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.NotNil(t, row)
- assert.Equal(t, 5, len(row.(*render.PodWithMetrics).Raw.Object))
+ assert.Len(t, row.(*render.PodWithMetrics).Raw.Object, 5)
}
func TestTableMeta(t *testing.T) {
uu := map[string]struct {
- gvr string
+ gvr *client.GVR
accessor dao.Accessor
renderer model1.Renderer
}{
"generic": {
- gvr: "containers",
+ gvr: client.CoGVR,
accessor: &dao.Container{},
renderer: &render.Container{},
},
"node": {
- gvr: "v1/nodes",
+ gvr: client.NodeGVR,
accessor: &dao.Node{},
renderer: &render.Node{},
},
@@ -85,7 +86,7 @@ func TestTableMeta(t *testing.T) {
for k := range uu {
u := uu[k]
- ta := NewTable(client.NewGVR(u.gvr))
+ ta := NewTable(u.gvr)
m := resourceMeta(ta.gvr)
assert.Equal(t, u.accessor, m.DAO)
@@ -110,10 +111,10 @@ func mustLoad(n string) *unstructured.Unstructured {
func load(t *testing.T, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
+ require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
+ require.NoError(t, err)
return &o
}
@@ -129,56 +130,56 @@ type testFactory struct {
var _ dao.Factory = testFactory{}
-func (f testFactory) Client() client.Connection {
+func (testFactory) Client() client.Connection {
return client.NewTestAPIClient()
}
-func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (f testFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
if len(f.rows) > 0 {
return f.rows[0], nil
}
return nil, nil
}
-func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (f testFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
if len(f.rows) > 0 {
return f.rows, nil
}
return nil, nil
}
-func (f testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (testFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (testFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
-func (f testFactory) WaitForCacheSync() {}
-func (f testFactory) Forwarders() watch.Forwarders {
+func (testFactory) WaitForCacheSync() {}
+func (testFactory) Forwarders() watch.Forwarders {
return nil
}
-func (f testFactory) DeleteForwarder(string) {}
+func (testFactory) DeleteForwarder(string) {}
// ----------------------------------------------------------------------------
type accessor struct {
- gvr client.GVR
+ gvr *client.GVR
}
var _ dao.Accessor = (*accessor)(nil)
-func (a *accessor) SetIncludeObject(bool) {}
+func (*accessor) SetIncludeObject(bool) {}
-func (a *accessor) List(ctx context.Context, ns string) ([]runtime.Object, error) {
+func (*accessor) List(context.Context, string) ([]runtime.Object, error) {
return []runtime.Object{&render.PodWithMetrics{Raw: mustLoad("p1")}}, nil
}
-func (a *accessor) Get(ctx context.Context, path string) (runtime.Object, error) {
+func (*accessor) Get(context.Context, string) (runtime.Object, error) {
return &render.PodWithMetrics{Raw: mustLoad("p1")}, nil
}
-func (a *accessor) Init(_ dao.Factory, gvr client.GVR) {
+func (a *accessor) Init(_ dao.Factory, gvr *client.GVR) {
a.gvr = gvr
}
diff --git a/internal/model/table_test.go b/internal/model/table_test.go
index e2df5c09..a877c5b8 100644
--- a/internal/model/table_test.go
+++ b/internal/model/table_test.go
@@ -17,6 +17,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/watch"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@@ -24,7 +25,7 @@ import (
)
func TestTableRefresh(t *testing.T) {
- ta := model.NewTable(client.NewGVR("v1/pods"))
+ ta := model.NewTable(client.PodGVR)
ta.SetNamespace(client.NamespaceAll)
l := tableListener{}
@@ -34,7 +35,7 @@ func TestTableRefresh(t *testing.T) {
ctx := context.WithValue(context.Background(), internal.KeyFactory, f)
ctx = context.WithValue(ctx, internal.KeyFields, "")
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
- assert.NoError(t, ta.Refresh(ctx))
+ require.NoError(t, ta.Refresh(ctx))
data := ta.Peek()
assert.Equal(t, 25, data.HeaderCount())
assert.Equal(t, 1, data.RowCount())
@@ -44,7 +45,7 @@ func TestTableRefresh(t *testing.T) {
}
func TestTableNS(t *testing.T) {
- ta := model.NewTable(client.NewGVR("v1/pods"))
+ ta := model.NewTable(client.PodGVR)
ta.SetNamespace("blee")
assert.Equal(t, "blee", ta.GetNamespace())
@@ -53,7 +54,7 @@ func TestTableNS(t *testing.T) {
}
func TestTableAddListener(t *testing.T) {
- ta := model.NewTable(client.NewGVR("v1/pods"))
+ ta := model.NewTable(client.PodGVR)
ta.SetNamespace("blee")
assert.True(t, ta.Empty())
@@ -61,8 +62,8 @@ func TestTableAddListener(t *testing.T) {
ta.AddListener(&l)
}
-func TestTableRmListener(t *testing.T) {
- ta := model.NewTable(client.NewGVR("v1/pods"))
+func TestTableRmListener(*testing.T) {
+ ta := model.NewTable(client.PodGVR)
ta.SetNamespace("blee")
l := tableListener{}
@@ -89,36 +90,36 @@ type tableFactory struct {
var _ dao.Factory = tableFactory{}
-func (f tableFactory) Client() client.Connection {
+func (tableFactory) Client() client.Connection {
return client.NewTestAPIClient()
}
-func (f tableFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (f tableFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
if len(f.rows) > 0 {
return f.rows[0], nil
}
return nil, nil
}
-func (f tableFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (f tableFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
if len(f.rows) > 0 {
return f.rows, nil
}
return nil, nil
}
-func (f tableFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (tableFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-func (f tableFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (tableFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
-func (f tableFactory) WaitForCacheSync() {}
-func (f tableFactory) Forwarders() watch.Forwarders {
+func (tableFactory) WaitForCacheSync() {}
+func (tableFactory) Forwarders() watch.Forwarders {
return nil
}
-func (f tableFactory) DeleteForwarder(string) {}
+func (tableFactory) DeleteForwarder(string) {}
func makeTableFactory() tableFactory {
return tableFactory{}
diff --git a/internal/model/text_test.go b/internal/model/text_test.go
index ffa390cd..65084ea4 100644
--- a/internal/model/text_test.go
+++ b/internal/model/text_test.go
@@ -84,7 +84,7 @@ func (l *textLis) TextChanged(ll []string) {
l.changed++
}
-func (l *textLis) TextFiltered(ll []string, mm fuzzy.Matches) {
+func (l *textLis) TextFiltered(_ []string, mm fuzzy.Matches) {
l.matches = len(mm)
l.filtered++
if len(mm) > 0 && len(mm[0].MatchedIndexes) > 0 {
diff --git a/internal/model/tree.go b/internal/model/tree.go
index f97b5852..c31b60ca 100644
--- a/internal/model/tree.go
+++ b/internal/model/tree.go
@@ -35,7 +35,7 @@ type TreeListener interface {
// Tree represents a tree model.
type Tree struct {
- gvr client.GVR
+ gvr *client.GVR
namespace string
root *xray.TreeNode
listeners []TreeListener
@@ -45,7 +45,7 @@ type Tree struct {
}
// NewTree returns a new model.
-func NewTree(gvr client.GVR) *Tree {
+func NewTree(gvr *client.GVR) *Tree {
return &Tree{
gvr: gvr,
refreshRate: 2 * time.Second,
@@ -133,7 +133,7 @@ func (t *Tree) Peek() *xray.TreeNode {
}
// Describe describes a given resource.
-func (t *Tree) Describe(ctx context.Context, gvr, path string) (string, error) {
+func (t *Tree) Describe(ctx context.Context, gvr *client.GVR, path string) (string, error) {
meta, err := t.getMeta(ctx, gvr)
if err != nil {
return "", err
@@ -148,7 +148,7 @@ func (t *Tree) Describe(ctx context.Context, gvr, path string) (string, error) {
}
// ToYAML returns a resource yaml.
-func (t *Tree) ToYAML(ctx context.Context, gvr, path string) (string, error) {
+func (t *Tree) ToYAML(ctx context.Context, gvr *client.GVR, path string) (string, error) {
meta, err := t.getMeta(ctx, gvr)
if err != nil {
return "", err
@@ -210,8 +210,7 @@ func (t *Tree) reconcile(ctx context.Context) error {
}
ns := client.CleanseNamespace(t.namespace)
- res := t.gvr.R()
- root := xray.NewTreeNode(res, res)
+ root := xray.NewTreeNode(t.gvr, t.gvr.R())
ctx = context.WithValue(ctx, xray.KeyParent, root)
if _, ok := meta.TreeRenderer.(*xray.Generic); ok {
table, ok := oo[0].(*metav1.Table)
@@ -264,13 +263,13 @@ func (t *Tree) fireTreeLoadFailed(err error) {
}
}
-func (t *Tree) getMeta(ctx context.Context, gvr string) (ResourceMeta, error) {
+func (t *Tree) getMeta(ctx context.Context, gvr *client.GVR) (ResourceMeta, error) {
meta := t.resourceMeta()
factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
if !ok {
return ResourceMeta{}, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory))
}
- meta.DAO.Init(factory, client.NewGVR(gvr))
+ meta.DAO.Init(factory, gvr)
return meta, nil
}
@@ -294,10 +293,15 @@ func treeHydrate(ctx context.Context, ns string, oo []runtime.Object, re TreeRen
if re == nil {
return fmt.Errorf("no tree renderer defined for this resource")
}
+ pool := internal.NewWorkerPool(ctx, internal.DefaultPoolSize)
for _, o := range oo {
- if err := re.Render(ctx, ns, o); err != nil {
- return err
- }
+ pool.Add(func(_ context.Context) error {
+ return re.Render(ctx, ns, o)
+ })
+ }
+ errs := pool.Drain()
+ if len(errs) > 0 {
+ return errs[0]
}
return nil
diff --git a/internal/model/types.go b/internal/model/types.go
index d175a0c2..9d7d9225 100644
--- a/internal/model/types.go
+++ b/internal/model/types.go
@@ -34,7 +34,7 @@ type ViewerToggleOpts map[string]bool
type ResourceViewer interface {
GetPath() string
Filter(string)
- GVR() client.GVR
+ GVR() *client.GVR
ClearFilter()
Peek() []string
SetOptions(context.Context, ViewerToggleOpts)
@@ -136,7 +136,7 @@ type Describer interface {
// TreeRenderer represents an xray node.
type TreeRenderer interface {
- Render(ctx context.Context, ns string, o interface{}) error
+ Render(ctx context.Context, ns string, o any) error
}
// ResourceMeta represents model info about a resource.
diff --git a/internal/model/values.go b/internal/model/values.go
index 54efcc79..40b2ea35 100644
--- a/internal/model/values.go
+++ b/internal/model/values.go
@@ -22,7 +22,7 @@ import (
// Values tracks Helm values representations.
type Values struct {
factory dao.Factory
- gvr client.GVR
+ gvr *client.GVR
inUpdate int32
path string
query string
@@ -33,7 +33,7 @@ type Values struct {
}
// NewValues return a new Helm values resource model.
-func NewValues(gvr client.GVR, path string) *Values {
+func NewValues(gvr *client.GVR, path string) *Values {
return &Values{
gvr: gvr,
path: path,
@@ -71,7 +71,7 @@ func (v *Values) getValues() ([]string, error) {
}
// GVR returns the resource gvr.
-func (v *Values) GVR() client.GVR {
+func (v *Values) GVR() *client.GVR {
return v.gvr
}
@@ -186,24 +186,20 @@ func (v *Values) updater(ctx context.Context) {
}
}
-func (v *Values) refresh(ctx context.Context) error {
+func (v *Values) refresh(context.Context) error {
if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) {
slog.Debug("Dropping update...")
- return nil
+ return fmt.Errorf("reconcile in progress. Dropping update")
}
defer atomic.StoreInt32(&v.inUpdate, 0)
- if err := v.reconcile(ctx); err != nil {
- return err
- }
+ v.reconcile()
return nil
}
-func (v *Values) reconcile(_ context.Context) error {
+func (v *Values) reconcile() {
v.fireResourceChanged(v.lines, v.filter(v.query, v.lines))
-
- return nil
}
// AddListener adds a new model listener.
diff --git a/internal/model/yaml.go b/internal/model/yaml.go
index b6ecc2b6..407cc302 100644
--- a/internal/model/yaml.go
+++ b/internal/model/yaml.go
@@ -25,7 +25,7 @@ const ManagedFieldsOpts = "ManagedFields"
// YAML tracks yaml resource representations.
type YAML struct {
- gvr client.GVR
+ gvr *client.GVR
inUpdate int32
path string
query string
@@ -35,7 +35,7 @@ type YAML struct {
}
// NewYAML return a new yaml resource model.
-func NewYAML(gvr client.GVR, path string) *YAML {
+func NewYAML(gvr *client.GVR, path string) *YAML {
return &YAML{
gvr: gvr,
path: path,
@@ -43,7 +43,7 @@ func NewYAML(gvr client.GVR, path string) *YAML {
}
// GVR returns the resource gvr.
-func (y *YAML) GVR() client.GVR {
+func (y *YAML) GVR() *client.GVR {
return y.gvr
}
@@ -195,7 +195,7 @@ func (y *YAML) RemoveListener(l ResourceViewerListener) {
}
// ToYAML returns a resource yaml.
-func (y *YAML) ToYAML(ctx context.Context, gvr client.GVR, path string, showManaged bool) (string, error) {
+func (*YAML) ToYAML(ctx context.Context, gvr *client.GVR, path string, showManaged bool) (string, error) {
meta, err := getMeta(ctx, gvr)
if err != nil {
return "", err
diff --git a/internal/model1/color.go b/internal/model1/color.go
index f570a040..0b69f712 100644
--- a/internal/model1/color.go
+++ b/internal/model1/color.go
@@ -37,7 +37,7 @@ func DefaultColorer(ns string, h Header, re *RowEvent) tcell.Color {
return ErrColor
}
- // nolint:exhaustive
+ //nolint:exhaustive
switch re.Kind {
case EventAdd:
return AddColor
diff --git a/internal/model1/header_test.go b/internal/model1/header_test.go
index 922656b4..f4bc4208 100644
--- a/internal/model1/header_test.go
+++ b/internal/model1/header_test.go
@@ -297,7 +297,7 @@ func TestHeaderClone(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := u.h.Clone()
- assert.Equal(t, len(u.h), len(c))
+ assert.Len(t, u.h, len(c))
if len(u.h) > 0 {
u.h[0].Name = "blee"
assert.Equal(t, "A", c[0].Name)
diff --git a/internal/model1/helpers.go b/internal/model1/helpers.go
index 5ee4541e..e6458a7b 100644
--- a/internal/model1/helpers.go
+++ b/internal/model1/helpers.go
@@ -4,7 +4,9 @@
package model1
import (
+ "context"
"fmt"
+ "log/slog"
"math"
"sort"
"strings"
@@ -15,11 +17,24 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+const poolSize = 10
+
func Hydrate(ns string, oo []runtime.Object, rr Rows, re Renderer) error {
+ pool := NewWorkerPool(context.Background(), poolSize)
for i, o := range oo {
- if err := re.Render(o, ns, &rr[i]); err != nil {
- return err
- }
+ pool.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ slog.Debug("Worker canceled")
+ return nil
+ default:
+ return re.Render(o, ns, &rr[i])
+ }
+ })
+ }
+ errs := pool.Drain()
+ if len(errs) > 0 {
+ return errs[0]
}
return nil
@@ -31,17 +46,28 @@ func GenericHydrate(ns string, table *metav1.Table, rr Rows, re Renderer) error
return fmt.Errorf("expecting generic renderer but got %T", re)
}
gr.SetTable(ns, table)
+ pool := NewWorkerPool(context.Background(), poolSize)
for i, row := range table.Rows {
- if err := gr.Render(row, ns, &rr[i]); err != nil {
- return err
- }
+ pool.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ slog.Debug("Worker canceled")
+ return nil
+ default:
+ return gr.Render(row, ns, &rr[i])
+ }
+ })
+ }
+ errs := pool.Drain()
+ if len(errs) > 0 {
+ return errs[0]
}
return nil
}
// IsValid returns true if resource is valid, false otherwise.
-func IsValid(ns string, h Header, r Row) bool {
+func IsValid(_ string, h Header, r Row) bool {
if len(r.Fields) == 0 {
return true
}
@@ -80,7 +106,7 @@ func labelize(labels string) map[string]string {
}
func durationToSeconds(duration string) int64 {
- if len(duration) == 0 {
+ if duration == "" {
return 0
}
if duration == NAValue {
diff --git a/internal/model1/helpers_test.go b/internal/model1/helpers_test.go
index e76e9f84..fcc6f24c 100644
--- a/internal/model1/helpers_test.go
+++ b/internal/model1/helpers_test.go
@@ -98,7 +98,6 @@ func TestIsValid(t *testing.T) {
assert.Equal(t, u.e, valid)
})
}
-
}
func TestDurationToSecond(t *testing.T) {
@@ -131,7 +130,7 @@ func BenchmarkDurationToSecond(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
durationToSeconds(t)
}
}
diff --git a/internal/model1/pool.go b/internal/model1/pool.go
new file mode 100644
index 00000000..7a07976b
--- /dev/null
+++ b/internal/model1/pool.go
@@ -0,0 +1,77 @@
+package model1
+
+import (
+ "context"
+ "log/slog"
+ "sync"
+
+ "github.com/derailed/k9s/internal/slogs"
+)
+
+type jobFn func(ctx context.Context) error
+
+type WorkerPool struct {
+ semC chan struct{}
+ errC chan error
+ ctx context.Context
+ cancelFn context.CancelFunc
+ mx sync.RWMutex
+ wg sync.WaitGroup
+ wge sync.WaitGroup
+ errs []error
+}
+
+func NewWorkerPool(ctx context.Context, size int) *WorkerPool {
+ _, cancelFn := context.WithCancel(ctx)
+
+ p := WorkerPool{
+ semC: make(chan struct{}, size),
+ errC: make(chan error, 1),
+ cancelFn: cancelFn,
+ ctx: ctx,
+ }
+
+ p.wge.Add(1)
+ go func(wg *sync.WaitGroup) {
+ defer wg.Done()
+ for err := range p.errC {
+ if err != nil {
+ p.mx.Lock()
+ p.errs = append(p.errs, err)
+ p.mx.Unlock()
+ }
+ }
+ }(&p.wge)
+
+ return &p
+}
+
+func (p *WorkerPool) Add(job jobFn) {
+ p.semC <- struct{}{}
+ p.wg.Add(1)
+ go func(ctx context.Context, wg *sync.WaitGroup, semC <-chan struct{}, errC chan<- error) {
+ defer func() {
+ <-semC
+ wg.Done()
+ }()
+ if err := job(ctx); err != nil {
+ slog.Error("Worker error", slogs.Error, err)
+ errC <- err
+ }
+ }(p.ctx, &p.wg, p.semC, p.errC)
+}
+
+func (p *WorkerPool) Drain() []error {
+ if p.cancelFn != nil {
+ p.cancelFn()
+ p.cancelFn = nil
+ }
+ p.wg.Wait()
+ close(p.semC)
+ close(p.errC)
+ p.wge.Wait()
+
+ p.mx.RLock()
+ defer p.mx.RUnlock()
+ return p.errs
+}
diff --git a/internal/model1/pool_test.go b/internal/model1/pool_test.go
new file mode 100644
index 00000000..e02b471e
--- /dev/null
+++ b/internal/model1/pool_test.go
@@ -0,0 +1,57 @@
+package model1_test
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+ "testing"
+
+ "github.com/derailed/k9s/internal/model1"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWorkerPoolPlain(t *testing.T) {
+ p := model1.NewWorkerPool(context.Background(), 2)
+
+ var c atomic.Int32
+ for range 10 {
+ p.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ fmt.Println("Worker canceled")
+ return nil
+ default:
+ c.Add(1)
+ return nil
+ }
+ })
+ }
+ errs := p.Drain()
+ assert.Equal(t, 10, int(c.Load()))
+ assert.Empty(t, errs)
+}
+
+func TestWorkerPoolWithError(t *testing.T) {
+ ctx := context.Background()
+ p := model1.NewWorkerPool(ctx, 2)
+
+ var c atomic.Int32
+ for i := range 10 {
+ p.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ fmt.Println("Worker canceled")
+ return nil
+ default:
+ if i%2 == 0 {
+ return fmt.Errorf("BOOM%d", i)
+ }
+ c.Add(1)
+ return nil
+ }
+ })
+ }
+ errs := p.Drain()
+ assert.Equal(t, 5, int(c.Load()))
+ assert.Len(t, errs, 5)
+}
diff --git a/internal/model1/row_event_test.go b/internal/model1/row_event_test.go
index 9ca93229..02c4a06f 100644
--- a/internal/model1/row_event_test.go
+++ b/internal/model1/row_event_test.go
@@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRowEventCustomize(t *testing.T) {
@@ -406,7 +407,7 @@ func TestRowEventsDelete(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.NoError(t, u.re.Delete(u.id))
+ require.NoError(t, u.re.Delete(u.id))
assert.Equal(t, u.e, u.re)
})
}
diff --git a/internal/model1/row_test.go b/internal/model1/row_test.go
index 55c5cccf..fed63dea 100644
--- a/internal/model1/row_test.go
+++ b/internal/model1/row_test.go
@@ -18,7 +18,7 @@ func BenchmarkRowCustomize(b *testing.B) {
cols := []int{0, 1, 2}
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
_ = row.Customize(cols)
}
}
diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go
index dc25afb2..f622b9ac 100644
--- a/internal/model1/table_data.go
+++ b/internal/model1/table_data.go
@@ -19,6 +19,7 @@ import (
"github.com/sahilm/fuzzy"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/sets"
)
// SortFn represent a function that can sort columnar data.
@@ -48,26 +49,26 @@ type TableData struct {
header Header
rowEvents *RowEvents
namespace string
- gvr client.GVR
+ gvr *client.GVR
mx sync.RWMutex
}
// NewTableData returns a new table.
-func NewTableData(gvr client.GVR) *TableData {
+func NewTableData(gvr *client.GVR) *TableData {
return &TableData{
gvr: gvr,
rowEvents: NewRowEvents(10),
}
}
-func NewTableDataFull(gvr client.GVR, ns string, h Header, re *RowEvents) *TableData {
+func NewTableDataFull(gvr *client.GVR, ns string, h Header, re *RowEvents) *TableData {
t := NewTableDataWithRows(gvr, h, re)
t.namespace = ns
return t
}
-func NewTableDataWithRows(gvr client.GVR, h Header, re *RowEvents) *TableData {
+func NewTableDataWithRows(gvr *client.GVR, h Header, re *RowEvents) *TableData {
t := NewTableData(gvr)
t.header, t.rowEvents = h, re
@@ -130,7 +131,7 @@ func (t *TableData) HeaderCount() int {
return len(t.header)
}
-func (t *TableData) HeadCol(n string, w bool) (HeaderColumn, int) {
+func (t *TableData) HeadCol(n string, w bool) (header HeaderColumn, idx int) {
idx, ok := t.header.IndexOf(n, w)
if !ok {
return HeaderColumn{}, -1
@@ -242,15 +243,13 @@ func (t *TableData) GetNamespace() string {
func (t *TableData) Reset(ns string) {
t.mx.Lock()
- {
- t.namespace = ns
- }
+ t.namespace = ns
t.mx.Unlock()
t.Clear()
}
-func (t *TableData) Reconcile(ctx context.Context, r Renderer, oo []runtime.Object) error {
+func (t *TableData) Render(_ context.Context, r Renderer, oo []runtime.Object) error {
var rows Rows
if len(oo) > 0 {
if r.IsGeneric() {
@@ -425,32 +424,30 @@ func (t *TableData) SetHeader(ns string, h Header) {
// Update computes row deltas and update the table data.
func (t *TableData) Update(rows Rows) {
empty := t.Empty()
- kk := make(map[string]struct{}, len(rows))
+ kk := sets.New[string]()
var blankDelta DeltaRow
t.mx.Lock()
- {
- for _, row := range rows {
- kk[row.ID] = struct{}{}
- if empty {
- t.rowEvents.Add(NewRowEvent(EventAdd, row))
- continue
- }
- if index, ok := t.rowEvents.FindIndex(row.ID); ok {
- ev, ok := t.rowEvents.At(index)
- if !ok {
- continue
- }
- delta := NewDeltaRow(ev.Row, row, t.header)
- if delta.IsBlank() {
- ev.Kind, ev.Deltas, ev.Row = EventUnchanged, blankDelta, row
- t.rowEvents.Set(index, ev)
- } else {
- t.rowEvents.Set(index, NewRowEventWithDeltas(row, delta))
- }
- continue
- }
+ for _, row := range rows {
+ kk.Insert(row.ID)
+ if empty {
t.rowEvents.Add(NewRowEvent(EventAdd, row))
+ continue
}
+ if index, ok := t.rowEvents.FindIndex(row.ID); ok {
+ ev, ok := t.rowEvents.At(index)
+ if !ok {
+ continue
+ }
+ delta := NewDeltaRow(ev.Row, row, t.header)
+ if delta.IsBlank() {
+ ev.Kind, ev.Deltas, ev.Row = EventUnchanged, blankDelta, row
+ t.rowEvents.Set(index, ev)
+ } else {
+ t.rowEvents.Set(index, NewRowEventWithDeltas(row, delta))
+ }
+ continue
+ }
+ t.rowEvents.Add(NewRowEvent(EventAdd, row))
}
t.mx.Unlock()
@@ -460,28 +457,28 @@ func (t *TableData) Update(rows Rows) {
}
// Delete removes items in cache that are no longer valid.
-func (t *TableData) Delete(newKeys map[string]struct{}) {
+func (t *TableData) Delete(newKeys sets.Set[string]) {
t.mx.Lock()
- {
- victims := make([]string, 0, 10)
- t.rowEvents.Range(func(_ int, e RowEvent) bool {
- if _, ok := newKeys[e.Row.ID]; !ok {
- victims = append(victims, e.Row.ID)
- } else {
- delete(newKeys, e.Row.ID)
- }
- return true
- })
- for _, id := range victims {
- if err := t.rowEvents.Delete(id); err != nil {
- slog.Error("Table delete failed",
- slogs.Error, err,
- slogs.Message, id,
- )
- }
+ defer t.mx.Unlock()
+
+ victims := sets.New[string]()
+ t.rowEvents.Range(func(_ int, e RowEvent) bool {
+ if newKeys.Has(e.Row.ID) {
+ delete(newKeys, e.Row.ID)
+ } else {
+ victims.Insert(e.Row.ID)
+ }
+ return true
+ })
+
+ for _, id := range victims.UnsortedList() {
+ if err := t.rowEvents.Delete(id); err != nil {
+ slog.Error("Table delete failed",
+ slogs.Error, err,
+ slogs.Message, id,
+ )
}
}
- t.mx.Unlock()
}
// Diff checks if two tables are equal.
diff --git a/internal/model1/table_data_test.go b/internal/model1/table_data_test.go
index 185f2cac..01dc8939 100644
--- a/internal/model1/table_data_test.go
+++ b/internal/model1/table_data_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
+ "k8s.io/apimachinery/pkg/util/sets"
)
func init() {
@@ -329,7 +330,7 @@ func TestTableDataUpdate(t *testing.T) {
func TestTableDataDelete(t *testing.T) {
uu := map[string]struct {
re, e *RowEvents
- kk map[string]struct{}
+ kk sets.Set[string]
}{
"ordered": {
re: NewRowEventsWithEvts(
@@ -337,7 +338,7 @@ func TestTableDataDelete(t *testing.T) {
RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}},
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
),
- kk: map[string]struct{}{"A": {}, "C": {}},
+ kk: sets.New[string]("A", "C"),
e: NewRowEventsWithEvts(
RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}},
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
@@ -350,7 +351,7 @@ func TestTableDataDelete(t *testing.T) {
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
RowEvent{Row: Row{ID: "D", Fields: Fields{"10", "2", "3"}}},
),
- kk: map[string]struct{}{"C": {}, "A": {}},
+ kk: sets.New[string]("C", "A"),
e: NewRowEventsWithEvts(
RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}},
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
diff --git a/internal/model1/types.go b/internal/model1/types.go
index 8235ca3c..b7b5004e 100644
--- a/internal/model1/types.go
+++ b/internal/model1/types.go
@@ -40,7 +40,7 @@ type Renderer interface {
IsGeneric() bool
// Render converts raw resources to tabular data.
- Render(o interface{}, ns string, row *Row) error
+ Render(o any, ns string, row *Row) error
// Header returns the resource header.
Header(ns string) Header
@@ -61,5 +61,5 @@ type Generic interface {
Header(ns string) Header
// Render renders the resource.
- Render(o interface{}, ns string, row *Row) error
+ Render(o any, ns string, row *Row) error
}
diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go
index e6ff7f47..41a24681 100644
--- a/internal/perf/benchmark.go
+++ b/internal/perf/benchmark.go
@@ -33,14 +33,14 @@ const (
// Benchmark puts a workload under load.
type Benchmark struct {
canceled bool
- config config.BenchConfig
+ config *config.BenchConfig
worker *requester.Work
cancelFn context.CancelFunc
mx sync.RWMutex
}
// NewBenchmark returns a new benchmark.
-func NewBenchmark(base, version string, cfg config.BenchConfig) (*Benchmark, error) {
+func NewBenchmark(base, version string, cfg *config.BenchConfig) (*Benchmark, error) {
b := Benchmark{config: cfg}
if err := b.init(base, version); err != nil {
return nil, err
@@ -51,7 +51,7 @@ func NewBenchmark(base, version string, cfg config.BenchConfig) (*Benchmark, err
func (b *Benchmark) init(base, version string) error {
var ctx context.Context
ctx, b.cancelFn = context.WithTimeout(context.Background(), benchTimeout)
- req, err := http.NewRequestWithContext(ctx, b.config.HTTP.Method, base, nil)
+ req, err := http.NewRequestWithContext(ctx, b.config.HTTP.Method, base, http.NoBody)
if err != nil {
return err
}
@@ -133,8 +133,8 @@ func (b *Benchmark) save(cluster, context string, r io.Reader) error {
return err
}
bf := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, n, time.Now().UnixNano()))
- if err := data.EnsureDirPath(bf, data.DefaultDirMod); err != nil {
- return err
+ if e := data.EnsureDirPath(bf, data.DefaultDirMod); e != nil {
+ return e
}
f, err := os.Create(bf)
diff --git a/internal/pool.go b/internal/pool.go
new file mode 100644
index 00000000..095ba679
--- /dev/null
+++ b/internal/pool.go
@@ -0,0 +1,79 @@
+package internal
+
+import (
+ "context"
+ "log/slog"
+ "sync"
+
+ "github.com/derailed/k9s/internal/slogs"
+)
+
+const DefaultPoolSize = 10
+
+type jobFn func(ctx context.Context) error
+
+type WorkerPool struct {
+ semC chan struct{}
+ errC chan error
+ ctx context.Context
+ cancelFn context.CancelFunc
+ mx sync.RWMutex
+ wg sync.WaitGroup
+ wge sync.WaitGroup
+ errs []error
+}
+
+func NewWorkerPool(ctx context.Context, size int) *WorkerPool {
+ _, cancelFn := context.WithCancel(ctx)
+
+ p := WorkerPool{
+ semC: make(chan struct{}, size),
+ errC: make(chan error, 1),
+ cancelFn: cancelFn,
+ ctx: ctx,
+ }
+
+ p.wge.Add(1)
+ go func(wg *sync.WaitGroup) {
+ defer wg.Done()
+ for err := range p.errC {
+ if err != nil {
+ p.mx.Lock()
+ p.errs = append(p.errs, err)
+ p.mx.Unlock()
+ }
+ }
+ }(&p.wge)
+
+ return &p
+}
+
+func (p *WorkerPool) Add(job jobFn) {
+ p.semC <- struct{}{}
+ p.wg.Add(1)
+ go func(ctx context.Context, wg *sync.WaitGroup, semC <-chan struct{}, errC chan<- error) {
+ defer func() {
+ <-semC
+ wg.Done()
+ }()
+ if err := job(ctx); err != nil {
+ slog.Error("Worker error", slogs.Error, err)
+ errC <- err
+ }
+ }(p.ctx, &p.wg, p.semC, p.errC)
+}
+
+func (p *WorkerPool) Drain() []error {
+ if p.cancelFn != nil {
+ p.cancelFn()
+ p.cancelFn = nil
+ }
+ p.wg.Wait()
+ close(p.semC)
+ close(p.errC)
+ p.wge.Wait()
+
+ p.mx.RLock()
+ defer p.mx.RUnlock()
+ return p.errs
+}
diff --git a/internal/pool_test.go b/internal/pool_test.go
new file mode 100644
index 00000000..f8973f55
--- /dev/null
+++ b/internal/pool_test.go
@@ -0,0 +1,57 @@
+package internal_test
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+ "testing"
+
+ "github.com/derailed/k9s/internal"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWorkerPoolPlain(t *testing.T) {
+ p := internal.NewWorkerPool(context.Background(), 2)
+
+ var c atomic.Int32
+ for range 10 {
+ p.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ fmt.Println("Worker canceled")
+ return nil
+ default:
+ c.Add(1)
+ return nil
+ }
+ })
+ }
+ errs := p.Drain()
+ assert.Equal(t, 10, int(c.Load()))
+ assert.Empty(t, errs)
+}
+
+func TestWorkerPoolWithError(t *testing.T) {
+ ctx := context.Background()
+ p := internal.NewWorkerPool(ctx, 2)
+
+ var c atomic.Int32
+ for i := range 10 {
+ p.Add(func(ctx context.Context) error {
+ select {
+ case <-ctx.Done():
+ fmt.Println("Worker canceled")
+ return nil
+ default:
+ if i%2 == 0 {
+ return fmt.Errorf("BOOM%d", i)
+ }
+ c.Add(1)
+ return nil
+ }
+ })
+ }
+ errs := p.Drain()
+ assert.Equal(t, 5, int(c.Load()))
+ assert.Len(t, errs, 5)
+}
diff --git a/internal/port/co_portspec_test.go b/internal/port/co_portspec_test.go
index 39f57040..9417aba9 100644
--- a/internal/port/co_portspec_test.go
+++ b/internal/port/co_portspec_test.go
@@ -8,6 +8,7 @@ import (
"github.com/derailed/k9s/internal/port"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestContainerPortSpecMatch(t *testing.T) {
@@ -62,7 +63,7 @@ func TestContainerPortSpecMatch(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pf, err := port.ParsePF(u.ann)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, u.spec.Match(pf))
})
@@ -134,7 +135,7 @@ func TestContainerPortSpecsMatch(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pf, err := port.ParsePF(u.ann)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, u.specs.Match(pf))
})
}
diff --git a/internal/port/pf.go b/internal/port/pf.go
index 9b56fbe4..8339dda7 100644
--- a/internal/port/pf.go
+++ b/internal/port/pf.go
@@ -35,7 +35,7 @@ type PFAnn struct {
}
func ParsePlainPF(ann string) (*PFAnn, error) {
- if len(ann) == 0 {
+ if ann == "" {
return nil, fmt.Errorf("invalid annotation %q", ann)
}
var pf PFAnn
@@ -43,7 +43,7 @@ func ParsePlainPF(ann string) (*PFAnn, error) {
if len(mm) < 3 {
return nil, fmt.Errorf("invalid plain port-forward %s", ann)
}
- if len(mm[2]) == 0 {
+ if mm[2] == "" {
pf.ContainerPort = intstr.Parse(mm[1])
pf.LocalPort = mm[1]
return &pf, nil
diff --git a/internal/port/pf_test.go b/internal/port/pf_test.go
index 66a11d15..5f415140 100644
--- a/internal/port/pf_test.go
+++ b/internal/port/pf_test.go
@@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/port"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/intstr"
)
@@ -143,7 +144,7 @@ func TestPFPortNum(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pf, err := port.ParsePF(u.exp)
- assert.Nil(t, err)
+ require.NoError(t, err)
n, err := pf.PortNum()
assert.Equal(t, u.err, err)
if err != nil {
@@ -184,7 +185,7 @@ func TestPFToTunnel(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pf, err := port.ParsePF(u.exp)
- assert.Nil(t, err)
+ require.NoError(t, err)
pt, err := pf.ToTunnel("blee")
assert.Equal(t, u.err, err)
if err != nil {
@@ -215,7 +216,7 @@ func TestPFString(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pf, err := port.ParsePF(u.exp)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, u.e, pf.String())
})
}
diff --git a/internal/port/pfs.go b/internal/port/pfs.go
index 85b0a2e5..9dd9e760 100644
--- a/internal/port/pfs.go
+++ b/internal/port/pfs.go
@@ -15,7 +15,7 @@ type PortChecker func(PortTunnel) bool
type PFAnns []*PFAnn
// ToPortSpec returns a container port and local port definitions.
-func (aa PFAnns) ToPortSpec(pp ContainerPortSpecs) (string, string) {
+func (aa PFAnns) ToPortSpec(pp ContainerPortSpecs) (ports, localPorts string) {
specs, lps := make([]string, 0, len(aa)), make([]string, 0, len(aa))
for _, a := range aa {
specs = append(specs, a.AsSpec())
@@ -32,7 +32,7 @@ func (aa PFAnns) ToPortSpec(pp ContainerPortSpecs) (string, string) {
return strings.Join(specs, ","), strings.Join(lps, ",")
}
-func (aa PFAnns) ToTunnels(address string, pp ContainerPortSpecs, available PortChecker) (PortTunnels, error) {
+func (aa PFAnns) ToTunnels(address string, _ ContainerPortSpecs, available PortChecker) (PortTunnels, error) {
pts := make(PortTunnels, 0, len(aa))
for _, a := range aa {
pt, err := a.ToTunnel(address)
diff --git a/internal/port/pfs_test.go b/internal/port/pfs_test.go
index 3d003a47..e7d7bf5f 100644
--- a/internal/port/pfs_test.go
+++ b/internal/port/pfs_test.go
@@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/port"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/intstr"
)
@@ -100,7 +101,7 @@ func TestPFsToTunnel(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
pfs, err := port.ParsePFs(u.exp)
- assert.Nil(t, err)
+ require.NoError(t, err)
pts, err := pfs.ToTunnels("fred", u.specs, f)
assert.Equal(t, u.e, err)
if err != nil {
diff --git a/internal/render/alias.go b/internal/render/alias.go
index 6b7f841e..3fc4d736 100644
--- a/internal/render/alias.go
+++ b/internal/render/alias.go
@@ -14,36 +14,37 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
+var defaultAliasHeader = model1.Header{
+ model1.HeaderColumn{Name: "RESOURCE"},
+ model1.HeaderColumn{Name: "GROUP"},
+ model1.HeaderColumn{Name: "VERSION"},
+ model1.HeaderColumn{Name: "COMMAND"},
+}
+
// Alias renders an aliases to screen.
type Alias struct {
Base
}
// Header returns a header row.
-func (Alias) Header(ns string) model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "RESOURCE"},
- model1.HeaderColumn{Name: "GROUP"},
- model1.HeaderColumn{Name: "VERSION"},
- model1.HeaderColumn{Name: "COMMAND"},
- }
+func (Alias) Header(string) model1.Header {
+ return defaultAliasHeader
}
// Render renders a K8s resource to screen.
// BOZO!! Pass in a row with pre-alloc fields??
-func (Alias) Render(o interface{}, ns string, r *model1.Row) error {
+func (Alias) Render(o any, _ string, r *model1.Row) error {
a, ok := o.(AliasRes)
if !ok {
return fmt.Errorf("expected AliasRes, but got %T", o)
}
-
slices.Sort(a.Aliases)
- gvr := client.NewGVR(a.GVR)
- r.ID = gvr.String()
+
+ r.ID = a.GVR.String()
r.Fields = append(r.Fields,
- gvr.R(),
- gvr.G(),
- gvr.V(),
+ a.GVR.R(),
+ a.GVR.G(),
+ a.GVR.V(),
strings.Join(a.Aliases, " "),
)
@@ -55,7 +56,7 @@ func (Alias) Render(o interface{}, ns string, r *model1.Row) error {
// AliasRes represents an alias resource.
type AliasRes struct {
- GVR string
+ GVR *client.GVR
Aliases []string
}
diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go
index b29bca0f..a6a453aa 100644
--- a/internal/render/alias_test.go
+++ b/internal/render/alias_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/render"
"github.com/derailed/tcell/v2"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestAliasColorer(t *testing.T) {
@@ -68,12 +69,12 @@ func TestAliasRender(t *testing.T) {
var a render.Alias
o := render.AliasRes{
- GVR: "fred/v1/blee",
+ GVR: client.NewGVR("fred/v1/blee"),
Aliases: []string{"a", "b", "c"},
}
var r model1.Row
- assert.Nil(t, a.Render(o, "fred/v1/blee", &r))
+ require.NoError(t, a.Render(o, "fred/v1/blee", &r))
assert.Equal(t, model1.Row{
ID: "fred/v1/blee",
Fields: model1.Fields{"blee", "fred", "v1", "a b c"},
@@ -82,15 +83,15 @@ func TestAliasRender(t *testing.T) {
func BenchmarkAlias(b *testing.B) {
o := render.AliasRes{
- GVR: "fred/v1/blee",
+ GVR: client.NewGVR("fred/v1/blee"),
Aliases: []string{"a", "b", "c"},
}
var a render.Alias
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for range b.N {
var r model1.Row
- _ = a.Render(o, "aliases", &r)
+ _ = a.Render(o, "ns-1", &r)
}
}
diff --git a/internal/render/base.go b/internal/render/base.go
index c86c17db..f3f35eb3 100644
--- a/internal/render/base.go
+++ b/internal/render/base.go
@@ -15,9 +15,7 @@ import (
type DecoratorFunc func(string) string
// AgeDecorator represents a timestamped as human column.
-var AgeDecorator = func(a string) string {
- return toAgeHuman(a)
-}
+var AgeDecorator = toAgeHuman
type Base struct {
vs *config.ViewSetting
diff --git a/internal/render/benchmark.go b/internal/render/benchmark.go
index c2cc433e..dca82656 100644
--- a/internal/render/benchmark.go
+++ b/internal/render/benchmark.go
@@ -24,7 +24,7 @@ var (
totalRx = regexp.MustCompile(`Total:\s+([0-9.]+)\ssecs`)
reqRx = regexp.MustCompile(`Requests/sec:\s+([0-9.]+)`)
okRx = regexp.MustCompile(`\[2\d{2}\]\s+(\d+)\s+responses`)
- errRx = regexp.MustCompile(`\[[4-5]\d{2}\]\s+(\d+)\s+responses`)
+ errRx = regexp.MustCompile(`\[[45]\d{2}\]\s+(\d+)\s+responses`)
toastRx = regexp.MustCompile(`Error distribution`)
)
@@ -34,7 +34,7 @@ type Benchmark struct {
}
// ColorerFunc colors a resource row.
-func (b Benchmark) ColorerFunc() model1.ColorerFunc {
+func (Benchmark) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
if !model1.IsValid(ns, h, re.Row) {
return model1.ErrColor
@@ -45,7 +45,7 @@ func (b Benchmark) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (Benchmark) Header(ns string) model1.Header {
+func (Benchmark) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAMESPACE"},
model1.HeaderColumn{Name: "NAME"},
@@ -61,7 +61,7 @@ func (Benchmark) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (b Benchmark) Render(o interface{}, ns string, r *model1.Row) error {
+func (b Benchmark) Render(o any, ns string, r *model1.Row) error {
bench, ok := o.(BenchInfo)
if !ok {
return fmt.Errorf("no benchmarks available %T", o)
@@ -111,7 +111,7 @@ func (Benchmark) readFile(file string) (string, error) {
return string(data), nil
}
-func (b Benchmark) initRow(row model1.Fields, f os.FileInfo) error {
+func (Benchmark) initRow(row model1.Fields, f os.FileInfo) error {
tokens := strings.Split(f.Name(), "_")
if len(tokens) < 2 {
return fmt.Errorf("invalid file name %s", f.Name())
@@ -125,7 +125,7 @@ func (b Benchmark) initRow(row model1.Fields, f os.FileInfo) error {
}
func (b Benchmark) augmentRow(fields model1.Fields, data string) {
- if len(data) == 0 {
+ if data == "" {
return
}
@@ -164,7 +164,7 @@ func (Benchmark) countReq(rr [][]string) string {
var sum int
for _, m := range rr {
- if m, err := strconv.Atoi(string(m[1])); err == nil {
+ if m, err := strconv.Atoi(m[1]); err == nil {
sum += m
}
}
diff --git a/internal/render/benchmark_int_test.go b/internal/render/benchmark_int_test.go
index bb6c42ed..3de18e55 100644
--- a/internal/render/benchmark_int_test.go
+++ b/internal/render/benchmark_int_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func init() {
@@ -44,7 +45,7 @@ func TestAugmentRow(t *testing.T) {
t.Run(k, func(t *testing.T) {
data, err := os.ReadFile(u.file)
- assert.Nil(t, err)
+ require.NoError(t, err)
fields := make(model1.Fields, 8)
b := Benchmark{}
b.augmentRow(fields, string(data))
diff --git a/internal/render/cm.go b/internal/render/cm.go
index 2dde05b8..73009cff 100644
--- a/internal/render/cm.go
+++ b/internal/render/cm.go
@@ -21,21 +21,19 @@ type ConfigMap struct {
// Header returns a header row.
func (m ConfigMap) Header(_ string) model1.Header {
- return m.doHeader(m.defaultHeader())
+ return m.doHeader(defaultCMHeader)
}
-func (ConfigMap) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "DATA"},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultCMHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "DATA"},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (m ConfigMap) Render(o interface{}, ns string, row *model1.Row) error {
+func (m ConfigMap) Render(o any, _ string, row *model1.Row) error {
if err := m.defaultRow(o, row); err != nil {
return err
}
@@ -43,7 +41,7 @@ func (m ConfigMap) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
- cols, err := m.specs.realize(o.(*unstructured.Unstructured), m.defaultHeader(), row)
+ cols, err := m.specs.realize(o.(*unstructured.Unstructured), defaultCMHeader, row)
if err != nil {
return err
}
@@ -53,10 +51,10 @@ func (m ConfigMap) Render(o interface{}, ns string, row *model1.Row) error {
}
// Render renders a K8s resource to screen.
-func (ConfigMap) defaultRow(o interface{}, r *model1.Row) error {
+func (ConfigMap) defaultRow(o any, r *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected ConfigMap, but got %T", o)
+ return fmt.Errorf("expected *Unstructured, but got %T", o)
}
var cm v1.ConfigMap
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cm)
diff --git a/internal/render/container.go b/internal/render/container.go
index c05242a0..0039e9a6 100644
--- a/internal/render/container.go
+++ b/internal/render/container.go
@@ -20,6 +20,8 @@ import (
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
+const falseStr = "false"
+
// ContainerWithMetrics represents a container and it's metrics.
type ContainerWithMetrics interface {
// Container returns the container
@@ -44,7 +46,7 @@ type Container struct {
}
// ColorerFunc colors a resource row.
-func (c Container) ColorerFunc() model1.ColorerFunc {
+func (Container) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -70,37 +72,35 @@ func (c Container) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (c Container) Header(_ string) model1.Header {
- return c.defaultHeader()
+func (Container) Header(_ string) model1.Header {
+ return defaultCOHeader
}
// Header returns a header row.
-func (Container) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "IDX"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "PF"},
- model1.HeaderColumn{Name: "IMAGE"},
- model1.HeaderColumn{Name: "READY"},
- model1.HeaderColumn{Name: "STATE"},
- model1.HeaderColumn{Name: "RESTARTS", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "PROBES(L:R:S)"},
- model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%MEM/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "PORTS"},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultCOHeader = model1.Header{
+ model1.HeaderColumn{Name: "IDX"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "PF"},
+ model1.HeaderColumn{Name: "IMAGE"},
+ model1.HeaderColumn{Name: "READY"},
+ model1.HeaderColumn{Name: "STATE"},
+ model1.HeaderColumn{Name: "RESTARTS", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "PROBES(L:R:S)"},
+ model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%MEM/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "PORTS"},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (c Container) Render(o interface{}, ns string, row *model1.Row) error {
+func (c Container) Render(o any, _ string, row *model1.Row) error {
cr, ok := o.(ContainerRes)
if !ok {
return fmt.Errorf("expected ContainerRes, but got %T", o)
@@ -111,7 +111,7 @@ func (c Container) Render(o interface{}, ns string, row *model1.Row) error {
func (c Container) defaultRow(cr ContainerRes, r *model1.Row) error {
cur, res := gatherMetrics(cr.Container, cr.MX)
- ready, state, restarts := "false", MissingValue, "0"
+ ready, state, restarts := falseStr, MissingValue, "0"
if cr.Status != nil {
ready, state, restarts = boolToStr(cr.Status.Ready), ToContainerState(cr.Status.State), strconv.Itoa(int(cr.Status.RestartCount))
}
@@ -148,7 +148,7 @@ func (Container) diagnose(state, ready string) error {
return nil
}
- if ready == "false" {
+ if ready == falseStr {
return errors.New("container is not ready")
}
return nil
@@ -200,7 +200,7 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, r metric
func ToContainerPorts(pp []v1.ContainerPort) string {
ports := make([]string, len(pp))
for i, p := range pp {
- if len(p.Name) > 0 {
+ if p.Name != "" {
ports[i] = p.Name + ":"
}
ports[i] += strconv.Itoa(int(p.ContainerPort))
@@ -255,7 +255,7 @@ type ContainerRes struct {
}
// GetObjectKind returns a schema object.
-func (c ContainerRes) GetObjectKind() schema.ObjectKind {
+func (ContainerRes) GetObjectKind() schema.ObjectKind {
return nil
}
diff --git a/internal/render/container_test.go b/internal/render/container_test.go
index d7f4f6f3..46169743 100644
--- a/internal/render/container_test.go
+++ b/internal/render/container_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -27,7 +28,7 @@ func TestContainer(t *testing.T) {
Age: makeAge(),
}
var r model1.Row
- assert.Nil(t, c.Render(cres, "blee", &r))
+ require.NoError(t, c.Render(cres, "blee", &r))
assert.Equal(t, "fred", r.ID)
assert.Equal(t, model1.Fields{
"",
@@ -54,19 +55,20 @@ func TestContainer(t *testing.T) {
}
func BenchmarkContainerRender(b *testing.B) {
- var c render.Container
-
- cres := render.ContainerRes{
- Container: makeContainer(),
- Status: makeContainerStatus(),
- MX: makeContainerMetrics(),
- Age: makeAge(),
- }
- var r model1.Row
+ var (
+ c render.Container
+ r model1.Row
+ cres = render.ContainerRes{
+ Container: makeContainer(),
+ Status: makeContainerStatus(),
+ MX: makeContainerMetrics(),
+ Age: makeAge(),
+ }
+ )
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
_ = c.Render(cres, "blee", &r)
}
}
diff --git a/internal/render/context.go b/internal/render/context.go
index 2aa17368..30cdaf69 100644
--- a/internal/render/context.go
+++ b/internal/render/context.go
@@ -34,7 +34,7 @@ func (Context) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (Context) Header(ns string) model1.Header {
+func (Context) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAME"},
model1.HeaderColumn{Name: "CLUSTER"},
@@ -44,7 +44,7 @@ func (Context) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (c Context) Render(o interface{}, _ string, r *model1.Row) error {
+func (Context) Render(o any, _ string, r *model1.Row) error {
ctx, ok := o.(*NamedContext)
if !ok {
return fmt.Errorf("expected *NamedContext, but got %T", o)
@@ -96,7 +96,7 @@ func (c *NamedContext) IsCurrentContext(n string) bool {
}
// GetObjectKind returns a schema object.
-func (c *NamedContext) GetObjectKind() schema.ObjectKind {
+func (*NamedContext) GetObjectKind() schema.ObjectKind {
return nil
}
diff --git a/internal/render/context_test.go b/internal/render/context_test.go
index 1cdc3911..d921b99c 100644
--- a/internal/render/context_test.go
+++ b/internal/render/context_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/client-go/tools/clientcmd/api"
)
func TestContextHeader(t *testing.T) {
var c render.Context
- assert.Equal(t, 4, len(c.Header("")))
+ assert.Len(t, c.Header(""), 4)
}
func TestContextRender(t *testing.T) {
@@ -48,7 +49,7 @@ func TestContextRender(t *testing.T) {
row := model1.NewRow(4)
err := r.Render(uc.ctx, "", &row)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, uc.e, row)
})
}
@@ -59,6 +60,6 @@ func TestContextRender(t *testing.T) {
type config struct{}
-func (k config) CurrentContextName() (string, error) {
+func (config) CurrentContextName() (string, error) {
return "fred", nil
}
diff --git a/internal/render/cr.go b/internal/render/cr.go
index 033e3388..eed69a6e 100644
--- a/internal/render/cr.go
+++ b/internal/render/cr.go
@@ -20,23 +20,21 @@ type ClusterRole struct {
// Header returns a header row.
func (c ClusterRole) Header(_ string) model1.Header {
- return c.doHeader(c.defaultHeader())
+ return c.doHeader(defaultCRHeader)
}
// Header returns a header rbw.
-func (ClusterRole) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultCRHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (p ClusterRole) Render(o interface{}, ns string, row *model1.Row) error {
+func (p ClusterRole) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expecting clusterrole, but got %T", o)
+ return fmt.Errorf("expecting Unstructured, but got %T", o)
}
if err := p.defaultRow(raw, row); err != nil {
return err
@@ -44,8 +42,7 @@ func (p ClusterRole) Render(o interface{}, ns string, row *model1.Row) error {
if p.specs.isEmpty() {
return nil
}
-
- cols, err := p.specs.realize(raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(raw, defaultCRHeader, row)
if err != nil {
return err
}
diff --git a/internal/render/cr_test.go b/internal/render/cr_test.go
index d6d17531..1f0ca608 100644
--- a/internal/render/cr_test.go
+++ b/internal/render/cr_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestClusterRoleRender(t *testing.T) {
c := render.ClusterRole{}
r := model1.NewRow(2)
- assert.NoError(t, c.Render(load(t, "cr"), "-", &r))
+ require.NoError(t, c.Render(load(t, "cr"), "-", &r))
assert.Equal(t, "-/blee", r.ID)
assert.Equal(t, model1.Fields{"blee"}, r.Fields[:1])
}
diff --git a/internal/render/crb.go b/internal/render/crb.go
index 7b8712fd..b41eb77c 100644
--- a/internal/render/crb.go
+++ b/internal/render/crb.go
@@ -13,6 +13,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultCRBHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "CLUSTERROLE"},
+ model1.HeaderColumn{Name: "SUBJECT-KIND"},
+ model1.HeaderColumn{Name: "SUBJECTS"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// ClusterRoleBinding renders a K8s ClusterRoleBinding to screen.
type ClusterRoleBinding struct {
Base
@@ -20,26 +29,14 @@ type ClusterRoleBinding struct {
// Header returns a header row.
func (c ClusterRoleBinding) Header(_ string) model1.Header {
- return c.doHeader(c.defaultHeader())
-}
-
-// Header returns a header rbw.
-func (ClusterRoleBinding) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "CLUSTERROLE"},
- model1.HeaderColumn{Name: "SUBJECT-KIND"},
- model1.HeaderColumn{Name: "SUBJECTS"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return c.doHeader(defaultCRBHeader)
}
// Render renders a K8s resource to screen.
-func (c ClusterRoleBinding) Render(o interface{}, ns string, row *model1.Row) error {
+func (c ClusterRoleBinding) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected ClusterRoleBinding, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := c.defaultRow(raw, row); err != nil {
return err
@@ -48,8 +45,7 @@ func (c ClusterRoleBinding) Render(o interface{}, ns string, row *model1.Row) er
return nil
}
- // !BOZO!! Call header 2 times
- cols, err := c.specs.realize(raw, c.defaultHeader(), row)
+ cols, err := c.specs.realize(raw, defaultCRBHeader, row)
if err != nil {
return err
}
diff --git a/internal/render/crb_test.go b/internal/render/crb_test.go
index 4b350a4c..ce10fca7 100644
--- a/internal/render/crb_test.go
+++ b/internal/render/crb_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestClusterRoleBindingRender(t *testing.T) {
c := render.ClusterRoleBinding{}
r := model1.NewRow(5)
- assert.NoError(t, c.Render(load(t, "crb"), "-", &r))
+ require.NoError(t, c.Render(load(t, "crb"), "-", &r))
assert.Equal(t, "-/blee", r.ID)
assert.Equal(t, model1.Fields{"blee", "blee", "User", "fernand"}, r.Fields[:4])
}
diff --git a/internal/render/crd.go b/internal/render/crd.go
index a5bb5af7..f8d19707 100644
--- a/internal/render/crd.go
+++ b/internal/render/crd.go
@@ -17,6 +17,18 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultCRDHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "GROUP"},
+ model1.HeaderColumn{Name: "KIND"},
+ model1.HeaderColumn{Name: "VERSIONS"},
+ model1.HeaderColumn{Name: "SCOPE"},
+ model1.HeaderColumn{Name: "ALIASES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// CustomResourceDefinition renders a K8s CustomResourceDefinition to screen.
type CustomResourceDefinition struct {
Base
@@ -24,29 +36,14 @@ type CustomResourceDefinition struct {
// Header returns a header row.
func (c CustomResourceDefinition) Header(_ string) model1.Header {
- return c.doHeader(c.defaultHeader())
-}
-
-// Header returns a header rbw.
-func (CustomResourceDefinition) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "GROUP"},
- model1.HeaderColumn{Name: "KIND"},
- model1.HeaderColumn{Name: "VERSIONS"},
- model1.HeaderColumn{Name: "SCOPE"},
- model1.HeaderColumn{Name: "ALIASES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return c.doHeader(defaultCRDHeader)
}
// Render renders a K8s resource to screen.
-func (c CustomResourceDefinition) Render(o interface{}, ns string, row *model1.Row) error {
+func (c CustomResourceDefinition) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected CustomResourceDefinition, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := c.defaultRow(raw, row); err != nil {
@@ -55,9 +52,7 @@ func (c CustomResourceDefinition) Render(o interface{}, ns string, row *model1.R
if c.specs.isEmpty() {
return nil
}
-
- // !BOZO!! Call header 2 times
- cols, err := c.specs.realize(raw, c.defaultHeader(), row)
+ cols, err := c.specs.realize(raw, defaultCRDHeader, row)
if err != nil {
return err
}
@@ -88,7 +83,7 @@ func (c CustomResourceDefinition) defaultRow(raw *unstructured.Unstructured, r *
slog.Warn("Unable to assert CRD versions", slogs.FQN, crd.Name)
}
- r.ID = client.MetaFQN(crd.ObjectMeta)
+ r.ID = client.MetaFQN(&crd.ObjectMeta)
r.Fields = model1.Fields{
crd.Spec.Names.Plural,
crd.Spec.Group,
@@ -104,7 +99,7 @@ func (c CustomResourceDefinition) defaultRow(raw *unstructured.Unstructured, r *
return nil
}
-func (c CustomResourceDefinition) diagnose(n string, vv []v1.CustomResourceDefinitionVersion) error {
+func (CustomResourceDefinition) diagnose(n string, vv []v1.CustomResourceDefinitionVersion) error {
if len(vv) == 0 {
return fmt.Errorf("unable to assert CRD servers versions for %s", n)
}
diff --git a/internal/render/crd_test.go b/internal/render/crd_test.go
index a88715ee..33ba8eb5 100644
--- a/internal/render/crd_test.go
+++ b/internal/render/crd_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestCustomResourceDefinitionRender(t *testing.T) {
c := render.CustomResourceDefinition{}
r := model1.NewRow(2)
- assert.NoError(t, c.Render(load(t, "crd"), "", &r))
+ require.NoError(t, c.Render(load(t, "crd"), "", &r))
assert.Equal(t, "-/adapters.config.istio.io", r.ID)
assert.Equal(t, "adapters", r.Fields[0])
assert.Equal(t, "config.istio.io", r.Fields[1])
diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go
index ca1cd1dc..72d12cfb 100644
--- a/internal/render/cronjob.go
+++ b/internal/render/cronjob.go
@@ -16,6 +16,22 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultCJHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "SCHEDULE"},
+ model1.HeaderColumn{Name: "SUSPEND"},
+ model1.HeaderColumn{Name: "ACTIVE"},
+ model1.HeaderColumn{Name: "LAST_SCHEDULE", Attrs: model1.Attrs{Time: true}},
+ model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// CronJob renders a K8s CronJob to screen.
type CronJob struct {
Base
@@ -23,32 +39,14 @@ type CronJob struct {
// Header returns a header row.
func (c CronJob) Header(_ string) model1.Header {
- return c.doHeader(c.defaultHeader())
-}
-
-func (CronJob) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "SCHEDULE"},
- model1.HeaderColumn{Name: "SUSPEND"},
- model1.HeaderColumn{Name: "ACTIVE"},
- model1.HeaderColumn{Name: "LAST_SCHEDULE", Attrs: model1.Attrs{Time: true}},
- model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return c.doHeader(defaultCJHeader)
}
// Render renders a K8s resource to screen.
-func (c CronJob) Render(o interface{}, ns string, row *model1.Row) error {
+func (c CronJob) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected CronJob, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := c.defaultRow(raw, row); err != nil {
return err
@@ -57,7 +55,7 @@ func (c CronJob) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
- cols, err := c.specs.realize(raw, c.defaultHeader(), row)
+ cols, err := c.specs.realize(raw, defaultCJHeader, row)
if err != nil {
return err
}
@@ -67,7 +65,7 @@ func (c CronJob) Render(o interface{}, ns string, row *model1.Row) error {
}
// Render renders a K8s resource to screen.
-func (c CronJob) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
+func (CronJob) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
var cj batchv1.CronJob
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cj)
if err != nil {
@@ -79,18 +77,18 @@ func (c CronJob) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error
lastScheduled = ToAge(*cj.Status.LastScheduleTime)
}
- r.ID = client.MetaFQN(cj.ObjectMeta)
+ r.ID = client.MetaFQN(&cj.ObjectMeta)
r.Fields = model1.Fields{
cj.Namespace,
cj.Name,
- computeVulScore(cj.ObjectMeta, &cj.Spec.JobTemplate.Spec.Template.Spec),
+ computeVulScore(cj.Namespace, cj.Labels, &cj.Spec.JobTemplate.Spec.Template.Spec),
cj.Spec.Schedule,
boolPtrToStr(cj.Spec.Suspend),
strconv.Itoa(len(cj.Status.Active)),
lastScheduled,
- jobSelector(cj.Spec.JobTemplate.Spec),
- podContainerNames(cj.Spec.JobTemplate.Spec.Template.Spec, true),
- podImageNames(cj.Spec.JobTemplate.Spec.Template.Spec, true),
+ jobSelector(&cj.Spec.JobTemplate.Spec),
+ podContainerNames(&cj.Spec.JobTemplate.Spec.Template.Spec, true),
+ podImageNames(&cj.Spec.JobTemplate.Spec.Template.Spec, true),
mapToStr(cj.Labels),
"",
ToAge(cj.GetCreationTimestamp()),
@@ -101,7 +99,7 @@ func (c CronJob) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error
// Helpers
-func jobSelector(spec batchv1.JobSpec) string {
+func jobSelector(spec *batchv1.JobSpec) string {
if spec.Selector == nil {
return MissingValue
}
@@ -120,31 +118,31 @@ func jobSelector(spec batchv1.JobSpec) string {
return strings.Join(ss, " ")
}
-func podContainerNames(spec v1.PodSpec, includeInit bool) string {
+func podContainerNames(spec *v1.PodSpec, includeInit bool) string {
cc := make([]string, 0, len(spec.Containers)+len(spec.InitContainers))
if includeInit {
- for _, c := range spec.InitContainers {
- cc = append(cc, c.Name)
+ for i := range spec.InitContainers {
+ cc = append(cc, spec.InitContainers[i].Name)
}
}
- for _, c := range spec.Containers {
- cc = append(cc, c.Name)
+ for i := range spec.Containers {
+ cc = append(cc, spec.Containers[i].Name)
}
return strings.Join(cc, ",")
}
-func podImageNames(spec v1.PodSpec, includeInit bool) string {
+func podImageNames(spec *v1.PodSpec, includeInit bool) string {
cc := make([]string, 0, len(spec.Containers)+len(spec.InitContainers))
if includeInit {
- for _, c := range spec.InitContainers {
- cc = append(cc, c.Image)
+ for i := range spec.InitContainers {
+ cc = append(cc, spec.InitContainers[i].Image)
}
}
- for _, c := range spec.Containers {
- cc = append(cc, c.Image)
+ for i := range spec.Containers {
+ cc = append(cc, spec.Containers[i].Image)
}
return strings.Join(cc, ",")
diff --git a/internal/render/cronjob_test.go b/internal/render/cronjob_test.go
index 34a77a96..d15e4575 100644
--- a/internal/render/cronjob_test.go
+++ b/internal/render/cronjob_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestCronJobRender(t *testing.T) {
c := render.CronJob{}
r := model1.NewRow(6)
- assert.NoError(t, c.Render(load(t, "cj"), "", &r))
+ require.NoError(t, c.Render(load(t, "cj"), "", &r))
assert.Equal(t, "default/hello", r.ID)
assert.Equal(t, model1.Fields{"default", "hello", "0", "*/1 * * * *", "false", "0"}, r.Fields[:6])
}
diff --git a/internal/render/cust_col.go b/internal/render/cust_col.go
index 2e91fae0..4f6095f2 100644
--- a/internal/render/cust_col.go
+++ b/internal/render/cust_col.go
@@ -14,7 +14,7 @@ import (
"k8s.io/kubectl/pkg/cmd/get"
)
-var fullRX = regexp.MustCompile(`^([\w\s%\/-]+)\:?([\w\d\S\W]*?)\|?([N|T|W|S|L|R|H]{0,3})$`)
+var fullRX = regexp.MustCompile(`^([\w\s%/-]+):?([\w\W]*?)\|?([NTWSLRH]{0,3})$`)
type colAttr byte
diff --git a/internal/render/cust_cols.go b/internal/render/cust_cols.go
index ef30d478..6caa0468 100644
--- a/internal/render/cust_cols.go
+++ b/internal/render/cust_cols.go
@@ -4,6 +4,7 @@
package render
import (
+ "errors"
"fmt"
"log/slog"
"reflect"
@@ -12,6 +13,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/slogs"
+ "github.com/itchyny/gojq"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -172,6 +174,13 @@ func hydrate(o runtime.Object, cc ColumnSpecs, parsers []*jsonpath.JSONPath, rh
err error
)
if unstructured, ok := o.(runtime.Unstructured); ok {
+ if vals, ok := jqParse(cc[idx].Spec, unstructured.UnstructuredContent()); ok {
+ cols[idx] = RenderedCol{
+ Header: cc[idx].Header,
+ Value: vals,
+ }
+ continue
+ }
vals, err = parser.FindResults(unstructured.UnstructuredContent())
} else {
vals, err = parser.FindResults(reflect.ValueOf(o).Elem().Interface())
@@ -232,3 +241,38 @@ func hydrate(o runtime.Object, cc ColumnSpecs, parsers []*jsonpath.JSONPath, rh
return cols, nil
}
+
+func isJQSpec(spec string) bool {
+ return len(strings.Split(spec, "|")) > 2
+}
+
+func jqParse(spec string, o map[string]any) (string, bool) {
+ if !isJQSpec(spec) {
+ return "", false
+ }
+
+ exp := spec[1 : len(spec)-1]
+ jq, err := gojq.Parse(exp)
+ if err != nil {
+ slog.Warn("Fail to parse JQ expression", slogs.JQExp, exp, slogs.Error, err)
+ return "", false
+ }
+
+ rr := make([]string, 0, 10)
+ iter := jq.Run(o)
+ for v, ok := iter.Next(); ok; v, ok = iter.Next() {
+ if e, cool := v.(error); cool && e != nil {
+ if errors.Is(e, new(gojq.HaltError)) {
+ break
+ }
+ slog.Error("JQ expression evaluation failed. Check your query", slogs.Error, e)
+ continue
+ }
+ rr = append(rr, fmt.Sprintf("%v", v))
+ }
+ if len(rr) == 0 {
+ return "", false
+ }
+
+ return strings.Join(rr, ","), true
+}
diff --git a/internal/render/dir.go b/internal/render/dir.go
index cfaa5e14..4ba6e922 100644
--- a/internal/render/dir.go
+++ b/internal/render/dir.go
@@ -24,7 +24,7 @@ func (Dir) IsGeneric() bool {
// ColorerFunc colors a resource row.
func (Dir) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorCadetBlue
}
}
@@ -32,7 +32,7 @@ func (Dir) ColorerFunc() model1.ColorerFunc {
func (Dir) SetViewSetting(*config.ViewSetting) {}
// Header returns a header row.
-func (Dir) Header(ns string) model1.Header {
+func (Dir) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAME"},
}
@@ -40,7 +40,7 @@ func (Dir) Header(ns string) model1.Header {
// Render renders a K8s resource to screen.
// BOZO!! Pass in a row with pre-alloc fields??
-func (Dir) Render(o interface{}, ns string, r *model1.Row) error {
+func (Dir) Render(o any, _ string, r *model1.Row) error {
d, ok := o.(DirRes)
if !ok {
return fmt.Errorf("expected DirRes, but got %T", o)
diff --git a/internal/render/dp.go b/internal/render/dp.go
index 014f0bbc..845f9c13 100644
--- a/internal/render/dp.go
+++ b/internal/render/dp.go
@@ -17,13 +17,25 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultDPHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "UP-TO-DATE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Deployment renders a K8s Deployment to screen.
type Deployment struct {
Base
}
// ColorerFunc colors a resource row.
-func (d Deployment) ColorerFunc() model1.ColorerFunc {
+func (Deployment) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -43,28 +55,14 @@ func (d Deployment) ColorerFunc() model1.ColorerFunc {
// Header returns a header row.
func (d Deployment) Header(_ string) model1.Header {
- return d.doHeader(d.defaultHeader())
-}
-
-func (Deployment) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "UP-TO-DATE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return d.doHeader(defaultDPHeader)
}
// Render renders a K8s resource to screen.
-func (d Deployment) Render(o interface{}, ns string, row *model1.Row) error {
+func (d Deployment) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Deployment, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := d.defaultRow(raw, row); err != nil {
return err
@@ -72,9 +70,7 @@ func (d Deployment) Render(o interface{}, ns string, row *model1.Row) error {
if d.specs.isEmpty() {
return nil
}
-
- // !BOZO!! Call header 2 times
- cols, err := d.specs.realize(raw, d.defaultHeader(), row)
+ cols, err := d.specs.realize(raw, defaultDPHeader, row)
if err != nil {
return err
}
@@ -91,11 +87,11 @@ func (d Deployment) defaultRow(raw *unstructured.Unstructured, r *model1.Row) er
return err
}
- r.ID = client.MetaFQN(dp.ObjectMeta)
+ r.ID = client.MetaFQN(&dp.ObjectMeta)
r.Fields = model1.Fields{
dp.Namespace,
dp.Name,
- computeVulScore(dp.ObjectMeta, &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.UpdatedReplicas)),
strconv.Itoa(int(dp.Status.AvailableReplicas)),
diff --git a/internal/render/dp_test.go b/internal/render/dp_test.go
index e4ecc4b1..52148446 100644
--- a/internal/render/dp_test.go
+++ b/internal/render/dp_test.go
@@ -9,25 +9,28 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDpRender(t *testing.T) {
c := render.Deployment{}
r := model1.NewRow(7)
- assert.Nil(t, c.Render(load(t, "dp"), "", &r))
+ require.NoError(t, c.Render(load(t, "dp"), "", &r))
assert.Equal(t, "icx/icx-db", r.ID)
assert.Equal(t, model1.Fields{"icx", "icx-db", "0", "1/1", "1", "1"}, r.Fields[:6])
}
func BenchmarkDpRender(b *testing.B) {
- c := render.Deployment{}
- r := model1.NewRow(7)
- o := load(b, "dp")
+ var (
+ c = render.Deployment{}
+ r = model1.NewRow(7)
+ o = load(b, "dp")
+ )
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for range b.N {
_ = c.Render(o, "", &r)
}
}
diff --git a/internal/render/ds.go b/internal/render/ds.go
index 433b2a00..5c67ad19 100644
--- a/internal/render/ds.go
+++ b/internal/render/ds.go
@@ -15,6 +15,20 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultDSHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "UP-TO-DATE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// DaemonSet renders a K8s DaemonSet to screen.
type DaemonSet struct {
Base
@@ -22,31 +36,14 @@ type DaemonSet struct {
// Header returns a header row.
func (d DaemonSet) Header(_ string) model1.Header {
- return d.doHeader(d.defaultHeader())
-}
-
-// Header returns a header row.
-func (DaemonSet) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "UP-TO-DATE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return d.doHeader(defaultDSHeader)
}
// Render renders a K8s resource to screen.
-func (d DaemonSet) Render(o interface{}, ns string, row *model1.Row) error {
+func (d DaemonSet) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Deployment, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := d.defaultRow(raw, row); err != nil {
return err
@@ -54,9 +51,7 @@ func (d DaemonSet) Render(o interface{}, ns string, row *model1.Row) error {
if d.specs.isEmpty() {
return nil
}
-
- // !BOZO!! Call header 2 times
- cols, err := d.specs.realize(raw, d.defaultHeader(), row)
+ cols, err := d.specs.realize(raw, defaultDSHeader, row)
if err != nil {
return err
}
@@ -73,11 +68,11 @@ func (d DaemonSet) defaultRow(raw *unstructured.Unstructured, r *model1.Row) err
return err
}
- r.ID = client.MetaFQN(ds.ObjectMeta)
+ r.ID = client.MetaFQN(&ds.ObjectMeta)
r.Fields = model1.Fields{
ds.Namespace,
ds.Name,
- computeVulScore(ds.ObjectMeta, &ds.Spec.Template.Spec),
+ computeVulScore(ds.Namespace, ds.Labels, &ds.Spec.Template.Spec),
strconv.Itoa(int(ds.Status.DesiredNumberScheduled)),
strconv.Itoa(int(ds.Status.CurrentNumberScheduled)),
strconv.Itoa(int(ds.Status.NumberReady)),
diff --git a/internal/render/ds_test.go b/internal/render/ds_test.go
index 16598332..2f16869f 100644
--- a/internal/render/ds_test.go
+++ b/internal/render/ds_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDaemonSetRender(t *testing.T) {
c := render.DaemonSet{}
r := model1.NewRow(9)
- assert.NoError(t, c.Render(load(t, "ds"), "", &r))
+ require.NoError(t, c.Render(load(t, "ds"), "", &r))
assert.Equal(t, "kube-system/fluentd-gcp-v3.2.0", r.ID)
assert.Equal(t, model1.Fields{"kube-system", "fluentd-gcp-v3.2.0", "0", "2", "2", "2", "2", "2"}, r.Fields[:8])
}
diff --git a/internal/render/ep.go b/internal/render/ep.go
index 70f404ad..9377d96b 100644
--- a/internal/render/ep.go
+++ b/internal/render/ep.go
@@ -15,6 +15,13 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultEPHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "ENDPOINTS"},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Endpoints renders a K8s Endpoints to screen.
type Endpoints struct {
Base
@@ -22,28 +29,18 @@ type Endpoints struct {
// Header returns a header row.
func (e Endpoints) Header(_ string) model1.Header {
- return e.doHeader(e.defaultHeader())
-}
-
-// Header returns a header row.
-func (Endpoints) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "ENDPOINTS"},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return e.doHeader(defaultEPHeader)
}
// Render renders a K8s resource to screen.
-func (e Endpoints) Render(o interface{}, ns string, row *model1.Row) error {
+func (e Endpoints) Render(o any, ns string, row *model1.Row) error {
if err := e.defaultRow(o, ns, row); err != nil {
return err
}
if e.specs.isEmpty() {
return nil
}
- cols, err := e.specs.realize(o.(*unstructured.Unstructured), e.defaultHeader(), row)
+ cols, err := e.specs.realize(o.(*unstructured.Unstructured), defaultEPHeader, row)
if err != nil {
return err
}
@@ -52,10 +49,10 @@ func (e Endpoints) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
-func (e Endpoints) defaultRow(o interface{}, ns string, r *model1.Row) error {
+func (e Endpoints) defaultRow(o any, ns string, r *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Endpoints, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
var ep v1.Endpoints
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ep)
@@ -63,7 +60,7 @@ func (e Endpoints) defaultRow(o interface{}, ns string, r *model1.Row) error {
return err
}
- r.ID = client.MetaFQN(ep.ObjectMeta)
+ r.ID = client.MetaFQN(&ep.ObjectMeta)
r.Fields = make(model1.Fields, 0, len(e.Header(ns)))
r.Fields = model1.Fields{
ep.Namespace,
@@ -91,16 +88,16 @@ func toEPs(ss []v1.EndpointSubset) string {
}
func portsToStrs(pp []v1.EndpointPort, ss []string) {
- for i := 0; i < len(pp); i++ {
+ for i := range pp {
ss[i] = strconv.Itoa(int(pp[i].Port))
}
}
-func processIPs(aa []string, pp []string, addrs []v1.EndpointAddress) {
+func processIPs(aa, pp []string, addrs []v1.EndpointAddress) {
const maxIPs = 3
var i int
for _, a := range addrs {
- if len(a.IP) == 0 {
+ if a.IP == "" {
continue
}
if len(pp) == 0 {
diff --git a/internal/render/ep_test.go b/internal/render/ep_test.go
index f4359f3a..f9d618b3 100644
--- a/internal/render/ep_test.go
+++ b/internal/render/ep_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestEndpointsRender(t *testing.T) {
c := render.Endpoints{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "ep"), "", &r))
+ require.NoError(t, c.Render(load(t, "ep"), "", &r))
assert.Equal(t, "default/dictionary1", r.ID)
assert.Equal(t, model1.Fields{"default", "dictionary1", ""}, r.Fields[:3])
}
diff --git a/internal/render/generic.go b/internal/render/generic.go
index bb2a5778..ed3c6695 100644
--- a/internal/render/generic.go
+++ b/internal/render/generic.go
@@ -11,6 +11,13 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
+var defaultGENHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Generic renders a K8s generic resource to screen.
type Generic struct {
Base
@@ -18,34 +25,22 @@ type Generic struct {
// Header returns a header row.
func (m Generic) Header(_ string) model1.Header {
- return m.doHeader(m.defaultHeader())
-}
-
-// Header returns a header rbw.
-func (Generic) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return m.doHeader(defaultGENHeader)
}
// Render renders a K8s resource to screen.
-func (m Generic) Render(o interface{}, ns string, row *model1.Row) error {
+func (m Generic) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected *Unstructured, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := m.defaultRow(raw, row); err != nil {
return err
}
if m.specs.isEmpty() {
return nil
}
-
- cols, err := m.specs.realize(o.(*unstructured.Unstructured), m.defaultHeader(), row)
+ cols, err := m.specs.realize(o.(*unstructured.Unstructured), defaultGENHeader, row)
if err != nil {
return err
}
diff --git a/internal/render/helm/chart.go b/internal/render/helm/chart.go
index fcfb1450..cf3adbc1 100644
--- a/internal/render/helm/chart.go
+++ b/internal/render/helm/chart.go
@@ -47,7 +47,7 @@ func (Chart) Header(_ string) model1.Header {
}
// Render renders a chart to screen.
-func (c Chart) Render(o interface{}, ns string, r *model1.Row) error {
+func (c Chart) Render(o any, _ string, r *model1.Row) error {
h, ok := o.(ReleaseRes)
if !ok {
return fmt.Errorf("expected ReleaseRes, but got %T", o)
@@ -68,7 +68,7 @@ func (c Chart) Render(o interface{}, ns string, r *model1.Row) error {
return nil
}
-func (c Chart) diagnose(s string) error {
+func (Chart) diagnose(s string) error {
if s != "deployed" {
return fmt.Errorf("chart is in an invalid state")
}
diff --git a/internal/render/helm/history.go b/internal/render/helm/history.go
index c558e660..13d5188d 100644
--- a/internal/render/helm/history.go
+++ b/internal/render/helm/history.go
@@ -18,7 +18,7 @@ import (
type History struct{}
// Healthy checks component health.
-func (History) Healthy(ctx context.Context, o interface{}) error {
+func (History) Healthy(context.Context, any) error {
return nil
}
@@ -47,7 +47,7 @@ func (History) Header(_ string) model1.Header {
}
// Render renders a chart to screen.
-func (c History) Render(o interface{}, ns string, r *model1.Row) error {
+func (c History) Render(o any, _ string, r *model1.Row) error {
h, ok := o.(ReleaseRes)
if !ok {
return fmt.Errorf("expected HistoryRes, but got %T", o)
@@ -67,6 +67,6 @@ func (c History) Render(o interface{}, ns string, r *model1.Row) error {
return nil
}
-func (c History) diagnose(s string) error {
+func (History) diagnose(string) error {
return nil
}
diff --git a/internal/render/helpers.go b/internal/render/helpers.go
index 5ce26018..3d180869 100644
--- a/internal/render/helpers.go
+++ b/internal/render/helpers.go
@@ -27,15 +27,15 @@ import (
// !!BOZO!! If this has any legs?? enable scans on other container types.
func ExtractImages(spec *v1.PodSpec) []string {
ii := make([]string, 0, len(spec.Containers))
- for _, c := range spec.Containers {
- ii = append(ii, c.Image)
+ for i := range spec.Containers {
+ ii = append(ii, spec.Containers[i].Image)
}
return ii
}
-func computeVulScore(m metav1.ObjectMeta, spec *v1.PodSpec) string {
- if vul.ImgScanner == nil || vul.ImgScanner.ShouldExcludes(m) {
+func computeVulScore(ns string, lbls map[string]string, spec *v1.PodSpec) string {
+ if vul.ImgScanner == nil || vul.ImgScanner.ShouldExcludes(ns, lbls) {
return "0"
}
ii := ExtractImages(spec)
@@ -91,26 +91,27 @@ func toSelector(m map[string]string) string {
}
// Blank checks if a collection is empty or all values are blank.
-func blank(s []string) bool {
- for _, v := range s {
- if len(v) != 0 {
+func blank(ss []string) bool {
+ for _, s := range ss {
+ if s != "" {
return false
}
}
+
return true
}
// Join a slice of strings, skipping blanks.
-func join(a []string, sep string) string {
- switch len(a) {
+func join(ss []string, sep string) string {
+ switch len(ss) {
case 0:
return ""
case 1:
- return a[0]
+ return ss[0]
}
- b := make([]string, 0, len(a))
- for _, s := range a {
+ b := make([]string, 0, len(ss))
+ for _, s := range ss {
if s != "" {
b = append(b, s)
}
@@ -120,8 +121,8 @@ func join(a []string, sep string) string {
}
n := len(sep) * (len(b) - 1)
- for i := 0; i < len(b); i++ {
- n += len(a[i])
+ for i := range b {
+ n += len(ss[i])
}
var buff strings.Builder
@@ -147,7 +148,7 @@ func PrintPerc(p int) string {
// IntToStr converts an int to a string.
func IntToStr(p int) string {
- return strconv.Itoa(int(p))
+ return strconv.Itoa(p)
}
func missing(s string) string {
@@ -166,7 +167,7 @@ func na(s string) string {
}
func check(s, sub string) string {
- if len(s) == 0 {
+ if s == "" {
return sub
}
@@ -192,7 +193,7 @@ func ToAge(t metav1.Time) string {
}
func toAgeHuman(s string) string {
- if len(s) == 0 {
+ if s == "" {
return UnknownValue
}
@@ -231,12 +232,12 @@ func mapToStr(m map[string]string) string {
return string(bb)
}
-func mapToIfc(m interface{}) (s string) {
+func mapToIfc(m any) (s string) {
if m == nil {
return ""
}
- mm, ok := m.(map[string]interface{})
+ mm, ok := m.(map[string]any)
if !ok {
return ""
}
diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go
index 355d4e6e..2bd2fe05 100644
--- a/internal/render/helpers_test.go
+++ b/internal/render/helpers_test.go
@@ -13,6 +13,7 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
@@ -28,11 +29,11 @@ func TestTableGenericHydrate(t *testing.T) {
},
Rows: []metav1beta1.TableRow{
{
- Cells: []interface{}{"fred", 10},
+ Cells: []any{"fred", 10},
Object: runtime.RawExtension{Object: raw},
},
{
- Cells: []interface{}{"blee", 20},
+ Cells: []any{"blee", 20},
Object: runtime.RawExtension{Object: raw},
},
},
@@ -41,9 +42,9 @@ func TestTableGenericHydrate(t *testing.T) {
var re Table
re.SetTable("blee", &tt)
- assert.Nil(t, model1.GenericHydrate("blee", &tt, rr, &re))
- assert.Equal(t, 2, len(rr))
- assert.Equal(t, 2, len(rr[0].Fields))
+ require.NoError(t, model1.GenericHydrate("blee", &tt, rr, &re))
+ assert.Len(t, rr, 2)
+ assert.Len(t, rr[0].Fields, 2)
}
func TestTableHydrate(t *testing.T) {
@@ -52,9 +53,10 @@ func TestTableHydrate(t *testing.T) {
}
rr := make([]model1.Row, 1)
- assert.Nil(t, model1.Hydrate("blee", oo, rr, NewPod()))
- assert.Equal(t, 1, len(rr))
- assert.Equal(t, 25, len(rr[0].Fields))
+ re := NewPod()
+ require.NoError(t, model1.Hydrate("blee", oo, rr, re))
+ assert.Len(t, rr, 1)
+ assert.Len(t, rr[0].Fields, 25)
}
func TestToAge(t *testing.T) {
@@ -286,7 +288,7 @@ func TestMetaFQN(t *testing.T) {
for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, uc.e, client.MetaFQN(uc.m))
+ assert.Equal(t, uc.e, client.MetaFQN(&uc.m))
})
}
}
@@ -329,7 +331,7 @@ func BenchmarkMapToStr(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for range b.N {
mapToStr(ll)
}
}
@@ -370,7 +372,7 @@ func BenchmarkRunesToNum(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for range b.N {
runesToNum(rr)
}
}
@@ -423,7 +425,7 @@ func BenchmarkIntToStr(b *testing.B) {
v := 10
b.ResetTimer()
b.ReportAllocs()
- for n := 0; n < b.N; n++ {
+ for range b.N {
IntToStr(v)
}
}
@@ -432,9 +434,9 @@ func BenchmarkIntToStr(b *testing.B) {
func load(t *testing.T, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
+ require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
+ require.NoError(t, err)
return &o
}
diff --git a/internal/render/hpa.go b/internal/render/hpa.go
index 44400322..65e748a1 100644
--- a/internal/render/hpa.go
+++ b/internal/render/hpa.go
@@ -17,7 +17,7 @@ type HorizontalPodAutoscaler struct {
}
// ColorerFunc colors a resource row.
-func (hpa HorizontalPodAutoscaler) ColorerFunc() model1.ColorerFunc {
+func (*HorizontalPodAutoscaler) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
diff --git a/internal/render/img_scan.go b/internal/render/img_scan.go
index 3bbe4189..68d17e5d 100644
--- a/internal/render/img_scan.go
+++ b/internal/render/img_scan.go
@@ -25,7 +25,7 @@ type ImageScan struct {
}
// ColorerFunc colors a resource row.
-func (c ImageScan) ColorerFunc() model1.ColorerFunc {
+func (ImageScan) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -54,7 +54,7 @@ func (c ImageScan) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (ImageScan) Header(ns string) model1.Header {
+func (ImageScan) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "SEVERITY"},
model1.HeaderColumn{Name: "VULNERABILITY"},
@@ -67,7 +67,7 @@ func (ImageScan) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (is ImageScan) Render(o interface{}, name string, r *model1.Row) error {
+func (ImageScan) Render(o any, _ string, r *model1.Row) error {
res, ok := o.(ImageScanRes)
if !ok {
return fmt.Errorf("expected ImageScanRes, but got %T", o)
diff --git a/internal/render/job.go b/internal/render/job.go
index d4be14e2..6778c590 100644
--- a/internal/render/job.go
+++ b/internal/render/job.go
@@ -17,6 +17,19 @@ import (
"k8s.io/apimachinery/pkg/util/duration"
)
+var defaultJOBHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "COMPLETIONS"},
+ model1.HeaderColumn{Name: "DURATION"},
+ model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Job renders a K8s Job to screen.
type Job struct {
Base
@@ -24,29 +37,14 @@ type Job struct {
// Header returns a header row.
func (j Job) Header(_ string) model1.Header {
- return j.doHeader(j.defaultHeader())
-}
-
-func (Job) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "COMPLETIONS"},
- model1.HeaderColumn{Name: "DURATION"},
- model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return j.doHeader(defaultJOBHeader)
}
// Render renders a K8s resource to screen.
-func (j Job) Render(o interface{}, ns string, row *model1.Row) error {
+func (j Job) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Job, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := j.defaultRow(raw, row); err != nil {
return err
@@ -54,8 +52,7 @@ func (j Job) Render(o interface{}, ns string, row *model1.Row) error {
if j.specs.isEmpty() {
return nil
}
-
- cols, err := j.specs.realize(raw, j.defaultHeader(), row)
+ cols, err := j.specs.realize(raw, defaultJOBHeader, row)
if err != nil {
return err
}
@@ -70,28 +67,28 @@ func (j Job) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
if err != nil {
return err
}
- ready := toCompletion(job.Spec, job.Status)
+ ready := toCompletion(&job.Spec, &job.Status)
- cc, ii := toContainers(job.Spec.Template.Spec)
+ cc, ii := toContainers(&job.Spec.Template.Spec)
- r.ID = client.MetaFQN(job.ObjectMeta)
+ r.ID = client.MetaFQN(&job.ObjectMeta)
r.Fields = model1.Fields{
job.Namespace,
job.Name,
- computeVulScore(job.ObjectMeta, &job.Spec.Template.Spec),
+ computeVulScore(job.Namespace, job.Labels, &job.Spec.Template.Spec),
ready,
- toDuration(job.Status),
- jobSelector(job.Spec),
+ toDuration(&job.Status),
+ jobSelector(&job.Spec),
cc,
ii,
- AsStatus(j.diagnose(ready, job.Status)),
+ AsStatus(j.diagnose(ready, &job.Status)),
ToAge(job.GetCreationTimestamp()),
}
return nil
}
-func (Job) diagnose(ready string, status batchv1.JobStatus) error {
+func (Job) diagnose(ready string, status *batchv1.JobStatus) error {
tokens := strings.Split(ready, "/")
if tokens[0] != tokens[1] && status.Failed > 0 {
return fmt.Errorf("%d pods failed", status.Failed)
@@ -104,7 +101,7 @@ func (Job) diagnose(ready string, status batchv1.JobStatus) error {
const maxShow = 2
-func toContainers(p v1.PodSpec) (string, string) {
+func toContainers(p *v1.PodSpec) (containers, images string) {
cc, ii := parseContainers(p.InitContainers)
cn, ci := parseContainers(p.Containers)
@@ -122,15 +119,15 @@ func toContainers(p v1.PodSpec) (string, string) {
}
func parseContainers(cos []v1.Container) (nn, ii []string) {
- for _, co := range cos {
- nn = append(nn, co.Name)
- ii = append(ii, co.Image)
+ nn, ii = make([]string, 0, len(cos)), make([]string, 0, len(cos))
+ for i := range cos {
+ nn, ii = append(nn, cos[i].Name), append(ii, cos[i].Image)
}
return nn, ii
}
-func toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) {
+func toCompletion(spec *batchv1.JobSpec, status *batchv1.JobStatus) (s string) {
if spec.Completions != nil {
return strconv.Itoa(int(status.Succeeded)) + "/" + strconv.Itoa(int(*spec.Completions))
}
@@ -147,7 +144,7 @@ func toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) {
return strconv.Itoa(int(status.Succeeded)) + "/1"
}
-func toDuration(status batchv1.JobStatus) string {
+func toDuration(status *batchv1.JobStatus) string {
if status.StartTime == nil || status.CompletionTime == nil {
return MissingValue
}
diff --git a/internal/render/job_test.go b/internal/render/job_test.go
index 028a4ddf..fc2c408f 100644
--- a/internal/render/job_test.go
+++ b/internal/render/job_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestJobRender(t *testing.T) {
c := render.Job{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "job"), "", &r))
+ require.NoError(t, c.Render(load(t, "job"), "", &r))
assert.Equal(t, "default/hello-1567179180", r.ID)
assert.Equal(t, model1.Fields{"default", "hello-1567179180", "0", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8])
}
diff --git a/internal/render/node.go b/internal/render/node.go
index 94e2087a..fc24e739 100644
--- a/internal/render/node.go
+++ b/internal/render/node.go
@@ -25,6 +25,29 @@ const (
labelNodeRoleSuffix = "kubernetes.io/role"
)
+var defaultNOHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "ROLE"},
+ model1.HeaderColumn{Name: "ARCH", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "TAINTS"},
+ model1.HeaderColumn{Name: "VERSION"},
+ model1.HeaderColumn{Name: "OS-IMAGE", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "KERNEL", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "INTERNAL-IP", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "EXTERNAL-IP", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "PODS", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "CPU/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "MEM/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Node renders a K8s Node to screen.
type Node struct {
Base
@@ -32,39 +55,14 @@ type Node struct {
// Header returns a header row.
func (n Node) Header(_ string) model1.Header {
- return n.doHeader(n.defaultHeader())
-}
-
-func (Node) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "ROLE"},
- model1.HeaderColumn{Name: "ARCH", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "TAINTS"},
- model1.HeaderColumn{Name: "VERSION"},
- model1.HeaderColumn{Name: "OS-IMAGE", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "KERNEL", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "INTERNAL-IP", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "EXTERNAL-IP", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "PODS", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "CPU/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "MEM/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return n.doHeader(defaultNOHeader)
}
// Render renders a K8s resource to screen.
-func (n Node) Render(o interface{}, ns string, row *model1.Row) error {
+func (n Node) Render(o any, _ string, row *model1.Row) error {
nwm, ok := o.(*NodeWithMetrics)
if !ok {
- return fmt.Errorf("expected PodWithMetrics, but got %T", o)
+ return fmt.Errorf("expected NodeWithMetrics, but got %T", o)
}
if err := n.defaultRow(nwm, row); err != nil {
return err
@@ -73,7 +71,7 @@ func (n Node) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
- cols, err := n.specs.realize(nwm.Raw, n.defaultHeader(), row)
+ cols, err := n.specs.realize(nwm.Raw, defaultNOHeader, row)
if err != nil {
return err
}
@@ -168,7 +166,7 @@ type NodeWithMetrics struct {
}
// GetObjectKind returns a schema object.
-func (n *NodeWithMetrics) GetObjectKind() schema.ObjectKind {
+func (*NodeWithMetrics) GetObjectKind() schema.ObjectKind {
return nil
}
@@ -196,7 +194,7 @@ func nodeRoles(node *v1.Node, res []string) {
for k, v := range node.Labels {
switch {
case strings.HasPrefix(k, labelNodeRolePrefix):
- if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 {
+ if role := strings.TrimPrefix(k, labelNodeRolePrefix); role != "" {
res[index] = role
index++
}
@@ -209,14 +207,14 @@ func nodeRoles(node *v1.Node, res []string) {
}
}
- if empty(res) {
+ if blank(res) {
res[index] = MissingValue
}
}
func getIPs(addrs []v1.NodeAddress) (iIP, eIP string) {
for _, a := range addrs {
- // nolint:exhaustive
+ //nolint:exhaustive
switch a.Type {
case v1.NodeExternalIP:
eIP = a.Address
@@ -257,12 +255,3 @@ func status(conds []v1.NodeCondition, exempt bool, res []string) {
res[index] = "SchedulingDisabled"
}
}
-
-func empty(s []string) bool {
- for _, v := range s {
- if len(v) != 0 {
- return false
- }
- }
- return true
-}
diff --git a/internal/render/node_test.go b/internal/render/node_test.go
index 156b054c..50d33b60 100644
--- a/internal/render/node_test.go
+++ b/internal/render/node_test.go
@@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
@@ -22,7 +23,7 @@ func TestNodeRender(t *testing.T) {
var no render.Node
r := model1.NewRow(14)
err := no.Render(&pom, "", &r)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "minikube", r.ID)
e := model1.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "Buildroot 2018.05.3", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"}
@@ -30,15 +31,18 @@ func TestNodeRender(t *testing.T) {
}
func BenchmarkNodeRender(b *testing.B) {
- pom := render.NodeWithMetrics{
- Raw: load(b, "no"),
- MX: makeNodeMX("n1", "10m", "10Mi"),
- }
- var no render.Node
- r := model1.NewRow(14)
+ var (
+ no render.Node
+ r = model1.NewRow(14)
+ pom = render.NodeWithMetrics{
+ Raw: load(b, "no"),
+ MX: makeNodeMX("n1", "10m", "10Mi"),
+ }
+ )
+
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for range b.N {
_ = no.Render(&pom, "", &r)
}
}
diff --git a/internal/render/np.go b/internal/render/np.go
index 364261e8..c54ac7f2 100644
--- a/internal/render/np.go
+++ b/internal/render/np.go
@@ -15,6 +15,21 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultNPHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "POD-SELECTOR"},
+ model1.HeaderColumn{Name: "ING-SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "ING-PORTS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "ING-BLOCK", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "EGR-SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "EGR-PORTS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "EGR-BLOCK", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// NetworkPolicy renders a K8s NetworkPolicy to screen.
type NetworkPolicy struct {
Base
@@ -22,31 +37,14 @@ type NetworkPolicy struct {
// Header returns a header row.
func (p NetworkPolicy) Header(_ string) model1.Header {
- return p.doHeader(p.defaultHeader())
-}
-
-func (NetworkPolicy) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "POD-SELECTOR"},
- model1.HeaderColumn{Name: "ING-SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "ING-PORTS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "ING-BLOCK", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "EGR-SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "EGR-PORTS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "EGR-BLOCK", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return p.doHeader(defaultNPHeader)
}
// Render renders a K8s resource to screen.
-func (p NetworkPolicy) Render(o interface{}, ns string, row *model1.Row) error {
+func (p NetworkPolicy) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected NetworkPolicy, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := p.defaultRow(raw, row); err != nil {
return err
@@ -55,7 +53,7 @@ func (p NetworkPolicy) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
- cols, err := p.specs.realize(raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(raw, defaultNPHeader, row)
if err != nil {
return err
}
@@ -64,7 +62,7 @@ func (p NetworkPolicy) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
-func (n NetworkPolicy) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
+func (NetworkPolicy) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
var np netv1.NetworkPolicy
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &np)
if err != nil {
@@ -81,7 +79,7 @@ func (n NetworkPolicy) defaultRow(raw *unstructured.Unstructured, r *model1.Row)
if len(np.Spec.PodSelector.MatchExpressions) > 0 {
podSel += "::" + expToStr(np.Spec.PodSelector.MatchExpressions)
}
- r.ID = client.MetaFQN(np.ObjectMeta)
+ r.ID = client.MetaFQN(&np.ObjectMeta)
r.Fields = model1.Fields{
np.Namespace,
np.Name,
@@ -102,7 +100,7 @@ func (n NetworkPolicy) defaultRow(raw *unstructured.Unstructured, r *model1.Row)
// Helpers...
-func ingress(ii []netv1.NetworkPolicyIngressRule) (string, string, string) {
+func ingress(ii []netv1.NetworkPolicyIngressRule) (port, selector, block string) {
var ports, sels, blocks []string
for _, i := range ii {
if p := portsToStr(i.Ports); p != "" {
@@ -119,7 +117,7 @@ func ingress(ii []netv1.NetworkPolicyIngressRule) (string, string, string) {
return strings.Join(ports, ","), strings.Join(sels, ","), strings.Join(blocks, ",")
}
-func egress(ee []netv1.NetworkPolicyEgressRule) (string, string, string) {
+func egress(ee []netv1.NetworkPolicyEgressRule) (port, selector, block string) {
var ports, sels, blocks []string
for _, e := range ee {
if p := portsToStr(e.Ports); p != "" {
@@ -151,7 +149,7 @@ func portsToStr(pp []netv1.NetworkPolicyPort) string {
return strings.Join(ports, ",")
}
-func peersToStr(pp []netv1.NetworkPolicyPeer) (string, string) {
+func peersToStr(pp []netv1.NetworkPolicyPeer) (selector, ip string) {
sels := make([]string, 0, len(pp))
ips := make([]string, 0, len(pp))
for _, p := range pp {
diff --git a/internal/render/np_test.go b/internal/render/np_test.go
index 28942782..b2839d27 100644
--- a/internal/render/np_test.go
+++ b/internal/render/np_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNetworkPolicyRender(t *testing.T) {
c := render.NetworkPolicy{}
r := model1.NewRow(9)
- assert.NoError(t, c.Render(load(t, "np"), "", &r))
+ require.NoError(t, c.Render(load(t, "np"), "", &r))
assert.Equal(t, "default/fred", r.ID)
assert.Equal(t, model1.Fields{"default", "fred", "app=nginx", "ns:app=blee,po:app=fred", "TCP:6379", "172.17.0.0/16[172.17.1.0/24,172.17.3.0/24...]", "", "TCP:5978", "10.0.0.0/24"}, r.Fields[:9])
}
diff --git a/internal/render/ns.go b/internal/render/ns.go
index 6438c945..e0fd0f29 100644
--- a/internal/render/ns.go
+++ b/internal/render/ns.go
@@ -16,13 +16,21 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultNSHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Namespace renders a K8s Namespace to screen.
type Namespace struct {
Base
}
// ColorerFunc colors a resource row.
-func (n Namespace) ColorerFunc() model1.ColorerFunc {
+func (Namespace) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
if c == model1.ErrColor {
@@ -41,24 +49,14 @@ func (n Namespace) ColorerFunc() model1.ColorerFunc {
// Header returns a header row.
func (n Namespace) Header(_ string) model1.Header {
- return n.doHeader(n.defaultHeader())
-}
-
-func (Namespace) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return n.doHeader(defaultNSHeader)
}
// Render renders a K8s resource to screen.
-func (n Namespace) Render(o interface{}, ns string, row *model1.Row) error {
+func (n Namespace) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected NetworkPolicy, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := n.defaultRow(raw, row); err != nil {
return err
@@ -67,7 +65,7 @@ func (n Namespace) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
- cols, err := n.specs.realize(raw, n.defaultHeader(), row)
+ cols, err := n.specs.realize(raw, defaultNSHeader, row)
if err != nil {
return err
}
@@ -83,7 +81,7 @@ func (n Namespace) defaultRow(raw *unstructured.Unstructured, r *model1.Row) err
return err
}
- r.ID = client.MetaFQN(ns.ObjectMeta)
+ r.ID = client.MetaFQN(&ns.ObjectMeta)
r.Fields = model1.Fields{
ns.Name,
string(ns.Status.Phase),
diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go
index ad4337bd..8f541b28 100644
--- a/internal/render/ns_test.go
+++ b/internal/render/ns_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/render"
"github.com/derailed/tcell/v2"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNSColorer(t *testing.T) {
@@ -73,7 +74,7 @@ func TestNamespaceRender(t *testing.T) {
c := render.Namespace{}
r := model1.NewRow(3)
- assert.NoError(t, c.Render(load(t, "ns"), "-", &r))
+ require.NoError(t, c.Render(load(t, "ns"), "-", &r))
assert.Equal(t, "-/kube-system", r.ID)
assert.Equal(t, model1.Fields{"kube-system", "Active"}, r.Fields[:2])
}
diff --git a/internal/render/pdb.go b/internal/render/pdb.go
index f5ff6bc5..35704f60 100644
--- a/internal/render/pdb.go
+++ b/internal/render/pdb.go
@@ -16,6 +16,20 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
+var defaultPDBHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "MIN-AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "MAX-UNAVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "ALLOWED-DISRUPTIONS", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "EXPECTED", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// PodDisruptionBudget renders a K8s PodDisruptionBudget to screen.
type PodDisruptionBudget struct {
Base
@@ -23,30 +37,14 @@ type PodDisruptionBudget struct {
// Header returns a header row.
func (p PodDisruptionBudget) Header(_ string) model1.Header {
- return p.doHeader(p.defaultHeader())
-}
-
-func (PodDisruptionBudget) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "MIN-AVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "MAX-UNAVAILABLE", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "ALLOWED-DISRUPTIONS", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "EXPECTED", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return p.doHeader(defaultPDBHeader)
}
// Render renders a K8s resource to screen.
-func (p PodDisruptionBudget) Render(o interface{}, ns string, row *model1.Row) error {
+func (p PodDisruptionBudget) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected PodDisruptionBudget, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := p.defaultRow(raw, row); err != nil {
return err
@@ -55,7 +53,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, row *model1.Row) e
return nil
}
- cols, err := p.specs.realize(raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(raw, defaultPDBHeader, row)
if err != nil {
return err
}
@@ -71,7 +69,7 @@ func (p PodDisruptionBudget) defaultRow(raw *unstructured.Unstructured, r *model
return err
}
- r.ID = client.MetaFQN(pdb.ObjectMeta)
+ r.ID = client.MetaFQN(&pdb.ObjectMeta)
r.Fields = model1.Fields{
pdb.Namespace,
pdb.Name,
@@ -89,13 +87,14 @@ func (p PodDisruptionBudget) defaultRow(raw *unstructured.Unstructured, r *model
return nil
}
-func (PodDisruptionBudget) diagnose(min *intstr.IntOrString, healthy int32) error {
- if min == nil {
+func (PodDisruptionBudget) diagnose(v *intstr.IntOrString, healthy int32) error {
+ if v == nil {
return nil
}
- if min.IntVal > healthy {
- return fmt.Errorf("expected %d but got %d", min.IntVal, healthy)
+ if v.IntVal > healthy {
+ return fmt.Errorf("expected %d but got %d", v.IntVal, healthy)
}
+
return nil
}
diff --git a/internal/render/pdb_test.go b/internal/render/pdb_test.go
index 9567c487..757eedae 100644
--- a/internal/render/pdb_test.go
+++ b/internal/render/pdb_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPodDisruptionBudgetRender(t *testing.T) {
c := render.PodDisruptionBudget{}
r := model1.NewRow(9)
- assert.NoError(t, c.Render(load(t, "pdb"), "", &r))
+ require.NoError(t, c.Render(load(t, "pdb"), "", &r))
assert.Equal(t, "default/fred", r.ID)
assert.Equal(t, model1.Fields{"default", "fred", "2", render.NAValue, "0", "0", "2", "0"}, r.Fields[:8])
}
diff --git a/internal/render/pod.go b/internal/render/pod.go
index 6e93b1d2..0c8b0d2d 100644
--- a/internal/render/pod.go
+++ b/internal/render/pod.go
@@ -7,6 +7,7 @@ import (
"fmt"
"strconv"
"strings"
+ "time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1"
@@ -18,6 +19,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/util/cache"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
@@ -46,20 +48,55 @@ const (
PhaseEvicted = "Evicted"
)
+var defaultPodHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "PF"},
+ model1.HeaderColumn{Name: "READY"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "RESTARTS", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "LAST RESTART", Attrs: model1.Attrs{Align: tview.AlignRight, Time: true, Wide: true}},
+ model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}},
+ model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}},
+ model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "%MEM/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
+ model1.HeaderColumn{Name: "IP"},
+ model1.HeaderColumn{Name: "NODE"},
+ model1.HeaderColumn{Name: "SERVICE-ACCOUNT", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "NOMINATED NODE", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "READINESS GATES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "QOS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
+const (
+ cacheSize = 5_000
+ expiration = 5 * time.Minute
+)
+
// Pod renders a K8s Pod to screen.
type Pod struct {
*Base
+ cache *cache.LRUExpireCache
}
// NewPod returns a new instance.
func NewPod() *Pod {
return &Pod{
- Base: new(Base),
+ Base: new(Base),
+ cache: cache.NewLRUExpireCache(cacheSize),
}
}
// ColorerFunc colors a resource row.
-func (p Pod) ColorerFunc() model1.ColorerFunc {
+func (*Pod) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -90,42 +127,12 @@ func (p Pod) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (p Pod) Header(_ string) model1.Header {
- return p.doHeader(p.defaultHeader())
-}
-
-func (Pod) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "PF"},
- model1.HeaderColumn{Name: "READY"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "RESTARTS", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "LAST RESTART", Attrs: model1.Attrs{Align: tview.AlignRight, Time: true, Wide: true}},
- model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}},
- model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}},
- model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "%MEM/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
- model1.HeaderColumn{Name: "IP"},
- model1.HeaderColumn{Name: "NODE"},
- model1.HeaderColumn{Name: "SERVICE-ACCOUNT", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "NOMINATED NODE", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "READINESS GATES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "QOS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+func (p *Pod) Header(string) model1.Header {
+ return p.doHeader(defaultPodHeader)
}
// Render renders a K8s resource to screen.
-func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
+func (p *Pod) Render(o any, _ string, row *model1.Row) error {
pwm, ok := o.(*PodWithMetrics)
if !ok {
return fmt.Errorf("expected PodWithMetrics, but got %T", o)
@@ -136,9 +143,7 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
if p.specs.isEmpty() {
return nil
}
-
- // !BOZO!! Call header 2 times
- cols, err := p.specs.realize(pwm.Raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(pwm.Raw, defaultPodHeader, row)
if err != nil {
return err
}
@@ -147,32 +152,49 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
-func (p Pod) defaultRow(pwm *PodWithMetrics, row *model1.Row) error {
- var po v1.Pod
- if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object, &po); err != nil {
+func (p *Pod) defaultRow(pwm *PodWithMetrics, row *model1.Row) error {
+ var st v1.PodStatus
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object["status"].(map[string]any), &st); err != nil {
return err
}
+ key := pwm.Raw.GetUID()
+ for _, o := range pwm.Raw.GetOwnerReferences() {
+ if o.Controller != nil && *o.Controller {
+ key = o.UID
+ break
+ }
+ }
- ics := po.Status.InitContainerStatuses
- _, _, irc := p.Statuses(ics)
- cs := po.Status.ContainerStatuses
- cr, _, rc := p.Statuses(cs)
- lr := p.lastRestart(cs)
+ spec := new(v1.PodSpec)
+ if cspec, ok := p.cache.Get(key); ok {
+ spec = cspec.(*v1.PodSpec)
+ } else {
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object["spec"].(map[string]any), spec); err != nil {
+ return err
+ }
+ p.cache.Add(key, spec, expiration)
+ }
+
+ dt := pwm.Raw.GetDeletionTimestamp()
+ _, _, irc, _ := p.Statuses(st.InitContainerStatuses)
+ cr, _, rc, lr := p.Statuses(st.ContainerStatuses)
var ccmx []mv1beta1.ContainerMetrics
if pwm.MX != nil {
ccmx = pwm.MX.Containers
}
- c, r := gatherCoMX(&po.Spec, ccmx)
- phase := p.Phase(&po)
+ c, r := gatherCoMX(spec, ccmx)
+ phase := p.Phase(dt, spec, &st)
- row.ID = client.MetaFQN(po.ObjectMeta)
+ ns, n := pwm.Raw.GetNamespace(), pwm.Raw.GetName()
+
+ row.ID = client.FQN(ns, n)
row.Fields = model1.Fields{
- po.Namespace,
- po.Name,
- computeVulScore(po.ObjectMeta, &po.Spec),
+ ns,
+ n,
+ computeVulScore(ns, pwm.Raw.GetLabels(), spec),
"โ",
- strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)),
+ strconv.Itoa(cr) + "/" + strconv.Itoa(len(spec.Containers)),
phase,
strconv.Itoa(rc + irc),
ToAge(lr),
@@ -184,21 +206,21 @@ func (p Pod) defaultRow(pwm *PodWithMetrics, row *model1.Row) error {
client.ToPercentageStr(c.cpu, r.lcpu),
client.ToPercentageStr(c.mem, r.mem),
client.ToPercentageStr(c.mem, r.lmem),
- na(po.Status.PodIP),
- na(po.Spec.NodeName),
- na(po.Spec.ServiceAccountName),
- asNominated(po.Status.NominatedNodeName),
- asReadinessGate(po),
- p.mapQOS(po.Status.QOSClass),
- mapToStr(po.Labels),
- AsStatus(p.diagnose(phase, cr, len(cs))),
- ToAge(po.GetCreationTimestamp()),
+ na(st.PodIP),
+ na(spec.NodeName),
+ na(spec.ServiceAccountName),
+ asNominated(st.NominatedNodeName),
+ asReadinessGate(spec, &st),
+ p.mapQOS(st.QOSClass),
+ mapToStr(pwm.Raw.GetLabels()),
+ AsStatus(p.diagnose(phase, cr, len(st.ContainerStatuses))),
+ ToAge(pwm.Raw.GetCreationTimestamp()),
}
return nil
}
-func (p Pod) diagnose(phase string, cr, ct int) error {
+func (*Pod) diagnose(phase string, cr, ct int) error {
if phase == Completed {
return nil
}
@@ -222,15 +244,15 @@ func asNominated(n string) string {
return n
}
-func asReadinessGate(pod v1.Pod) string {
- if len(pod.Spec.ReadinessGates) == 0 {
+func asReadinessGate(spec *v1.PodSpec, st *v1.PodStatus) string {
+ if len(spec.ReadinessGates) == 0 {
return MissingValue
}
var trueConditions int
- for _, readinessGate := range pod.Spec.ReadinessGates {
+ for _, readinessGate := range spec.ReadinessGates {
conditionType := readinessGate.ConditionType
- for _, condition := range pod.Status.Conditions {
+ for _, condition := range st.Conditions {
if condition.Type == conditionType {
if condition.Status == "True" {
trueConditions++
@@ -240,7 +262,7 @@ func asReadinessGate(pod v1.Pod) string {
}
}
- return strconv.Itoa(trueConditions) + "/" + strconv.Itoa(len(pod.Spec.ReadinessGates))
+ return strconv.Itoa(trueConditions) + "/" + strconv.Itoa(len(spec.ReadinessGates))
}
// PodWithMetrics represents a pod and its metrics.
@@ -250,7 +272,7 @@ type PodWithMetrics struct {
}
// GetObjectKind returns a schema object.
-func (p *PodWithMetrics) GetObjectKind() schema.ObjectKind {
+func (*PodWithMetrics) GetObjectKind() schema.ObjectKind {
return nil
}
@@ -276,10 +298,10 @@ func gatherCoMX(spec *v1.PodSpec, ccmx []mv1beta1.ContainerMetrics) (c, r metric
return
}
-func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) {
+func cosLimits(cc []v1.Container) (cpuQ, memQ resource.Quantity) {
cpu, mem := new(resource.Quantity), new(resource.Quantity)
- for _, c := range cc {
- limits := c.Resources.Limits
+ for i := range cc {
+ limits := cc[i].Resources.Limits
if len(limits) == 0 {
continue
}
@@ -294,10 +316,10 @@ func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) {
return *cpu, *mem
}
-func cosRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) {
+func cosRequests(cc []v1.Container) (cpuQ, memQ resource.Quantity) {
cpu, mem := new(resource.Quantity), new(resource.Quantity)
- for _, c := range cc {
- co := c
+ for i := range cc {
+ co := cc[i]
rl := containerRequests(&co)
if rl.Cpu() != nil {
cpu.Add(*rl.Cpu())
@@ -310,7 +332,7 @@ func cosRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) {
return *cpu, *mem
}
-func currentRes(ccmx []mv1beta1.ContainerMetrics) (resource.Quantity, resource.Quantity) {
+func currentRes(ccmx []mv1beta1.ContainerMetrics) (cpuQ, memQ resource.Quantity) {
cpu, mem := new(resource.Quantity), new(resource.Quantity)
if ccmx == nil {
return *cpu, *mem
@@ -325,7 +347,7 @@ func currentRes(ccmx []mv1beta1.ContainerMetrics) (resource.Quantity, resource.Q
}
func (*Pod) mapQOS(class v1.PodQOSClass) string {
- // nolint:exhaustive
+ //nolint:exhaustive
switch class {
case v1.PodQOSGuaranteed:
return "GA"
@@ -337,61 +359,54 @@ func (*Pod) mapQOS(class v1.PodQOSClass) string {
}
// Statuses reports current pod container statuses.
-func (*Pod) Statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
- for _, c := range ss {
- if c.State.Terminated != nil {
+func (*Pod) Statuses(cc []v1.ContainerStatus) (cr, ct, rc int, latest metav1.Time) {
+ for i := range cc {
+ if cc[i].State.Terminated != nil {
ct++
}
- if c.Ready {
- cr = cr + 1
+ if cc[i].Ready {
+ cr++
+ }
+ rc += int(cc[i].RestartCount)
+
+ if t := cc[i].LastTerminationState.Terminated; t != nil {
+ ts := cc[i].LastTerminationState.Terminated.FinishedAt
+ if latest.IsZero() || ts.After(latest.Time) {
+ latest = ts
+ }
}
- rc += int(c.RestartCount)
}
return
}
-// lastRestart returns the last container restart time.
-func (*Pod) lastRestart(ss []v1.ContainerStatus) (latest metav1.Time) {
- for _, c := range ss {
- if c.LastTerminationState.Terminated == nil {
- continue
- }
- ts := c.LastTerminationState.Terminated.FinishedAt
- if latest.IsZero() || ts.After(latest.Time) {
- latest = ts
- }
- }
- return
-}
-
// Phase reports the given pod phase.
-func (p *Pod) Phase(po *v1.Pod) string {
- status := string(po.Status.Phase)
- if po.Status.Reason != "" {
- if po.DeletionTimestamp != nil && po.Status.Reason == NodeUnreachablePodReason {
+func (p *Pod) Phase(dt *metav1.Time, spec *v1.PodSpec, st *v1.PodStatus) string {
+ status := string(st.Phase)
+ if st.Reason != "" {
+ if dt != nil && st.Reason == NodeUnreachablePodReason {
return "Unknown"
}
- status = po.Status.Reason
+ status = st.Reason
}
- status, ok := p.initContainerPhase(po, status)
+ status, ok := p.initContainerPhase(spec, st, status)
if ok {
return status
}
- status, ok = p.containerPhase(po.Status, status)
+ status, ok = p.containerPhase(st, status)
if ok && status == Completed {
status = Running
}
- if po.DeletionTimestamp == nil {
+ if dt == nil {
return status
}
return Terminating
}
-func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) {
+func (*Pod) containerPhase(st *v1.PodStatus, status string) (string, bool) {
var running bool
for i := len(st.ContainerStatuses) - 1; i >= 0; i-- {
cs := st.ContainerStatuses[i]
@@ -414,14 +429,15 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) {
return status, running
}
-func (*Pod) initContainerPhase(po *v1.Pod, status string) (string, bool) {
- count := len(po.Spec.InitContainers)
+func (*Pod) initContainerPhase(spec *v1.PodSpec, pst *v1.PodStatus, status string) (string, bool) {
+ count := len(spec.InitContainers)
rs := make(map[string]bool, count)
- for _, c := range po.Spec.InitContainers {
- rs[c.Name] = restartableInitCO(c.RestartPolicy)
+ for i := range spec.InitContainers {
+ co := spec.InitContainers[i]
+ rs[co.Name] = restartableInitCO(co.RestartPolicy)
}
- for i, cs := range po.Status.InitContainerStatuses {
- if s := checkInitContainerStatus(cs, i, count, rs[cs.Name]); s != "" {
+ for i := range pst.InitContainerStatuses {
+ if s := checkInitContainerStatus(&pst.InitContainerStatuses[i], i, count, rs[pst.InitContainerStatuses[i].Name]); s != "" {
return s, true
}
}
@@ -432,7 +448,7 @@ func (*Pod) initContainerPhase(po *v1.Pod, status string) (string, bool) {
// ----------------------------------------------------------------------------
// Helpers..
-func checkInitContainerStatus(cs v1.ContainerStatus, count, initCount int, restartable bool) string {
+func checkInitContainerStatus(cs *v1.ContainerStatus, count, initCount int, restartable bool) string {
switch {
case cs.State.Terminated != nil:
if cs.State.Terminated.ExitCode == 0 {
@@ -486,7 +502,7 @@ func PodStatus(pod *v1.Pod) string {
reason = "Init:" + container.State.Terminated.Reason
}
initializing = true
- case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
+ case container.State.Waiting != nil && container.State.Waiting.Reason != "" && container.State.Waiting.Reason != "PodInitializing":
reason = "Init:" + container.State.Waiting.Reason
initializing = true
default:
@@ -499,17 +515,18 @@ func PodStatus(pod *v1.Pod) string {
var hasRunning bool
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
- if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
+ switch {
+ case container.State.Waiting != nil && container.State.Waiting.Reason != "":
reason = container.State.Waiting.Reason
- } else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
+ case container.State.Terminated != nil && container.State.Terminated.Reason != "":
reason = container.State.Terminated.Reason
- } else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
+ case container.State.Terminated != nil && container.State.Terminated.Reason == "":
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
}
- } else if container.Ready && container.State.Running != nil {
+ case container.Ready && container.State.Running != nil:
hasRunning = true
}
}
@@ -548,7 +565,8 @@ func restartableInitCO(p *v1.ContainerRestartPolicy) bool {
func filterSidecarCO(cc []v1.Container) []v1.Container {
rcc := make([]v1.Container, 0, len(cc))
- for _, c := range cc {
+ for i := range cc {
+ c := cc[i]
if c.RestartPolicy != nil && *c.RestartPolicy == v1.ContainerRestartPolicyAlways {
rcc = append(rcc, c)
}
diff --git a/internal/render/pod_int_test.go b/internal/render/pod_int_test.go
index d135aa5d..e9215318 100644
--- a/internal/render/pod_int_test.go
+++ b/internal/render/pod_int_test.go
@@ -17,7 +17,7 @@ import (
)
func Test_checkInitContainerStatus(t *testing.T) {
- true := true
+ trueVal := true
uu := map[string]struct {
status v1.ContainerStatus
e string
@@ -30,7 +30,7 @@ func Test_checkInitContainerStatus(t *testing.T) {
"restart": {
status: v1.ContainerStatus{
Name: "ic1",
- Started: &true,
+ Started: &trueVal,
State: v1.ContainerState{},
},
restart: true,
@@ -39,7 +39,7 @@ func Test_checkInitContainerStatus(t *testing.T) {
"no-restart": {
status: v1.ContainerStatus{
Name: "ic1",
- Started: &true,
+ Started: &trueVal,
State: v1.ContainerState{},
},
e: "Init:0/0",
@@ -125,7 +125,7 @@ func Test_checkInitContainerStatus(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, checkInitContainerStatus(u.status, u.count, u.total, u.restart))
+ assert.Equal(t, u.e, checkInitContainerStatus(&u.status, u.count, u.total, u.restart))
})
}
}
@@ -267,7 +267,7 @@ func Test_containerPhase(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- s, ok := p.containerPhase(u.status, "")
+ s, ok := p.containerPhase(&u.status, "")
assert.Equal(t, u.ok, ok)
assert.Equal(t, u.e, s)
})
@@ -427,7 +427,8 @@ func Test_lastRestart(t *testing.T) {
var p Pod
for name, u := range uu {
t.Run(name, func(t *testing.T) {
- assert.Equal(t, u.expected, p.lastRestart(u.containerStatuses))
+ _, _, _, lr := p.Statuses(u.containerStatuses)
+ assert.Equal(t, u.expected, lr)
})
}
}
@@ -618,7 +619,7 @@ func makeRes(c, m string) v1.ResourceList {
}
}
-func makeCoMX(n string, c, m string) mv1beta1.ContainerMetrics {
+func makeCoMX(n, c, m string) mv1beta1.ContainerMetrics {
return mv1beta1.ContainerMetrics{
Name: n,
Usage: makeRes(c, m),
diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go
index 2c9c96c6..6da164dd 100644
--- a/internal/render/pod_test.go
+++ b/internal/render/pod_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/render"
"github.com/derailed/tcell/v2"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
res "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -160,7 +161,7 @@ func TestPodRender(t *testing.T) {
po := render.NewPod()
r := model1.NewRow(14)
err := po.Render(&pom, "", &r)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "default/nginx", r.ID)
e := model1.Fields{"default", "nginx", "0", "โ", "1/1", "Running", "0", "", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "default", ""}
@@ -177,7 +178,7 @@ func BenchmarkPodRender(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for range b.N {
_ = po.Render(&pom, "", &r)
}
}
@@ -191,7 +192,7 @@ func TestPodInitRender(t *testing.T) {
po := render.NewPod()
r := model1.NewRow(14)
err := po.Render(&pom, "", &r)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "default/nginx", r.ID)
e := model1.Fields{"default", "nginx", "0", "โ", "1/1", "Init:0/1", "0", "", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "default", ""}
@@ -207,7 +208,7 @@ func TestPodSidecarRender(t *testing.T) {
po := render.NewPod()
r := model1.NewRow(14)
err := po.Render(&pom, "", &r)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Equal(t, "default/sleep", r.ID)
e := model1.Fields{"default", "sleep", "0", "โ", "1/1", "Running", "0", "", "100", "40", "50:250", "50:80", "200", "40", "80", "50", "10.244.0.8", "kind-control-plane", "default", ""}
@@ -655,7 +656,7 @@ func TestCheckPhase(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, p.Phase(&u.pod))
+ assert.Equal(t, u.e, p.Phase(u.pod.DeletionTimestamp, &u.pod.Spec, &u.pod.Status))
})
}
}
diff --git a/internal/render/policy.go b/internal/render/policy.go
index 2e8297a6..1b809943 100644
--- a/internal/render/policy.go
+++ b/internal/render/policy.go
@@ -37,13 +37,13 @@ type Policy struct {
// ColorerFunc colors a resource row.
func (Policy) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorMediumSpringGreen
}
}
// Header returns a header row.
-func (Policy) Header(ns string) model1.Header {
+func (Policy) Header(string) model1.Header {
h := model1.Header{
model1.HeaderColumn{Name: "NAMESPACE"},
model1.HeaderColumn{Name: "NAME"},
@@ -57,8 +57,8 @@ func (Policy) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (Policy) Render(o interface{}, gvr string, r *model1.Row) error {
- p, ok := o.(PolicyRes)
+func (Policy) Render(o any, _ string, r *model1.Row) error {
+ p, ok := o.(*PolicyRes)
if !ok {
return fmt.Errorf("expecting PolicyRes but got %T", o)
}
@@ -102,8 +102,8 @@ type PolicyRes struct {
}
// NewPolicyRes returns a new policy.
-func NewPolicyRes(ns, binding, res, grp string, vv []string) PolicyRes {
- return PolicyRes{
+func NewPolicyRes(ns, binding, res, grp string, vv []string) *PolicyRes {
+ return &PolicyRes{
Namespace: ns,
Binding: binding,
Resource: res,
@@ -113,13 +113,14 @@ func NewPolicyRes(ns, binding, res, grp string, vv []string) PolicyRes {
}
// GR returns the group/resource path.
-func (p PolicyRes) GR() string {
+func (p *PolicyRes) GR() string {
return p.Group + "/" + p.Resource
}
-func (p PolicyRes) Merge(p1 PolicyRes) (PolicyRes, error) {
+// Merge merges two policies.
+func (p *PolicyRes) Merge(p1 *PolicyRes) (*PolicyRes, error) {
if p.GR() != p1.GR() {
- return PolicyRes{}, fmt.Errorf("policy mismatch %s vs %s", p.GR(), p1.GR())
+ return nil, fmt.Errorf("policy mismatch %s vs %s", p.GR(), p1.GR())
}
for _, v := range p1.Verbs {
@@ -131,7 +132,7 @@ func (p PolicyRes) Merge(p1 PolicyRes) (PolicyRes, error) {
return p, nil
}
-func (p PolicyRes) hasVerb(v1 string) bool {
+func (p *PolicyRes) hasVerb(v1 string) bool {
for _, v := range p.Verbs {
if v == v1 {
return true
@@ -142,20 +143,20 @@ func (p PolicyRes) hasVerb(v1 string) bool {
}
// GetObjectKind returns a schema object.
-func (p PolicyRes) GetObjectKind() schema.ObjectKind {
+func (*PolicyRes) GetObjectKind() schema.ObjectKind {
return nil
}
// DeepCopyObject returns a container copy.
-func (p PolicyRes) DeepCopyObject() runtime.Object {
+func (p *PolicyRes) DeepCopyObject() runtime.Object {
return p
}
// Policies represents a collection of RBAC policies.
-type Policies []PolicyRes
+type Policies []*PolicyRes
// Upsert adds a new policy.
-func (pp Policies) Upsert(p PolicyRes) Policies {
+func (pp Policies) Upsert(p *PolicyRes) Policies {
idx, ok := pp.find(p.GR())
if !ok {
return append(pp, p)
diff --git a/internal/render/policy_test.go b/internal/render/policy_test.go
index b0770d14..af5e2fb6 100644
--- a/internal/render/policy_test.go
+++ b/internal/render/policy_test.go
@@ -10,11 +10,12 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPolicyResMerge(t *testing.T) {
uu := map[string]struct {
- p1, p2, e render.PolicyRes
+ p1, p2, e *render.PolicyRes
err error
}{
"simple": {
@@ -58,7 +59,7 @@ func TestPolicyRender(t *testing.T) {
Verbs: []string{"get", "list", "watch"},
}
- assert.Nil(t, p.Render(o, "fred", &r))
+ require.NoError(t, p.Render(&o, "fred", &r))
assert.Equal(t, "blee/res", r.ID)
assert.Equal(t, model1.Fields{
"blee",
diff --git a/internal/render/port_forward_test.go b/internal/render/port_forward_test.go
index 20e0683a..e5449f95 100644
--- a/internal/render/port_forward_test.go
+++ b/internal/render/port_forward_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPortForwardRender(t *testing.T) {
@@ -25,7 +26,7 @@ func TestPortForwardRender(t *testing.T) {
var p render.PortForward
var r model1.Row
- assert.Nil(t, p.Render(o, "fred", &r))
+ require.NoError(t, p.Render(o, "fred", &r))
assert.Equal(t, "blee/fred", r.ID)
assert.Equal(t, model1.Fields{
"blee",
@@ -43,30 +44,30 @@ func TestPortForwardRender(t *testing.T) {
type fwd struct{}
-func (f fwd) ID() string {
+func (fwd) ID() string {
return "blee/fred"
}
-func (f fwd) Path() string {
+func (fwd) Path() string {
return "blee/fred"
}
-func (f fwd) Container() string {
+func (fwd) Container() string {
return "co"
}
-func (f fwd) Port() string {
+func (fwd) Port() string {
return "p1:p2"
}
-func (f fwd) Active() bool {
+func (fwd) Active() bool {
return true
}
-func (f fwd) Age() time.Time {
+func (fwd) Age() time.Time {
return testTime()
}
-func (f fwd) Address() string {
+func (fwd) Address() string {
return ""
}
diff --git a/internal/render/portforward.go b/internal/render/portforward.go
index c128c2f2..dfb5e3e7 100644
--- a/internal/render/portforward.go
+++ b/internal/render/portforward.go
@@ -44,13 +44,13 @@ type PortForward struct {
// ColorerFunc colors a resource row.
func (PortForward) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorSkyblue
}
}
// Header returns a header row.
-func (PortForward) Header(ns string) model1.Header {
+func (PortForward) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAMESPACE"},
model1.HeaderColumn{Name: "NAME"},
@@ -65,7 +65,7 @@ func (PortForward) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (f PortForward) Render(o interface{}, gvr string, r *model1.Row) error {
+func (PortForward) Render(o any, _ string, r *model1.Row) error {
pf, ok := o.(ForwardRes)
if !ok {
return fmt.Errorf("expecting a ForwardRes but got %T", o)
@@ -127,7 +127,7 @@ type ForwardRes struct {
}
// GetObjectKind returns a schema object.
-func (f ForwardRes) GetObjectKind() schema.ObjectKind {
+func (ForwardRes) GetObjectKind() schema.ObjectKind {
return nil
}
diff --git a/internal/render/pv.go b/internal/render/pv.go
index c52add5e..e4b613a3 100644
--- a/internal/render/pv.go
+++ b/internal/render/pv.go
@@ -24,7 +24,7 @@ type PersistentVolume struct {
}
// ColorerFunc colors a resource row.
-func (p PersistentVolume) ColorerFunc() model1.ColorerFunc {
+func (PersistentVolume) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -49,41 +49,37 @@ func (p PersistentVolume) ColorerFunc() model1.ColorerFunc {
// Header returns a header row.
func (p PersistentVolume) Header(_ string) model1.Header {
- return p.doHeader(p.defaultHeader())
+ return p.doHeader(defaultPVHeader)
}
-func (PersistentVolume) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "CAPACITY", Attrs: model1.Attrs{Capacity: true}},
- model1.HeaderColumn{Name: "ACCESS MODES"},
- model1.HeaderColumn{Name: "RECLAIM POLICY"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "CLAIM"},
- model1.HeaderColumn{Name: "STORAGECLASS"},
- model1.HeaderColumn{Name: "REASON"},
- model1.HeaderColumn{Name: "VOLUMEMODE", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultPVHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "CAPACITY", Attrs: model1.Attrs{Capacity: true}},
+ model1.HeaderColumn{Name: "ACCESS MODES"},
+ model1.HeaderColumn{Name: "RECLAIM POLICY"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "CLAIM"},
+ model1.HeaderColumn{Name: "STORAGECLASS"},
+ model1.HeaderColumn{Name: "REASON"},
+ model1.HeaderColumn{Name: "VOLUMEMODE", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (p PersistentVolume) Render(o interface{}, ns string, row *model1.Row) error {
+func (p PersistentVolume) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected PersistentVolume, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := p.defaultRow(raw, row); err != nil {
return err
}
if p.specs.isEmpty() {
return nil
}
-
- cols, err := p.specs.realize(raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(raw, defaultPVHeader, row)
if err != nil {
return err
}
@@ -114,7 +110,7 @@ func (p PersistentVolume) defaultRow(raw *unstructured.Unstructured, r *model1.R
size := pv.Spec.Capacity[v1.ResourceStorage]
- r.ID = client.MetaFQN(pv.ObjectMeta)
+ r.ID = client.MetaFQN(&pv.ObjectMeta)
r.Fields = model1.Fields{
pv.Name,
size.String(),
diff --git a/internal/render/pv_test.go b/internal/render/pv_test.go
index 615fd8b6..2a8bac49 100644
--- a/internal/render/pv_test.go
+++ b/internal/render/pv_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPersistentVolumeRender(t *testing.T) {
c := render.PersistentVolume{}
r := model1.NewRow(9)
- assert.NoError(t, c.Render(load(t, "pv"), "-", &r))
+ require.NoError(t, c.Render(load(t, "pv"), "-", &r))
assert.Equal(t, "-/pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", r.ID)
assert.Equal(t, model1.Fields{"pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", "1Gi", "RWO", "Delete", "Bound", "default/www-nginx-sts-1", "standard"}, r.Fields[:7])
}
@@ -24,7 +25,7 @@ func TestTerminatingPersistentVolumeRender(t *testing.T) {
c := render.PersistentVolume{}
r := model1.NewRow(9)
- assert.NoError(t, c.Render(load(t, "pv_terminating"), "-", &r))
+ require.NoError(t, c.Render(load(t, "pv_terminating"), "-", &r))
assert.Equal(t, "-/pvc-a4d86f51-916c-476b-83af-b551c91a8ac0", r.ID)
assert.Equal(t, model1.Fields{"pvc-a4d86f51-916c-476b-83af-b551c91a8ac0", "1Gi", "RWO", "Delete", "Terminating", "default/www-nginx-sts-2", "standard"}, r.Fields[:7])
}
diff --git a/internal/render/pvc.go b/internal/render/pvc.go
index bb561c94..1e8181c3 100644
--- a/internal/render/pvc.go
+++ b/internal/render/pvc.go
@@ -13,6 +13,19 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultPVCHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "VOLUME"},
+ model1.HeaderColumn{Name: "CAPACITY", Attrs: model1.Attrs{Capacity: true}},
+ model1.HeaderColumn{Name: "ACCESS MODES"},
+ model1.HeaderColumn{Name: "STORAGECLASS"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// PersistentVolumeClaim renders a K8s PersistentVolumeClaim to screen.
type PersistentVolumeClaim struct {
Base
@@ -20,29 +33,14 @@ type PersistentVolumeClaim struct {
// Header returns a header row.
func (p PersistentVolumeClaim) Header(_ string) model1.Header {
- return p.doHeader(p.defaultHeader())
-}
-
-func (PersistentVolumeClaim) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "VOLUME"},
- model1.HeaderColumn{Name: "CAPACITY", Attrs: model1.Attrs{Capacity: true}},
- model1.HeaderColumn{Name: "ACCESS MODES"},
- model1.HeaderColumn{Name: "STORAGECLASS"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return p.doHeader(defaultPVCHeader)
}
// Render renders a K8s resource to screen.
-func (p PersistentVolumeClaim) Render(o interface{}, ns string, row *model1.Row) error {
+func (p PersistentVolumeClaim) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected PersistentVolumeClaim, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
if err := p.defaultRow(raw, row); err != nil {
@@ -52,7 +50,7 @@ func (p PersistentVolumeClaim) Render(o interface{}, ns string, row *model1.Row)
return nil
}
- cols, err := p.specs.realize(raw, p.defaultHeader(), row)
+ cols, err := p.specs.realize(raw, defaultPVCHeader, row)
if err != nil {
return err
}
@@ -87,7 +85,7 @@ func (p PersistentVolumeClaim) defaultRow(raw *unstructured.Unstructured, r *mod
}
}
- r.ID = client.MetaFQN(pvc.ObjectMeta)
+ r.ID = client.MetaFQN(&pvc.ObjectMeta)
r.Fields = model1.Fields{
pvc.Namespace,
pvc.Name,
diff --git a/internal/render/pvc_test.go b/internal/render/pvc_test.go
index ec85c2e1..3b361f3d 100644
--- a/internal/render/pvc_test.go
+++ b/internal/render/pvc_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPersistentVolumeClaimRender(t *testing.T) {
c := render.PersistentVolumeClaim{}
r := model1.NewRow(8)
- assert.NoError(t, c.Render(load(t, "pvc"), "", &r))
+ require.NoError(t, c.Render(load(t, "pvc"), "", &r))
assert.Equal(t, "default/www-nginx-sts-0", r.ID)
assert.Equal(t, model1.Fields{"default", "www-nginx-sts-0", "Bound", "pvc-fbabd470-8725-11e9-a8e8-42010a80015b", "1Gi", "RWO", "standard"}, r.Fields[:7])
}
diff --git a/internal/render/rbac.go b/internal/render/rbac.go
index 8869ad63..b5748c12 100644
--- a/internal/render/rbac.go
+++ b/internal/render/rbac.go
@@ -43,7 +43,7 @@ func (Rbac) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (Rbac) Header(ns string) model1.Header {
+func (Rbac) Header(string) model1.Header {
h := make(model1.Header, 0, 10)
h = append(h,
model1.HeaderColumn{Name: "NAME"},
@@ -55,10 +55,10 @@ func (Rbac) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (r Rbac) Render(o interface{}, ns string, ro *model1.Row) error {
- p, ok := o.(PolicyRes)
+func (r Rbac) Render(o any, ns string, ro *model1.Row) error {
+ p, ok := o.(*PolicyRes)
if !ok {
- return fmt.Errorf("expecting RuleRes but got %T", o)
+ return fmt.Errorf("expecting PolicyRes but got %T", o)
}
ro.ID = p.Resource
@@ -135,8 +135,8 @@ type RuleRes struct {
}
// NewRuleRes returns a new rule.
-func NewRuleRes(res, grp string, vv []string) RuleRes {
- return RuleRes{
+func NewRuleRes(res, grp string, vv []string) *RuleRes {
+ return &RuleRes{
Resource: res,
Group: grp,
Verbs: vv,
@@ -144,20 +144,20 @@ func NewRuleRes(res, grp string, vv []string) RuleRes {
}
// GetObjectKind returns a schema object.
-func (r RuleRes) GetObjectKind() schema.ObjectKind {
+func (*RuleRes) GetObjectKind() schema.ObjectKind {
return nil
}
// DeepCopyObject returns a container copy.
-func (r RuleRes) DeepCopyObject() runtime.Object {
+func (r *RuleRes) DeepCopyObject() runtime.Object {
return r
}
// Rules represents a collection of rules.
-type Rules []RuleRes
+type Rules []*RuleRes
// Upsert adds a new rule.
-func (rr Rules) Upsert(r RuleRes) Rules {
+func (rr Rules) Upsert(r *RuleRes) Rules {
idx, ok := rr.find(r.Resource)
if !ok {
return append(rr, r)
diff --git a/internal/render/reference.go b/internal/render/reference.go
index 21dec9d7..6e64f497 100644
--- a/internal/render/reference.go
+++ b/internal/render/reference.go
@@ -20,13 +20,13 @@ type Reference struct {
// ColorerFunc colors a resource row.
func (Reference) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorCadetBlue
}
}
// Header returns a header row.
-func (Reference) Header(ns string) model1.Header {
+func (Reference) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAMESPACE"},
model1.HeaderColumn{Name: "NAME"},
@@ -36,7 +36,7 @@ func (Reference) Header(ns string) model1.Header {
// Render renders a K8s resource to screen.
// BOZO!! Pass in a row with pre-alloc fields??
-func (Reference) Render(o interface{}, ns string, r *model1.Row) error {
+func (Reference) Render(o any, _ string, r *model1.Row) error {
ref, ok := o.(ReferenceRes)
if !ok {
return fmt.Errorf("expected ReferenceRes, but got %T", o)
diff --git a/internal/render/reference_test.go b/internal/render/reference_test.go
index 46aaf700..9846b36e 100644
--- a/internal/render/reference_test.go
+++ b/internal/render/reference_test.go
@@ -6,27 +6,29 @@ package render_test
import (
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestReferenceRender(t *testing.T) {
o := render.ReferenceRes{
Namespace: "ns1",
Name: "blee",
- GVR: "v1/secrets",
+ GVR: client.SecGVR.String(),
}
var (
ref = render.Reference{}
r model1.Row
)
- assert.Nil(t, ref.Render(o, "fred", &r))
+ require.NoError(t, ref.Render(o, "fred", &r))
assert.Equal(t, "ns1/blee", r.ID)
assert.Equal(t, model1.Fields{
"ns1",
"blee",
- "v1/secrets",
+ client.SecGVR.String(),
}, r.Fields)
}
diff --git a/internal/render/render_test.go b/internal/render/render_test.go
index 13192cb9..c34f1b7c 100644
--- a/internal/render/render_test.go
+++ b/internal/render/render_test.go
@@ -9,7 +9,7 @@ import (
"os"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -17,11 +17,11 @@ import (
func load(t testing.TB, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
+ require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
+ require.NoError(t, err)
return &o
}
diff --git a/internal/render/ro.go b/internal/render/ro.go
index 7bf03349..e1b7180f 100644
--- a/internal/render/ro.go
+++ b/internal/render/ro.go
@@ -20,34 +20,30 @@ type Role struct {
// Header returns a header row.
func (r Role) Header(_ string) model1.Header {
- return r.doHeader(r.defaultHeader())
+ return r.doHeader(defaultROHeader)
}
-func (Role) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultROHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (r Role) Render(o interface{}, _ string, row *model1.Row) error {
+func (r Role) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Role, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := r.defaultRow(raw, row); err != nil {
return err
}
if r.specs.isEmpty() {
return nil
}
-
- cols, err := r.specs.realize(raw, r.defaultHeader(), row)
+ cols, err := r.specs.realize(raw, defaultROHeader, row)
if err != nil {
return err
}
@@ -56,14 +52,14 @@ func (r Role) Render(o interface{}, _ string, row *model1.Row) error {
return nil
}
-func (r Role) defaultRow(raw *unstructured.Unstructured, row *model1.Row) error {
+func (Role) defaultRow(raw *unstructured.Unstructured, row *model1.Row) error {
var ro rbacv1.Role
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ro)
if err != nil {
return err
}
- row.ID = client.MetaFQN(ro.ObjectMeta)
+ row.ID = client.MetaFQN(&ro.ObjectMeta)
row.Fields = model1.Fields{
ro.Namespace,
ro.Name,
diff --git a/internal/render/ro_test.go b/internal/render/ro_test.go
index 5beb907d..2985c816 100644
--- a/internal/render/ro_test.go
+++ b/internal/render/ro_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRoleRender(t *testing.T) {
c := render.Role{}
r := model1.NewRow(3)
- assert.NoError(t, c.Render(load(t, "ro"), "", &r))
+ require.NoError(t, c.Render(load(t, "ro"), "", &r))
assert.Equal(t, "default/blee", r.ID)
assert.Equal(t, model1.Fields{"default", "blee"}, r.Fields[:2])
}
diff --git a/internal/render/rob.go b/internal/render/rob.go
index 0f025541..7724e749 100644
--- a/internal/render/rob.go
+++ b/internal/render/rob.go
@@ -14,6 +14,17 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultROBHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "ROLE"},
+ model1.HeaderColumn{Name: "KIND"},
+ model1.HeaderColumn{Name: "SUBJECTS"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// RoleBinding renders a K8s RoleBinding to screen.
type RoleBinding struct {
Base
@@ -21,37 +32,22 @@ type RoleBinding struct {
// Header returns a header row.
func (r RoleBinding) Header(_ string) model1.Header {
- return r.doHeader(r.defaultHeader())
-}
-
-func (RoleBinding) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "ROLE"},
- model1.HeaderColumn{Name: "KIND"},
- model1.HeaderColumn{Name: "SUBJECTS"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return r.doHeader(defaultROBHeader)
}
// Render renders a K8s resource to screen.
-func (r RoleBinding) Render(o interface{}, ns string, row *model1.Row) error {
+func (r RoleBinding) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected RoleBinding, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := r.defaultRow(raw, row); err != nil {
return err
}
if r.specs.isEmpty() {
return nil
}
-
- cols, err := r.specs.realize(raw, r.defaultHeader(), row)
+ cols, err := r.specs.realize(raw, defaultROBHeader, row)
if err != nil {
return err
}
@@ -60,7 +56,7 @@ func (r RoleBinding) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
-func (r RoleBinding) defaultRow(raw *unstructured.Unstructured, row *model1.Row) error {
+func (RoleBinding) defaultRow(raw *unstructured.Unstructured, row *model1.Row) error {
var rb rbacv1.RoleBinding
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rb)
if err != nil {
@@ -69,7 +65,7 @@ func (r RoleBinding) defaultRow(raw *unstructured.Unstructured, row *model1.Row)
kind, ss := renderSubjects(rb.Subjects)
- row.ID = client.MetaFQN(rb.ObjectMeta)
+ row.ID = client.MetaFQN(&rb.ObjectMeta)
row.Fields = model1.Fields{
rb.Namespace,
rb.Name,
@@ -87,7 +83,7 @@ func (r RoleBinding) defaultRow(raw *unstructured.Unstructured, row *model1.Row)
// ----------------------------------------------------------------------------
// Helpers...
-func renderSubjects(ss []rbacv1.Subject) (kind string, subjects string) {
+func renderSubjects(ss []rbacv1.Subject) (kind, subjects string) {
if len(ss) == 0 {
return NAValue, ""
}
@@ -101,7 +97,7 @@ func renderSubjects(ss []rbacv1.Subject) (kind string, subjects string) {
}
func toSubjectAlias(s string) string {
- if len(s) == 0 {
+ if s == "" {
return s
}
diff --git a/internal/render/rob_test.go b/internal/render/rob_test.go
index f18a08bf..4f16fc73 100644
--- a/internal/render/rob_test.go
+++ b/internal/render/rob_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRoleBindingRender(t *testing.T) {
c := render.RoleBinding{}
r := model1.NewRow(6)
- assert.NoError(t, c.Render(load(t, "rb"), "", &r))
+ require.NoError(t, c.Render(load(t, "rb"), "", &r))
assert.Equal(t, "default/blee", r.ID)
assert.Equal(t, model1.Fields{"default", "blee", "blee", "SvcAcct", "fernand"}, r.Fields[:5])
}
diff --git a/internal/render/rs.go b/internal/render/rs.go
index 45f1bd1b..f9c1fa54 100644
--- a/internal/render/rs.go
+++ b/internal/render/rs.go
@@ -22,46 +22,42 @@ type ReplicaSet struct {
}
// ColorerFunc colors a resource row.
-func (r ReplicaSet) ColorerFunc() model1.ColorerFunc {
+func (ReplicaSet) ColorerFunc() model1.ColorerFunc {
return model1.DefaultColorer
}
// Header returns a header row.
func (r ReplicaSet) Header(_ string) model1.Header {
- return r.doHeader(r.defaultHeader())
+ return r.doHeader(defaultRSHeader)
}
-func (ReplicaSet) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
- model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+var defaultRSHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "DESIRED", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "CURRENT", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "READY", Attrs: model1.Attrs{Align: tview.AlignRight}},
+ model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}
// Render renders a K8s resource to screen.
-func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error {
+func (r ReplicaSet) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected ReplicaSet, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := r.defaultRow(raw, row); err != nil {
return err
}
if r.specs.isEmpty() {
return nil
}
-
- cols, err := r.specs.realize(raw, r.defaultHeader(), row)
+ cols, err := r.specs.realize(raw, defaultRSHeader, row)
if err != nil {
return err
}
@@ -81,29 +77,29 @@ func (r ReplicaSet) defaultRow(raw *unstructured.Unstructured, row *model1.Row)
cc = rs.Spec.Template.Spec.Containers
cos, imgs = make([]string, 0, len(cc)), make([]string, 0, len(cc))
)
- for _, co := range cc {
- cos, imgs = append(cos, co.Name), append(imgs, co.Image)
+ for i := range cc {
+ cos, imgs = append(cos, cc[i].Name), append(imgs, cc[i].Image)
}
- row.ID = client.MetaFQN(rs.ObjectMeta)
+ row.ID = client.MetaFQN(&rs.ObjectMeta)
row.Fields = model1.Fields{
rs.Namespace,
rs.Name,
- computeVulScore(rs.ObjectMeta, &rs.Spec.Template.Spec),
+ computeVulScore(rs.Namespace, rs.Labels, &rs.Spec.Template.Spec),
strconv.Itoa(int(*rs.Spec.Replicas)),
strconv.Itoa(int(rs.Status.Replicas)),
strconv.Itoa(int(rs.Status.ReadyReplicas)),
strings.Join(cos, ","),
strings.Join(imgs, ","),
mapToStr(rs.Labels),
- AsStatus(r.diagnose(rs)),
+ AsStatus(r.diagnose(&rs)),
ToAge(rs.GetCreationTimestamp()),
}
return nil
}
-func (ReplicaSet) diagnose(rs appsv1.ReplicaSet) error {
+func (ReplicaSet) diagnose(rs *appsv1.ReplicaSet) error {
if rs.Status.Replicas != rs.Status.ReadyReplicas {
if rs.Status.Replicas == 0 {
return fmt.Errorf("did not phase down correctly expecting 0 replicas but got %d", rs.Status.ReadyReplicas)
diff --git a/internal/render/rs_test.go b/internal/render/rs_test.go
index 7a84cf38..29fc0563 100644
--- a/internal/render/rs_test.go
+++ b/internal/render/rs_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestReplicaSetRender(t *testing.T) {
c := render.ReplicaSet{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "rs"), "", &r))
+ require.NoError(t, c.Render(load(t, "rs"), "", &r))
assert.Equal(t, "icx/icx-db-7d4b578979", r.ID)
assert.Equal(t, model1.Fields{"icx", "icx-db-7d4b578979", "0", "1", "1", "1"}, r.Fields[:6])
}
diff --git a/internal/render/sa.go b/internal/render/sa.go
index c6c801f0..f49a2fbb 100644
--- a/internal/render/sa.go
+++ b/internal/render/sa.go
@@ -14,6 +14,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultSAHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "SECRET"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// ServiceAccount renders a K8s ServiceAccount to screen.
type ServiceAccount struct {
Base
@@ -21,35 +30,22 @@ type ServiceAccount struct {
// Header returns a header row.
func (s ServiceAccount) Header(_ string) model1.Header {
- return s.doHeader(s.defaultHeader())
-}
-
-func (ServiceAccount) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "SECRET"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return s.doHeader(defaultSAHeader)
}
// Render renders a K8s resource to screen.
-func (s ServiceAccount) Render(o interface{}, ns string, row *model1.Row) error {
+func (s ServiceAccount) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected ServiceAccount, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := s.defaultRow(raw, row); err != nil {
return err
}
if s.specs.isEmpty() {
return nil
}
-
- cols, err := s.specs.realize(raw, s.defaultHeader(), row)
+ cols, err := s.specs.realize(raw, defaultSAHeader, row)
if err != nil {
return err
}
@@ -58,14 +54,14 @@ func (s ServiceAccount) Render(o interface{}, ns string, row *model1.Row) error
return nil
}
-func (s ServiceAccount) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
+func (ServiceAccount) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
var sa v1.ServiceAccount
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sa)
if err != nil {
return err
}
- r.ID = client.MetaFQN(sa.ObjectMeta)
+ r.ID = client.MetaFQN(&sa.ObjectMeta)
r.Fields = model1.Fields{
sa.Namespace,
sa.Name,
diff --git a/internal/render/sa_test.go b/internal/render/sa_test.go
index 932ee798..8615d114 100644
--- a/internal/render/sa_test.go
+++ b/internal/render/sa_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestServiceAccountRender(t *testing.T) {
c := render.ServiceAccount{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "sa"), "", &r))
+ require.NoError(t, c.Render(load(t, "sa"), "", &r))
assert.Equal(t, "default/blee", r.ID)
assert.Equal(t, model1.Fields{"default", "blee", "2"}, r.Fields[:3])
}
diff --git a/internal/render/sc.go b/internal/render/sc.go
index 07a25592..cfbf27b8 100644
--- a/internal/render/sc.go
+++ b/internal/render/sc.go
@@ -15,6 +15,17 @@ import (
"k8s.io/kubectl/pkg/util/storage"
)
+var defaultSCHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "PROVISIONER"},
+ model1.HeaderColumn{Name: "RECLAIMPOLICY"},
+ model1.HeaderColumn{Name: "VOLUMEBINDINGMODE"},
+ model1.HeaderColumn{Name: "ALLOWVOLUMEEXPANSION"},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// StorageClass renders a K8s StorageClass to screen.
type StorageClass struct {
Base
@@ -22,37 +33,22 @@ type StorageClass struct {
// Header returns a header row.
func (s StorageClass) Header(_ string) model1.Header {
- return s.doHeader(s.defaultHeader())
-}
-
-func (StorageClass) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "PROVISIONER"},
- model1.HeaderColumn{Name: "RECLAIMPOLICY"},
- model1.HeaderColumn{Name: "VOLUMEBINDINGMODE"},
- model1.HeaderColumn{Name: "ALLOWVOLUMEEXPANSION"},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return s.doHeader(defaultSCHeader)
}
// Render renders a K8s resource to screen.
-func (s StorageClass) Render(o interface{}, ns string, row *model1.Row) error {
+func (s StorageClass) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected StorageClass, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := s.defaultRow(raw, row); err != nil {
return err
}
if s.specs.isEmpty() {
return nil
}
-
- cols, err := s.specs.realize(raw, s.defaultHeader(), row)
+ cols, err := s.specs.realize(raw, defaultSCHeader, row)
if err != nil {
return err
}
@@ -70,7 +66,7 @@ func (s StorageClass) defaultRow(raw *unstructured.Unstructured, r *model1.Row)
r.ID = client.FQN(client.ClusterScope, sc.Name)
r.Fields = model1.Fields{
- s.nameWithDefault(sc.ObjectMeta),
+ s.nameWithDefault(&sc.ObjectMeta),
sc.Provisioner,
strPtrToStr((*string)(sc.ReclaimPolicy)),
strPtrToStr((*string)(sc.VolumeBindingMode)),
@@ -83,8 +79,8 @@ func (s StorageClass) defaultRow(raw *unstructured.Unstructured, r *model1.Row)
return nil
}
-func (StorageClass) nameWithDefault(meta metav1.ObjectMeta) string {
- if storage.IsDefaultAnnotationText(meta) == "Yes" {
+func (StorageClass) nameWithDefault(meta *metav1.ObjectMeta) string {
+ if storage.IsDefaultAnnotationText(*meta) == "Yes" {
return meta.Name + " (default)"
}
return meta.Name
diff --git a/internal/render/sc_test.go b/internal/render/sc_test.go
index c5883136..a0a340f3 100644
--- a/internal/render/sc_test.go
+++ b/internal/render/sc_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestStorageClassRender(t *testing.T) {
c := render.StorageClass{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "sc"), "", &r))
+ require.NoError(t, c.Render(load(t, "sc"), "", &r))
assert.Equal(t, "-/standard", r.ID)
assert.Equal(t, model1.Fields{"standard (default)", "kubernetes.io/gce-pd", "Delete", "Immediate", "true"}, r.Fields[:5])
}
diff --git a/internal/render/screen_dump.go b/internal/render/screen_dump.go
index 6558616e..9d22f838 100644
--- a/internal/render/screen_dump.go
+++ b/internal/render/screen_dump.go
@@ -23,13 +23,13 @@ type ScreenDump struct {
// ColorerFunc colors a resource row.
func (ScreenDump) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorNavajoWhite
}
}
// Header returns a header row.
-func (ScreenDump) Header(ns string) model1.Header {
+func (ScreenDump) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAME"},
model1.HeaderColumn{Name: "DIR"},
@@ -39,7 +39,7 @@ func (ScreenDump) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (b ScreenDump) Render(o interface{}, ns string, r *model1.Row) error {
+func (ScreenDump) Render(o any, _ string, r *model1.Row) error {
f, ok := o.(FileRes)
if !ok {
return fmt.Errorf("expecting screendumper, but got %T", o)
@@ -70,7 +70,7 @@ type FileRes struct {
}
// GetObjectKind returns a schema object.
-func (c FileRes) GetObjectKind() schema.ObjectKind {
+func (FileRes) GetObjectKind() schema.ObjectKind {
return nil
}
diff --git a/internal/render/screen_dump_test.go b/internal/render/screen_dump_test.go
index 4831587c..a18acc25 100644
--- a/internal/render/screen_dump_test.go
+++ b/internal/render/screen_dump_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestScreenDumpRender(t *testing.T) {
@@ -21,7 +22,7 @@ func TestScreenDumpRender(t *testing.T) {
Dir: "fred/blee",
}
- assert.Nil(t, s.Render(o, "fred", &r))
+ require.NoError(t, s.Render(o, "fred", &r))
assert.Equal(t, "fred/blee/bob", r.ID)
assert.Equal(t, model1.Fields{
"bob",
@@ -36,12 +37,12 @@ type fileInfo struct{}
var _ os.FileInfo = fileInfo{}
-func (f fileInfo) Name() string { return "bob" }
-func (f fileInfo) Size() int64 { return 100 }
-func (f fileInfo) ModTime() time.Time { return testTime() }
-func (f fileInfo) IsDir() bool { return false }
-func (f fileInfo) Sys() interface{} { return nil }
+func (fileInfo) Name() string { return "bob" }
+func (fileInfo) Size() int64 { return 100 }
+func (fileInfo) ModTime() time.Time { return testTime() }
+func (fileInfo) IsDir() bool { return false }
+func (fileInfo) Sys() any { return nil }
-func (f fileInfo) Mode() os.FileMode {
+func (fileInfo) Mode() os.FileMode {
return os.FileMode(0644)
}
diff --git a/internal/render/secret.go b/internal/render/secret.go
index ac78e5a6..7ba6e6c4 100644
--- a/internal/render/secret.go
+++ b/internal/render/secret.go
@@ -14,6 +14,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultSECHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "TYPE"},
+ model1.HeaderColumn{Name: "DATA"},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Secret renders a K8s Secret to screen.
type Secret struct {
Base
@@ -21,35 +30,22 @@ type Secret struct {
// Header returns a header row.
func (s Secret) Header(_ string) model1.Header {
- return s.doHeader(s.defaultHeader())
-}
-
-func (Secret) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "TYPE"},
- model1.HeaderColumn{Name: "DATA"},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return s.doHeader(defaultSECHeader)
}
// Render renders a K8s resource to screen.
-func (s Secret) Render(o interface{}, ns string, row *model1.Row) error {
+func (s Secret) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Secret, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := s.defaultRow(raw, row); err != nil {
return err
}
if s.specs.isEmpty() {
return nil
}
-
- cols, err := s.specs.realize(raw, s.defaultHeader(), row)
+ cols, err := s.specs.realize(raw, defaultSECHeader, row)
if err != nil {
return err
}
@@ -58,7 +54,7 @@ func (s Secret) Render(o interface{}, ns string, row *model1.Row) error {
return nil
}
-func (n Secret) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
+func (Secret) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
var sec v1.Secret
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sec)
if err != nil {
diff --git a/internal/render/sts.go b/internal/render/sts.go
index 45dd6bc6..4293ada2 100644
--- a/internal/render/sts.go
+++ b/internal/render/sts.go
@@ -14,6 +14,20 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+var defaultSTSHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
+ model1.HeaderColumn{Name: "READY"},
+ model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "SERVICE"},
+ model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// StatefulSet renders a K8s StatefulSet to screen.
type StatefulSet struct {
Base
@@ -21,40 +35,22 @@ type StatefulSet struct {
// Header returns a header row.
func (s StatefulSet) Header(_ string) model1.Header {
- return s.doHeader(s.defaultHeader())
-}
-
-func (StatefulSet) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "VS", Attrs: model1.Attrs{VS: true}},
- model1.HeaderColumn{Name: "READY"},
- model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "SERVICE"},
- model1.HeaderColumn{Name: "CONTAINERS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "IMAGES", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return s.doHeader(defaultSTSHeader)
}
// Render renders a K8s resource to screen.
-func (s StatefulSet) Render(o interface{}, ns string, row *model1.Row) error {
+func (s StatefulSet) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected StatefulSet, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := s.defaultRow(raw, row); err != nil {
return err
}
if s.specs.isEmpty() {
return nil
}
-
- cols, err := s.specs.realize(raw, s.defaultHeader(), row)
+ cols, err := s.specs.realize(raw, defaultSTSHeader, row)
if err != nil {
return err
}
@@ -70,16 +66,16 @@ func (s StatefulSet) defaultRow(raw *unstructured.Unstructured, r *model1.Row) e
return err
}
- r.ID = client.MetaFQN(sts.ObjectMeta)
+ r.ID = client.MetaFQN(&sts.ObjectMeta)
r.Fields = model1.Fields{
sts.Namespace,
sts.Name,
- computeVulScore(sts.ObjectMeta, &sts.Spec.Template.Spec),
+ computeVulScore(sts.Namespace, sts.Labels, &sts.Spec.Template.Spec),
strconv.Itoa(int(sts.Status.ReadyReplicas)) + "/" + strconv.Itoa(int(sts.Status.Replicas)),
asSelector(sts.Spec.Selector),
na(sts.Spec.ServiceName),
- podContainerNames(sts.Spec.Template.Spec, true),
- podImageNames(sts.Spec.Template.Spec, true),
+ podContainerNames(&sts.Spec.Template.Spec, true),
+ podImageNames(&sts.Spec.Template.Spec, true),
mapToStr(sts.Labels),
AsStatus(s.diagnose(sts.Spec.Replicas, sts.Status.Replicas, sts.Status.ReadyReplicas)),
ToAge(sts.GetCreationTimestamp()),
diff --git a/internal/render/sts_test.go b/internal/render/sts_test.go
index d8a4edc8..0caaf850 100644
--- a/internal/render/sts_test.go
+++ b/internal/render/sts_test.go
@@ -9,13 +9,14 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestStatefulSetRender(t *testing.T) {
c := render.StatefulSet{}
r := model1.NewRow(4)
- assert.Nil(t, c.Render(load(t, "sts"), "", &r))
+ require.NoError(t, c.Render(load(t, "sts"), "", &r))
assert.Equal(t, "default/nginx-sts", r.ID)
assert.Equal(t, model1.Fields{"default", "nginx-sts", "0", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1])
}
diff --git a/internal/render/subject.go b/internal/render/subject.go
index bb0f1b9a..6d254dc3 100644
--- a/internal/render/subject.go
+++ b/internal/render/subject.go
@@ -19,13 +19,13 @@ type Subject struct {
// ColorerFunc colors a resource row.
func (Subject) ColorerFunc() model1.ColorerFunc {
- return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
+ return func(string, model1.Header, *model1.RowEvent) tcell.Color {
return tcell.ColorMediumSpringGreen
}
}
// Header returns a header row.
-func (Subject) Header(ns string) model1.Header {
+func (Subject) Header(string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAME"},
model1.HeaderColumn{Name: "KIND"},
@@ -35,7 +35,7 @@ func (Subject) Header(ns string) model1.Header {
}
// Render renders a K8s resource to screen.
-func (s Subject) Render(o interface{}, ns string, r *model1.Row) error {
+func (s Subject) Render(o any, _ string, r *model1.Row) error {
res, ok := o.(SubjectRes)
if !ok {
return fmt.Errorf("expected SubjectRes, but got %T", s)
diff --git a/internal/render/svc.go b/internal/render/svc.go
index 43b7bb3a..cfa71f8d 100644
--- a/internal/render/svc.go
+++ b/internal/render/svc.go
@@ -16,6 +16,20 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+// Header returns a header row.
+var defaultSVCHeader = model1.Header{
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "TYPE"},
+ model1.HeaderColumn{Name: "CLUSTER-IP"},
+ model1.HeaderColumn{Name: "EXTERNAL-IP"},
+ model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "PORTS", Attrs: model1.Attrs{Wide: false}},
+ model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Service renders a K8s Service to screen.
type Service struct {
Base
@@ -23,40 +37,22 @@ type Service struct {
// Header returns a header row.
func (s Service) Header(_ string) model1.Header {
- return s.doHeader(s.defaultHeader())
-}
-
-// Header returns a header row.
-func (Service) defaultHeader() model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "TYPE"},
- model1.HeaderColumn{Name: "CLUSTER-IP"},
- model1.HeaderColumn{Name: "EXTERNAL-IP"},
- model1.HeaderColumn{Name: "SELECTOR", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "PORTS", Attrs: model1.Attrs{Wide: false}},
- model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return s.doHeader(defaultSVCHeader)
}
// Render renders a K8s resource to screen.
-func (s Service) Render(o interface{}, ns string, row *model1.Row) error {
+func (s Service) Render(o any, _ string, row *model1.Row) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
- return fmt.Errorf("expected Service, but got %T", o)
+ return fmt.Errorf("expected Unstructured, but got %T", o)
}
-
if err := s.defaultRow(raw, row); err != nil {
return err
}
if s.specs.isEmpty() {
return nil
}
-
- cols, err := s.specs.realize(raw, s.defaultHeader(), row)
+ cols, err := s.specs.realize(raw, defaultSVCHeader, row)
if err != nil {
return err
}
@@ -72,7 +68,7 @@ func (s Service) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error
return err
}
- r.ID = client.MetaFQN(svc.ObjectMeta)
+ r.ID = client.MetaFQN(&svc.ObjectMeta)
r.Fields = model1.Fields{
svc.Namespace,
svc.Name,
@@ -107,19 +103,17 @@ func getSvcExtIPS(svc *v1.Service) []string {
results := []string{}
switch svc.Spec.Type {
- case v1.ServiceTypeClusterIP:
- fallthrough
- case v1.ServiceTypeNodePort:
+ case v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP:
return svc.Spec.ExternalIPs
case v1.ServiceTypeLoadBalancer:
lbIps := lbIngressIP(svc.Status.LoadBalancer)
if len(svc.Spec.ExternalIPs) > 0 {
- if len(lbIps) > 0 {
+ if lbIps != "" {
results = append(results, lbIps)
}
return append(results, svc.Spec.ExternalIPs...)
}
- if len(lbIps) > 0 {
+ if lbIps != "" {
results = append(results, lbIps)
}
case v1.ServiceTypeExternalName:
@@ -133,9 +127,9 @@ func lbIngressIP(s v1.LoadBalancerStatus) string {
ingress := s.Ingress
result := []string{}
for i := range ingress {
- if len(ingress[i].IP) > 0 {
+ if ingress[i].IP != "" {
result = append(result, ingress[i].IP)
- } else if len(ingress[i].Hostname) > 0 {
+ } else if ingress[i].Hostname != "" {
result = append(result, ingress[i].Hostname)
}
}
@@ -159,7 +153,7 @@ func toIPs(svcType v1.ServiceType, ips []string) string {
func ToPorts(pp []v1.ServicePort) string {
ports := make([]string, len(pp))
for i, p := range pp {
- if len(p.Name) > 0 {
+ if p.Name != "" {
ports[i] = p.Name + ":"
}
ports[i] += strconv.Itoa(int(p.Port)) +
diff --git a/internal/render/svc_test.go b/internal/render/svc_test.go
index 6a1ea62f..fe67c674 100644
--- a/internal/render/svc_test.go
+++ b/internal/render/svc_test.go
@@ -9,24 +9,28 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestServiceRender(t *testing.T) {
c := render.Service{}
r := model1.NewRow(4)
- assert.NoError(t, c.Render(load(t, "svc"), "", &r))
+ require.NoError(t, c.Render(load(t, "svc"), "", &r))
assert.Equal(t, "default/dictionary1", r.ID)
assert.Equal(t, model1.Fields{"default", "dictionary1", "ClusterIP", "10.47.248.116", "", "app=dictionary1", "http:4001โบ0"}, r.Fields[:7])
}
func BenchmarkSvcRender(b *testing.B) {
- var svc render.Service
- r := model1.NewRow(4)
- s := load(b, "svc")
+ var (
+ svc render.Service
+ r = model1.NewRow(4)
+ s = load(b, "svc")
+ )
+
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for range b.N {
_ = svc.Render(s, "", &r)
}
}
diff --git a/internal/render/table.go b/internal/render/table.go
index e509e77c..ab9d451c 100644
--- a/internal/render/table.go
+++ b/internal/render/table.go
@@ -8,6 +8,7 @@ import (
"fmt"
"log/slog"
"strings"
+ "sync"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1"
@@ -23,12 +24,25 @@ type Table struct {
table *metav1.Table
header model1.Header
ageIndex int
+ mx sync.RWMutex
}
func (*Table) IsGeneric() bool {
return true
}
+func (t *Table) setAgeIndex(idx int) {
+ t.mx.Lock()
+ defer t.mx.Unlock()
+ t.ageIndex = idx
+}
+
+func (t *Table) getAgeIndex() int {
+ t.mx.RLock()
+ defer t.mx.RUnlock()
+ return t.ageIndex
+}
+
// SetTable sets the tabular resource.
func (t *Table) SetTable(ns string, table *metav1.Table) {
t.table = table
@@ -41,7 +55,7 @@ func (*Table) ColorerFunc() model1.ColorerFunc {
}
// Header returns a header row.
-func (t *Table) Header(ns string) model1.Header {
+func (t *Table) Header(string) model1.Header {
return t.doHeader(t.defaultHeader())
}
@@ -53,12 +67,12 @@ func (t *Table) defaultHeader() model1.Header {
h := make(model1.Header, 0, len(t.table.ColumnDefinitions))
for i, c := range t.table.ColumnDefinitions {
if c.Name == ageTableCol {
- t.ageIndex = i
+ t.setAgeIndex(i)
continue
}
h = append(h, model1.HeaderColumn{Name: strings.ToUpper(c.Name)})
}
- if t.ageIndex > 0 {
+ if t.getAgeIndex() > 0 {
h = append(h, model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}})
}
@@ -69,16 +83,14 @@ func (t *Table) defaultHeader() model1.Header {
func (t *Table) Render(o any, ns string, r *model1.Row) error {
row, ok := o.(metav1.TableRow)
if !ok {
- return fmt.Errorf("expected Table, but got %T", o)
+ return fmt.Errorf("expected TableRow, but got %T", o)
}
-
if err := t.defaultRow(&row, ns, r); err != nil {
return err
}
if t.specs.isEmpty() {
return nil
}
-
cols, err := t.specs.realize(row.Object.Object, t.defaultHeader(), r)
if err != nil {
return err
@@ -89,7 +101,7 @@ func (t *Table) Render(o any, ns string, r *model1.Row) error {
}
func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error {
- th := t.defaultHeader()
+ th := t.header
ons, name := ns, UnknownValue
switch {
case row.Object.Object != nil:
@@ -103,11 +115,11 @@ func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error
}
ons, name = pm.Namespace, pm.Name
default:
- if idx, ok := th.IndexOf("NAME", true); ok && idx >= 0 {
+ if idx, ok := th.IndexOf("NAME", true); ok && idx >= 0 && idx < len(row.Cells) {
name = row.Cells[idx].(string)
- if idx, ok := th.IndexOf("NAMESPACE", true); ok && idx >= 0 {
- ons = row.Cells[idx].(string)
- }
+ }
+ if idx, ok := th.IndexOf("NAMESPACE", true); ok && idx >= 0 && idx < len(row.Cells) {
+ ons = row.Cells[idx].(string)
}
}
@@ -116,9 +128,12 @@ func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error
}
r.ID = client.FQN(ons, name)
r.Fields = make(model1.Fields, 0, len(th))
- var age any
+ var (
+ age any
+ ageIdx = t.getAgeIndex()
+ )
for i, c := range row.Cells {
- if t.ageIndex > 0 && i == t.ageIndex {
+ if ageIdx > 0 && i == ageIdx {
age = c
continue
}
@@ -130,7 +145,7 @@ func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error
}
if d, ok := age.(string); ok {
r.Fields = append(r.Fields, d)
- } else if t.ageIndex > 0 {
+ } else if ageIdx > 0 {
slog.Warn("No Duration detected on age field")
r.Fields = append(r.Fields, NAValue)
}
diff --git a/internal/render/table_test.go b/internal/render/table_test.go
index add7b484..2ca5933d 100644
--- a/internal/render/table_test.go
+++ b/internal/render/table_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
@@ -83,7 +84,7 @@ func TestGenericRender(t *testing.T) {
re.SetTable(u.ns, u.table)
assert.Equal(t, u.eHeader, re.Header(u.ns))
- assert.Nil(t, re.Render(u.table.Rows[0], u.ns, &r))
+ require.NoError(t, re.Render(u.table.Rows[0], u.ns, &r))
assert.Equal(t, u.eID, r.ID)
assert.Equal(t, u.eFields, r.Fields)
})
@@ -130,7 +131,7 @@ func TestGenericCustRender(t *testing.T) {
re.SetTable(u.ns, u.table)
assert.Equal(t, u.eHeader, re.Header(u.ns))
- assert.Nil(t, re.Render(u.table.Rows[0], u.ns, &r))
+ require.NoError(t, re.Render(u.table.Rows[0], u.ns, &r))
assert.Equal(t, u.eID, r.ID)
assert.Equal(t, u.eFields, r.Fields)
})
@@ -152,17 +153,17 @@ func makeNSGeneric() *metav1beta1.Table {
{
Object: runtime.RawExtension{
Object: &unstructured.Unstructured{
- Object: map[string]interface{}{
+ Object: map[string]any{
"kind": "fred",
"apiVersion": "v1",
- "metadata": map[string]interface{}{
+ "metadata": map[string]any{
"namespace": "ns1",
"name": "fred",
},
},
},
},
- Cells: []interface{}{
+ Cells: []any{
"ns1",
"c1",
"c2",
@@ -184,16 +185,16 @@ func makeNoNSGeneric() *metav1beta1.Table {
{
Object: runtime.RawExtension{
Object: &unstructured.Unstructured{
- Object: map[string]interface{}{
+ Object: map[string]any{
"kind": "fred",
"apiVersion": "v1",
- "metadata": map[string]interface{}{
+ "metadata": map[string]any{
"name": "fred",
},
},
},
},
- Cells: []interface{}{
+ Cells: []any{
"c1",
"c2",
"c3",
@@ -214,16 +215,16 @@ func makeAgeGeneric() *metav1beta1.Table {
{
Object: runtime.RawExtension{
Object: &unstructured.Unstructured{
- Object: map[string]interface{}{
+ Object: map[string]any{
"kind": "fred",
"apiVersion": "v1",
- "metadata": map[string]interface{}{
+ "metadata": map[string]any{
"name": "fred",
},
},
},
},
- Cells: []interface{}{
+ Cells: []any{
"c1",
"2d",
"c2",
diff --git a/internal/render/workload.go b/internal/render/workload.go
index b06e7300..dbfcfeaf 100644
--- a/internal/render/workload.go
+++ b/internal/render/workload.go
@@ -14,13 +14,23 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
+var defaultWKHeader = model1.Header{
+ model1.HeaderColumn{Name: "KIND"},
+ model1.HeaderColumn{Name: "NAMESPACE"},
+ model1.HeaderColumn{Name: "NAME"},
+ model1.HeaderColumn{Name: "STATUS"},
+ model1.HeaderColumn{Name: "READY"},
+ model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
+ model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
+}
+
// Workload renders a workload to screen.
type Workload struct {
Base
}
// ColorerFunc colors a resource row.
-func (n Workload) ColorerFunc() model1.ColorerFunc {
+func (Workload) ColorerFunc() model1.ColorerFunc {
return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
c := model1.DefaultColorer(ns, h, re)
@@ -39,22 +49,14 @@ func (n Workload) ColorerFunc() model1.ColorerFunc {
// Header returns a header rbw.
func (Workload) Header(string) model1.Header {
- return model1.Header{
- model1.HeaderColumn{Name: "KIND"},
- model1.HeaderColumn{Name: "NAMESPACE"},
- model1.HeaderColumn{Name: "NAME"},
- model1.HeaderColumn{Name: "STATUS"},
- model1.HeaderColumn{Name: "READY"},
- model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
- model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
- }
+ return defaultWKHeader
}
// Render renders a K8s resource to screen.
-func (n Workload) Render(o interface{}, _ string, r *model1.Row) error {
+func (Workload) Render(o any, _ string, r *model1.Row) error {
res, ok := o.(*WorkloadRes)
if !ok {
- return fmt.Errorf("expected allRes but got %T", o)
+ return fmt.Errorf("expected WorkloadRes but got %T", o)
}
r.ID = fmt.Sprintf("%s|%s|%s", res.Row.Cells[0].(string), res.Row.Cells[1].(string), res.Row.Cells[2].(string))
@@ -76,7 +78,7 @@ type WorkloadRes struct {
}
// GetObjectKind returns a schema object.
-func (a *WorkloadRes) GetObjectKind() schema.ObjectKind {
+func (*WorkloadRes) GetObjectKind() schema.ObjectKind {
return nil
}
diff --git a/internal/slogs/keys.go b/internal/slogs/keys.go
index 7ece5324..24ee165a 100644
--- a/internal/slogs/keys.go
+++ b/internal/slogs/keys.go
@@ -212,4 +212,10 @@ const (
// ID tracks an id logger key.
ID = "id"
+
+ // ViewSetting tracks a view setting logger key.
+ ViewSetting = "view-setting"
+
+ // JQExp tracks a jq expression logger key.
+ JQExp = "jq-exp"
)
diff --git a/internal/tchart/component.go b/internal/tchart/component.go
index b557631e..a0a17458 100644
--- a/internal/tchart/component.go
+++ b/internal/tchart/component.go
@@ -24,7 +24,8 @@ type Component struct {
focusFgColor, focusBgColor string
seriesColors []tcell.Color
dimmed tcell.Style
- id, legend string
+ id string
+ legend string
blur func(tcell.Key)
mx sync.RWMutex
}
@@ -67,7 +68,7 @@ func (c *Component) SetLegend(l string) {
// InputHandler returns the handler for this primitive.
func (c *Component) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
- // nolint:exhaustive
+ //nolint:exhaustive
switch key := event.Key(); key {
case tcell.KeyEnter:
case tcell.KeyBacktab, tcell.KeyTab:
@@ -80,7 +81,7 @@ func (c *Component) InputHandler() func(event *tcell.EventKey, setFocus func(p t
}
// IsDial returns true if chart is a dial.
-func (c *Component) IsDial() bool {
+func (*Component) IsDial() bool {
return false
}
@@ -117,13 +118,13 @@ func (c *Component) GetSeriesColorNames() []string {
return nn
}
-func (c *Component) colorForSeries() (tcell.Color, tcell.Color) {
+func (c *Component) colorForSeries() (cool, fault tcell.Color) {
c.mx.RLock()
defer c.mx.RUnlock()
-
if len(c.seriesColors) == 2 {
return c.seriesColors[0], c.seriesColors[1]
}
+
return okColor, faultColor
}
diff --git a/internal/tchart/dot_matrix.go b/internal/tchart/dot_matrix.go
index ae6b1bcc..8c424982 100644
--- a/internal/tchart/dot_matrix.go
+++ b/internal/tchart/dot_matrix.go
@@ -45,7 +45,7 @@ func NewDotMatrix() DotMatrix {
}
// Print prints the matrix.
-func (d DotMatrix) Print(n int) Matrix {
+func (DotMatrix) Print(n int) Matrix {
return To3x3Char(n)
}
diff --git a/internal/tchart/dot_matrix_test.go b/internal/tchart/dot_matrix_test.go
index df1c85d2..d15a0e6d 100644
--- a/internal/tchart/dot_matrix_test.go
+++ b/internal/tchart/dot_matrix_test.go
@@ -13,7 +13,7 @@ import (
func TestDial3x3(t *testing.T) {
d := tchart.NewDotMatrix()
- for n := 0; n <= 2; n++ {
+ for n := range 2 {
i := n
t.Run(strconv.Itoa(n), func(t *testing.T) {
assert.Equal(t, tchart.To3x3Char(i), d.Print(i))
diff --git a/internal/tchart/gauge.go b/internal/tchart/gauge.go
index 5b830528..273eedbf 100644
--- a/internal/tchart/gauge.go
+++ b/internal/tchart/gauge.go
@@ -48,7 +48,7 @@ func (g *Gauge) SetResolution(n int) {
}
// IsDial returns true if chart is a dial.
-func (g *Gauge) IsDial() bool {
+func (*Gauge) IsDial() bool {
return true
}
@@ -81,12 +81,12 @@ func (g *Gauge) Draw(sc tcell.Screen) {
style = style.Foreground(tcell.ColorYellow)
sc.SetContent(mid.X, mid.Y, 'โ ', nil, style)
- max := g.data.MaxDigits()
- if max < g.resolution {
- max = g.resolution
+ maxD := g.data.MaxDigits()
+ if maxD < g.resolution {
+ maxD = g.resolution
}
var (
- fmat = "%" + fmt.Sprintf(gaugeFmt, max)
+ fmat = "%" + fmt.Sprintf(gaugeFmt, maxD)
o = image.Point{X: mid.X, Y: mid.Y - 1}
)
@@ -118,7 +118,7 @@ func (g *Gauge) drawNum(sc tcell.Screen, o image.Point, n number, style tcell.St
if significant {
style = g.dimmed
}
- for i := 0; i < len(n.str); i++ {
+ for i := range len(n.str) {
if n.str[i] == '0' && !significant {
g.drawDial(sc, dm.Print(int(n.str[i]-48)), o, g.dimmed)
} else {
@@ -134,8 +134,8 @@ func (g *Gauge) drawNum(sc tcell.Screen, o image.Point, n number, style tcell.St
}
func (g *Gauge) drawDial(sc tcell.Screen, m Matrix, o image.Point, style tcell.Style) {
- for r := 0; r < len(m); r++ {
- for c := 0; c < len(m[r]); c++ {
+ for r := range m {
+ for c := range m[r] {
dot := m[r][c]
if dot == dots[0] {
sc.SetContent(o.X+c, o.Y+r, dots[1], nil, g.dimmed)
@@ -167,7 +167,7 @@ func computeDelta(d1, d2 int64) delta {
func printDelta(sc tcell.Screen, d delta, o image.Point, s tcell.Style) {
s = s.Dim(false)
- // nolint:exhaustive
+ //nolint:exhaustive
switch d {
case DeltaLess:
sc.SetContent(o.X-1, o.Y+1, 'โ', nil, s)
diff --git a/internal/tchart/sparkline.go b/internal/tchart/sparkline.go
index 2df126b0..6376dc99 100644
--- a/internal/tchart/sparkline.go
+++ b/internal/tchart/sparkline.go
@@ -91,7 +91,7 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
rect := s.asRect()
s.cutSet(rect.Dx())
- max := s.computeMax()
+ maxVal := s.computeMax()
cX, idx := rect.Min.X+1, 0
if len(s.data)*2 < rect.Dx() {
@@ -100,7 +100,7 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
idx = len(s.data) - rect.Dx()/2
}
- scale := float64(len(sparks)*(rect.Dy()-pad)) / float64(max)
+ scale := float64(len(sparks)*(rect.Dy()-pad)) / float64(maxVal)
c1, c2 := s.colorForSeries()
for _, d := range s.data[idx:] {
b := toBlocks(d, scale)
@@ -124,7 +124,7 @@ func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int,
style := tcell.StyleDefault.Foreground(c).Background(s.bgColor)
zeroY := r.Max.Y - r.Dy()
- for i := 0; i < b.full; i++ {
+ for range b.full {
screen.SetContent(x, y, sparks[len(sparks)-1], nil, style)
y--
if y <= zeroY {
@@ -147,15 +147,15 @@ func (s *SparkLine) cutSet(width int) {
}
func (s *SparkLine) computeMax() int64 {
- var max int64
+ var maxVal int64
for _, d := range s.data {
m := d.Max()
- if max < m {
- max = m
+ if maxVal < m {
+ maxVal = m
}
}
- return max
+ return maxVal
}
func toBlocks(m Metric, scale float64) blocks {
diff --git a/internal/tchart/sparkline_int_test.go b/internal/tchart/sparkline_int_test.go
index 14f0cd9b..b7345e1b 100644
--- a/internal/tchart/sparkline_int_test.go
+++ b/internal/tchart/sparkline_int_test.go
@@ -45,7 +45,7 @@ func TestCutSet(t *testing.T) {
s.Add(m)
}
s.cutSet(u.w)
- assert.Equal(t, u.e, len(s.data))
+ assert.Len(t, s.data, u.e)
})
}
}
diff --git a/internal/ui/action.go b/internal/ui/action.go
index 305d3624..4a6f3591 100644
--- a/internal/ui/action.go
+++ b/internal/ui/action.go
@@ -5,7 +5,7 @@ package ui
import (
"log/slog"
- "sort"
+ "slices"
"sync"
"github.com/derailed/k9s/internal/model"
@@ -110,9 +110,7 @@ func (a *KeyActions) Reset(aa *KeyActions) {
func (a *KeyActions) Range(f RangeFn) {
var km KeyMap
a.mx.RLock()
- {
- km = a.actions
- }
+ km = a.actions
a.mx.RUnlock()
for k, v := range km {
@@ -195,22 +193,22 @@ func (a *KeyActions) Hints() model.MenuHints {
a.mx.RLock()
defer a.mx.RUnlock()
- kk := make([]int, 0, len(a.actions))
+ kk := make([]tcell.Key, 0, len(a.actions))
for k := range a.actions {
if !a.actions[k].Opts.Shared {
- kk = append(kk, int(k))
+ kk = append(kk, k)
}
}
- sort.Ints(kk)
+ slices.Sort(kk)
hh := make(model.MenuHints, 0, len(kk))
for _, k := range kk {
- if name, ok := tcell.KeyNames[tcell.Key(int16(k))]; ok {
+ if name, ok := tcell.KeyNames[k]; ok {
hh = append(hh,
model.MenuHint{
Mnemonic: name,
- Description: a.actions[tcell.Key(k)].Description,
- Visible: a.actions[tcell.Key(k)].Opts.Visible,
+ Description: a.actions[k].Description,
+ Visible: a.actions[k].Opts.Visible,
},
)
} else {
diff --git a/internal/ui/action_test.go b/internal/ui/action_test.go
index 60733aa8..213dc8bf 100644
--- a/internal/ui/action_test.go
+++ b/internal/ui/action_test.go
@@ -20,6 +20,6 @@ func TestKeyActionsHints(t *testing.T) {
hh := kk.Hints()
- assert.Equal(t, 3, len(hh))
+ assert.Len(t, hh, 3)
assert.Equal(t, model.MenuHint{Mnemonic: "b", Description: "blee", Visible: true}, hh[0])
}
diff --git a/internal/ui/app.go b/internal/ui/app.go
index cbfb1a64..a1f21b7b 100644
--- a/internal/ui/app.go
+++ b/internal/ui/app.go
@@ -31,7 +31,7 @@ type App struct {
}
// NewApp returns a new app.
-func NewApp(cfg *config.Config, context string) *App {
+func NewApp(cfg *config.Config, _ string) *App {
a := App{
Application: tview.NewApplication(),
actions: NewKeyActions(),
@@ -96,13 +96,13 @@ func (a *App) SetRunning(f bool) {
}
// BufferCompleted indicates input was accepted.
-func (a *App) BufferCompleted(_, _ string) {}
+func (*App) BufferCompleted(_, _ string) {}
// BufferChanged indicates the buffer was changed.
-func (a *App) BufferChanged(_, _ string) {}
+func (*App) BufferChanged(_, _ string) {}
// BufferActive indicates the buff activity changed.
-func (a *App) BufferActive(state bool, kind model.BufferKind) {
+func (a *App) BufferActive(state bool, _ model.BufferKind) {
flex, ok := a.Main.GetPrimitive("main").(*tview.Flex)
if !ok {
return
@@ -117,7 +117,7 @@ func (a *App) BufferActive(state bool, kind model.BufferKind) {
}
// SuggestionChanged notifies of update to command suggestions.
-func (a *App) SuggestionChanged(ss []string) {}
+func (*App) SuggestionChanged([]string) {}
// StylesChanged notifies the skin changed.
func (a *App) StylesChanged(s *config.Styles) {
@@ -174,7 +174,7 @@ func (a *App) ResetCmd() {
a.cmdBuff.Reset()
}
-func (a *App) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (a *App) saveCmd(*tcell.EventKey) *tcell.EventKey {
if err := a.Config.Save(true); err != nil {
a.Flash().Err(err)
}
diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go
index 2c4b1372..0725561a 100644
--- a/internal/ui/app_test.go
+++ b/internal/ui/app_test.go
@@ -36,7 +36,7 @@ func TestAppResetCmd(t *testing.T) {
a.ResetCmd()
- assert.Equal(t, "", a.CmdBuff().GetText())
+ assert.Empty(t, a.CmdBuff().GetText())
}
func TestAppHasCmd(t *testing.T) {
diff --git a/internal/ui/config.go b/internal/ui/config.go
index a4ab43df..0a4cdeaf 100644
--- a/internal/ui/config.go
+++ b/internal/ui/config.go
@@ -83,6 +83,7 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e
if err := w.Add(config.AppViewsFile); err != nil {
return err
}
+ slog.Debug("Loading custom views", slogs.FileName, config.AppViewsFile)
return c.RefreshCustomViews()
}
@@ -182,7 +183,7 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error
if !ok {
return nil
}
- ctConfigFile := filepath.Join(config.AppContextConfig(cl, ct))
+ ctConfigFile := config.AppContextConfig(cl, ct)
slog.Debug("ConfigWatcher watching", slogs.FileName, ctConfigFile)
return w.Add(ctConfigFile)
@@ -214,7 +215,7 @@ func (c *Configurator) activeSkin() (string, bool) {
return skin, skin != ""
}
-func (c *Configurator) activeConfig() (cluster string, context string, ok bool) {
+func (c *Configurator) activeConfig() (cluster, context string, ok bool) {
if c.Config == nil || c.Config.K9s == nil {
return
}
@@ -254,7 +255,7 @@ func (c *Configurator) RefreshStyles(s synchronizer) {
}
}
-func (c *Configurator) loadSkinFile(s synchronizer) {
+func (c *Configurator) loadSkinFile(synchronizer) {
skin, ok := c.activeSkin()
if !ok {
slog.Debug("No custom skin found. Using stock skin")
diff --git a/internal/ui/config_int_test.go b/internal/ui/config_int_test.go
index d5545494..364916a1 100644
--- a/internal/ui/config_int_test.go
+++ b/internal/ui/config_int_test.go
@@ -10,12 +10,13 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func Test_activeConfig(t *testing.T) {
- assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
- assert.NoError(t, config.InitLocs())
+ require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
+ require.NoError(t, config.InitLocs())
cl, ct := "cl-1", "ct-1-1"
uu := map[string]struct {
@@ -45,7 +46,7 @@ func Test_activeConfig(t *testing.T) {
cfg := u.cfg
if cfg.Config != nil {
_, err := cfg.Config.K9s.ActivateContext(ct)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
cl, ct, ok := cfg.activeConfig()
assert.Equal(t, u.ok, ok)
diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go
index 95b71089..9bc34292 100644
--- a/internal/ui/config_test.go
+++ b/internal/ui/config_test.go
@@ -17,19 +17,20 @@ import (
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func TestSkinnedContext(t *testing.T) {
- assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/k9s-test"))
- assert.NoError(t, config.InitLocs())
- defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir))
+ require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/k9s-test"))
+ require.NoError(t, config.InitLocs())
+ defer require.NoError(t, os.RemoveAll(config.K9sEnvConfigDir))
sf := filepath.Join("..", "config", "testdata", "skins", "black-and-wtf.yaml")
raw, err := os.ReadFile(sf)
- assert.NoError(t, err)
+ require.NoError(t, err)
tf := filepath.Join(config.AppSkinsDir, "black-and-wtf.yaml")
- assert.NoError(t, os.WriteFile(tf, raw, data.DefaultFileMod))
+ require.NoError(t, os.WriteFile(tf, raw, data.DefaultFileMod))
var cfg ui.Configurator
cfg.Config = mock.NewMockConfig()
@@ -43,7 +44,7 @@ func TestSkinnedContext(t *testing.T) {
mock.NewMockConnection(),
mock.NewMockKubeSettings(&flags))
_, err = cfg.Config.K9s.ActivateContext("ct-1-1")
- assert.NoError(t, err)
+ require.NoError(t, err)
cfg.Config.K9s.UI = config.UI{Skin: "black-and-wtf"}
cfg.RefreshStyles(newMockSynchronizer())
assert.True(t, cfg.HasSkin())
@@ -52,12 +53,12 @@ func TestSkinnedContext(t *testing.T) {
}
func TestBenchConfig(t *testing.T) {
- assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
- assert.NoError(t, config.InitLocs())
- defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir))
+ require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
+ require.NoError(t, config.InitLocs())
+ defer require.NoError(t, os.RemoveAll(config.K9sEnvConfigDir))
- bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1")
- assert.NoError(t, error)
+ bc, err := config.EnsureBenchmarksCfgFile("cl-1", "ct-1")
+ require.NoError(t, err)
assert.Equal(t, "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", bc)
}
@@ -69,10 +70,10 @@ func newMockSynchronizer() synchronizer {
return synchronizer{}
}
-func (s synchronizer) Flash() *model.Flash {
+func (synchronizer) Flash() *model.Flash {
return model.NewFlash(100 * time.Millisecond)
}
-func (s synchronizer) Logo() *ui.Logo { return nil }
-func (s synchronizer) UpdateClusterInfo() {}
-func (s synchronizer) QueueUpdateDraw(func()) {}
-func (s synchronizer) QueueUpdate(func()) {}
+func (synchronizer) Logo() *ui.Logo { return nil }
+func (synchronizer) UpdateClusterInfo() {}
+func (synchronizer) QueueUpdateDraw(func()) {}
+func (synchronizer) QueueUpdate(func()) {}
diff --git a/internal/ui/crumbs.go b/internal/ui/crumbs.go
index 3a757763..d9f5cac3 100644
--- a/internal/ui/crumbs.go
+++ b/internal/ui/crumbs.go
@@ -56,7 +56,7 @@ func (c *Crumbs) StackPopped(_, _ model.Component) {
}
// StackTop indicates the top of the stack.
-func (c *Crumbs) StackTop(top model.Component) {}
+func (*Crumbs) StackTop(model.Component) {}
// Refresh updates view with new crumbs.
func (c *Crumbs) refresh(crumbs []string) {
diff --git a/internal/ui/crumbs_test.go b/internal/ui/crumbs_test.go
index 57daa103..508e513d 100644
--- a/internal/ui/crumbs_test.go
+++ b/internal/ui/crumbs_test.go
@@ -52,7 +52,7 @@ func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse,
return nil
}
func (c) SetRect(int, int, int, int) {}
-func (c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 }
+func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 }
func (c c) GetFocusable() tview.Focusable { return c }
func (c) Focus(func(tview.Primitive)) {}
func (c) Blur() {}
diff --git a/internal/ui/deltas.go b/internal/ui/deltas.go
index 5e4fdb7b..99419a0f 100644
--- a/internal/ui/deltas.go
+++ b/internal/ui/deltas.go
@@ -22,7 +22,7 @@ const (
MinusSign = "[green::b]โ"
)
-var percent = regexp.MustCompile(`\A(\d+)\%\z`)
+var percent = regexp.MustCompile(`\A(\d+)%\z`)
func deltaNumb(o, n string) (string, bool) {
var delta string
diff --git a/internal/ui/dialog/confirm.go b/internal/ui/dialog/confirm.go
index c2314376..bf451c1a 100644
--- a/internal/ui/dialog/confirm.go
+++ b/internal/ui/dialog/confirm.go
@@ -46,7 +46,7 @@ func ShowConfirmAck(app *ui.App, pages *ui.Pages, acceptStr string, override boo
dismissConfirm(pages)
cancel()
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
b := f.GetButton(i)
if b == nil {
continue
@@ -67,7 +67,7 @@ func ShowConfirmAck(app *ui.App, pages *ui.Pages, acceptStr string, override boo
}
// ShowConfirm pops a confirmation dialog.
-func ShowConfirm(styles config.Dialog, pages *ui.Pages, title, msg string, ack confirmFunc, cancel cancelFunc) {
+func ShowConfirm(styles *config.Dialog, pages *ui.Pages, title, msg string, ack confirmFunc, cancel cancelFunc) {
f := tview.NewForm().
SetItemPadding(0).
SetButtonsAlign(tview.AlignCenter).
@@ -85,7 +85,7 @@ func ShowConfirm(styles config.Dialog, pages *ui.Pages, title, msg string, ack c
dismiss(pages)
cancel()
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
if b := f.GetButton(i); b != nil {
b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color())
b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
diff --git a/internal/ui/dialog/confirm_test.go b/internal/ui/dialog/confirm_test.go
index 211e2091..41353cb6 100644
--- a/internal/ui/dialog/confirm_test.go
+++ b/internal/ui/dialog/confirm_test.go
@@ -16,14 +16,7 @@ func TestConfirmDialog(t *testing.T) {
a := tview.NewApplication()
p := ui.NewPages()
a.SetRoot(p, false)
-
- ackFunc := func() {
- assert.True(t, true)
- }
- caFunc := func() {
- assert.True(t, true)
- }
- ShowConfirm(config.Dialog{}, p, "Blee", "Yo", ackFunc, caFunc)
+ ShowConfirm(new(config.Dialog), p, "Blee", "Yo", func() {}, func() {})
d := p.GetPrimitive(dialogKey).(*tview.ModalForm)
assert.NotNil(t, d)
diff --git a/internal/ui/dialog/delete.go b/internal/ui/dialog/delete.go
index 686e95e6..62c98e8e 100644
--- a/internal/ui/dialog/delete.go
+++ b/internal/ui/dialog/delete.go
@@ -28,7 +28,7 @@ var propagationOptions []string = []string{
}
// ShowDelete pops a resource deletion dialog.
-func ShowDelete(styles config.Dialog, pages *ui.Pages, msg string, ok okFunc, cancel cancelFunc) {
+func ShowDelete(styles *config.Dialog, pages *ui.Pages, msg string, ok okFunc, cancel cancelFunc) {
propagation, force := "", false
f := tview.NewForm()
f.SetItemPadding(0)
@@ -63,7 +63,7 @@ func ShowDelete(styles config.Dialog, pages *ui.Pages, msg string, ok okFunc, ca
dismiss(pages)
cancel()
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
b := f.GetButton(i)
if b == nil {
continue
diff --git a/internal/ui/dialog/delete_test.go b/internal/ui/dialog/delete_test.go
index 39b2eba0..5c31132d 100644
--- a/internal/ui/dialog/delete_test.go
+++ b/internal/ui/dialog/delete_test.go
@@ -20,10 +20,7 @@ func TestDeleteDialog(t *testing.T) {
assert.Equal(t, propagationOptions[defaultPropagationIdx], p)
assert.True(t, f)
}
- caFunc := func() {
- assert.True(t, true)
- }
- ShowDelete(config.Dialog{}, p, "Yo", okFunc, caFunc)
+ ShowDelete(new(config.Dialog), p, "Yo", okFunc, func() {})
d := p.GetPrimitive(dialogKey).(*tview.ModalForm)
assert.NotNil(t, d)
diff --git a/internal/ui/dialog/error.go b/internal/ui/dialog/error.go
index 1d81db5e..59198a63 100644
--- a/internal/ui/dialog/error.go
+++ b/internal/ui/dialog/error.go
@@ -14,7 +14,7 @@ import (
)
// ShowError pops an error dialog.
-func ShowError(styles config.Dialog, pages *ui.Pages, msg string) {
+func ShowError(styles *config.Dialog, pages *ui.Pages, msg string) {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
diff --git a/internal/ui/dialog/error_test.go b/internal/ui/dialog/error_test.go
index b21fa4c6..2e0a4a73 100644
--- a/internal/ui/dialog/error_test.go
+++ b/internal/ui/dialog/error_test.go
@@ -15,7 +15,7 @@ import (
func TestErrorDialog(t *testing.T) {
p := ui.NewPages()
- ShowError(config.Dialog{}, p, "Yo")
+ ShowError(new(config.Dialog), p, "Yo")
d := p.GetPrimitive(dialogKey).(*tview.ModalForm)
assert.NotNil(t, d)
diff --git a/internal/ui/dialog/prompt.go b/internal/ui/dialog/prompt.go
index 8232d2d2..83f656b3 100644
--- a/internal/ui/dialog/prompt.go
+++ b/internal/ui/dialog/prompt.go
@@ -14,7 +14,7 @@ import (
type promptAction func(ctx context.Context)
// ShowPrompt pops a prompt dialog.
-func ShowPrompt(styles config.Dialog, pages *ui.Pages, title, msg string, action promptAction, cancel cancelFunc) {
+func ShowPrompt(styles *config.Dialog, pages *ui.Pages, title, msg string, action promptAction, cancel cancelFunc) {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
@@ -31,7 +31,7 @@ func ShowPrompt(styles config.Dialog, pages *ui.Pages, title, msg string, action
cancel()
})
- for i := 0; i < f.GetButtonCount(); i++ {
+ for i := range f.GetButtonCount() {
b := f.GetButton(i)
if b == nil {
continue
diff --git a/internal/ui/dialog/prompt_test.go b/internal/ui/dialog/prompt_test.go
index a5a7c022..4b7067d5 100644
--- a/internal/ui/dialog/prompt_test.go
+++ b/internal/ui/dialog/prompt_test.go
@@ -21,7 +21,7 @@ func TestShowPrompt(t *testing.T) {
p := ui.NewPages()
a.SetRoot(p, false)
- ShowPrompt(config.Dialog{}, p, "Running", "Pod", func(context.Context) {
+ ShowPrompt(new(config.Dialog), p, "Running", "Pod", func(context.Context) {
time.Sleep(time.Millisecond)
}, func() {
t.Errorf("unexpected cancellations")
@@ -33,7 +33,7 @@ func TestShowPrompt(t *testing.T) {
p := ui.NewPages()
a.SetRoot(p, false)
- go ShowPrompt(config.Dialog{}, p, "Running", "Pod", func(ctx context.Context) {
+ go ShowPrompt(new(config.Dialog), p, "Running", "Pod", func(ctx context.Context) {
select {
case <-time.After(time.Second):
t.Errorf("expected cancellations")
diff --git a/internal/ui/dialog/selection.go b/internal/ui/dialog/selection.go
index 592105c4..ce0339d5 100644
--- a/internal/ui/dialog/selection.go
+++ b/internal/ui/dialog/selection.go
@@ -11,7 +11,7 @@ import (
type SelectAction func(index int)
-func ShowSelection(styles config.Dialog, pages *ui.Pages, title string, options []string, action SelectAction) {
+func ShowSelection(styles *config.Dialog, pages *ui.Pages, title string, options []string, action SelectAction) {
list := tview.NewList()
list.ShowSecondaryText(false)
list.SetSelectedTextColor(styles.ButtonFocusFgColor.Color())
@@ -23,7 +23,7 @@ func ShowSelection(styles config.Dialog, pages *ui.Pages, title string, options
}
modal := ui.NewModalList("<"+title+">", list)
- modal.SetDoneFunc(func(i int, s string) {
+ modal.SetDoneFunc(func(i int, _ string) {
dismiss(pages)
action(i)
})
diff --git a/internal/ui/dialog/transfer.go b/internal/ui/dialog/transfer.go
index 3141a894..767d21f7 100644
--- a/internal/ui/dialog/transfer.go
+++ b/internal/ui/dialog/transfer.go
@@ -31,7 +31,7 @@ type TransferDialogOpts struct {
Cancel cancelFunc
}
-func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts) {
+func ShowUploads(styles *config.Dialog, pages *ui.Pages, opts *TransferDialogOpts) {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
@@ -98,7 +98,7 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts)
dismissConfirm(pages)
opts.Cancel()
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
b := f.GetButton(i)
if b == nil {
continue
diff --git a/internal/ui/flash.go b/internal/ui/flash.go
index 0ba37d5b..6cfc273f 100644
--- a/internal/ui/flash.go
+++ b/internal/ui/flash.go
@@ -88,7 +88,7 @@ func (f *Flash) flashEmoji(l model.FlashLevel) string {
if f.app.Config.K9s.UI.NoIcons {
return ""
}
- // nolint:exhaustive
+ //nolint:exhaustive
switch l {
case model.FlashWarn:
return emoDoh
@@ -102,7 +102,7 @@ func (f *Flash) flashEmoji(l model.FlashLevel) string {
// Helpers...
func flashColor(l model.FlashLevel) tcell.Color {
- // nolint:exhaustive
+ //nolint:exhaustive
switch l {
case model.FlashWarn:
return tcell.ColorOrange
diff --git a/internal/ui/indicator.go b/internal/ui/indicator.go
index ed4645a4..82ee5c09 100644
--- a/internal/ui/indicator.go
+++ b/internal/ui/indicator.go
@@ -51,7 +51,7 @@ func (s *StatusIndicator) StylesChanged(styles *config.Styles) {
const statusIndicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s[white::]::[darkturquoise::]%s"
// ClusterInfoUpdated notifies the cluster meta was updated.
-func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
+func (s *StatusIndicator) ClusterInfoUpdated(data *model.ClusterMeta) {
s.app.QueueUpdateDraw(func() {
s.SetPermanent(fmt.Sprintf(
statusIndicatorFmt,
@@ -66,7 +66,7 @@ func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
}
// ClusterInfoChanged notifies the cluster meta was changed.
-func (s *StatusIndicator) ClusterInfoChanged(prev, cur model.ClusterMeta) {
+func (s *StatusIndicator) ClusterInfoChanged(prev, cur *model.ClusterMeta) {
if !s.app.IsRunning() {
return
}
diff --git a/internal/ui/logo_test.go b/internal/ui/logo_test.go
index 168ef86e..22ba7f11 100644
--- a/internal/ui/logo_test.go
+++ b/internal/ui/logo_test.go
@@ -17,7 +17,7 @@ func TestNewLogoView(t *testing.T) {
const elogo = "[#ffa500::b] ____ __ ________ \n[#ffa500::b]| |/ / __ \\______\n[#ffa500::b]| /\\____ / ___/\n[#ffa500::b]| \\ \\ / /\\___ \\\n[#ffa500::b]|____|\\__ \\/____//____ /\n[#ffa500::b] \\/ \\/ \n"
assert.Equal(t, elogo, v.Logo().GetText(false))
- assert.Equal(t, "", v.Status().GetText(false))
+ assert.Empty(t, v.Status().GetText(false))
}
func TestLogoStatus(t *testing.T) {
diff --git a/internal/ui/menu.go b/internal/ui/menu.go
index b1b5a939..e74834bb 100644
--- a/internal/ui/menu.go
+++ b/internal/ui/menu.go
@@ -47,8 +47,8 @@ func NewMenu(styles *config.Styles) *Menu {
func (m *Menu) StylesChanged(s *config.Styles) {
m.styles = s
m.SetBackgroundColor(s.BgColor())
- for row := 0; row < m.GetRowCount(); row++ {
- for col := 0; col < m.GetColumnCount(); col++ {
+ for row := range m.GetRowCount() {
+ for col := range m.GetColumnCount() {
if c := m.GetCell(row, col); c != nil {
c.BackgroundColor = s.BgColor()
}
@@ -62,7 +62,7 @@ func (m *Menu) StackPushed(c model.Component) {
}
// StackPopped notifies a component was removed.
-func (m *Menu) StackPopped(o, top model.Component) {
+func (m *Menu) StackPopped(_, top model.Component) {
if top != nil {
m.HydrateMenu(top.Hints())
} else {
@@ -85,15 +85,15 @@ func (m *Menu) HydrateMenu(hh model.MenuHints) {
if m.hasDigits(hh) {
colCount++
}
- for row := 0; row < maxRows; row++ {
+ for row := range maxRows {
table[row] = make(model.MenuHints, colCount)
}
t := m.buildMenuTable(hh, table, colCount)
- for row := 0; row < len(t); row++ {
- for col := 0; col < len(t[row]); col++ {
+ for row := range t {
+ for col := range len(t[row]) {
c := tview.NewTableCell(t[row][col])
- if len(t[row][col]) == 0 {
+ if t[row][col] == "" {
c = tview.NewTableCell("")
}
c.SetBackgroundColor(m.styles.BgColor())
@@ -102,7 +102,7 @@ func (m *Menu) HydrateMenu(hh model.MenuHints) {
}
}
-func (m *Menu) hasDigits(hh model.MenuHints) bool {
+func (*Menu) hasDigits(hh model.MenuHints) bool {
for _, h := range hh {
if !h.Visible {
continue
@@ -160,12 +160,13 @@ func (m *Menu) formatMenu(h model.MenuHint, size int) string {
if h.Mnemonic == "" || h.Description == "" {
return ""
}
+ styles := m.styles.Frame()
i, err := strconv.Atoi(h.Mnemonic)
if err == nil {
- return formatNSMenu(i, h.Description, m.styles.Frame())
+ return formatNSMenu(i, h.Description, &styles)
}
- return formatPlainMenu(h, size, m.styles.Frame())
+ return formatPlainMenu(h, size, &styles)
}
// ----------------------------------------------------------------------------
@@ -195,7 +196,7 @@ func ToMnemonic(s string) string {
return "<" + keyConv(strings.ToLower(s)) + ">"
}
-func formatNSMenu(i int, name string, styles config.Frame) string {
+func formatNSMenu(i int, name string, styles *config.Frame) string {
fmat := strings.Replace(menuIndexFmt, "[key", "["+styles.Menu.NumKeyColor.String(), 1)
fmat = strings.ReplaceAll(fmat, ":bg:", ":"+styles.Title.BgColor.String()+":")
fmat = strings.Replace(fmat, "[fg", "["+styles.Menu.FgColor.String(), 1)
@@ -204,7 +205,7 @@ func formatNSMenu(i int, name string, styles config.Frame) string {
return fmt.Sprintf(fmat, i, name)
}
-func formatPlainMenu(h model.MenuHint, size int, styles config.Frame) string {
+func formatPlainMenu(h model.MenuHint, size int, styles *config.Frame) string {
menuFmt := " [key:-:b]%-" + strconv.Itoa(size+2) + "s [fg:-:fgstyle]%s "
fmat := strings.Replace(menuFmt, "[key", "["+styles.Menu.KeyColor.String(), 1)
fmat = strings.Replace(fmat, "[fg", "["+styles.Menu.FgColor.String(), 1)
diff --git a/internal/ui/modal_list.go b/internal/ui/modal_list.go
index ac331a9e..10ec39a6 100644
--- a/internal/ui/modal_list.go
+++ b/internal/ui/modal_list.go
@@ -52,7 +52,7 @@ func NewModalList(title string, list *tview.List) *ModalList {
func (m *ModalList) Draw(screen tcell.Screen) {
// Calculate the width of this modal.
width := 0
- for i := 0; i < m.list.GetItemCount(); i++ {
+ for i := range m.list.GetItemCount() {
main, secondary := m.list.GetItemText(i)
width = max(width, len(main)+len(secondary)+2)
}
diff --git a/internal/ui/padding.go b/internal/ui/padding.go
index 25cacdc5..d1fbfc62 100644
--- a/internal/ui/padding.go
+++ b/internal/ui/padding.go
@@ -40,7 +40,7 @@ func ComputeMaxColumns(pads MaxyPad, sortColName string, t *model1.TableData) {
// IsASCII checks if table cell has all ascii characters.
func IsASCII(s string) bool {
- for i := 0; i < len(s); i++ {
+ for i := range s {
if s[i] > unicode.MaxASCII {
return false
}
diff --git a/internal/ui/padding_test.go b/internal/ui/padding_test.go
index 0dbcb87a..99028941 100644
--- a/internal/ui/padding_test.go
+++ b/internal/ui/padding_test.go
@@ -144,7 +144,7 @@ func BenchmarkMaxColumn(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
ComputeMaxColumns(pads, "A", table)
}
}
diff --git a/internal/ui/pages.go b/internal/ui/pages.go
index 919376ba..0b4b5138 100644
--- a/internal/ui/pages.go
+++ b/internal/ui/pages.go
@@ -87,7 +87,7 @@ func (p *Pages) StackPushed(c model.Component) {
}
// StackPopped notifies a component was removed.
-func (p *Pages) StackPopped(o, top model.Component) {
+func (p *Pages) StackPopped(o, _ model.Component) {
p.delete(o)
}
diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go
index 0fcc88bb..5878dc75 100644
--- a/internal/ui/prompt.go
+++ b/internal/ui/prompt.go
@@ -145,7 +145,7 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
- // nolint:exhaustive
+ //nolint:exhaustive
switch evt.Key() {
case tcell.KeyBackspace2, tcell.KeyBackspace, tcell.KeyDelete:
p.model.Delete()
@@ -279,7 +279,7 @@ func (p *Prompt) iconFor(k model.BufferKind) rune {
return ' '
}
- // nolint:exhaustive
+ //nolint:exhaustive
switch k {
case model.CommandBuffer:
return '๐ถ'
@@ -292,7 +292,7 @@ func (p *Prompt) iconFor(k model.BufferKind) rune {
// Helpers...
func (p *Prompt) colorFor(k model.BufferKind) tcell.Color {
- // nolint:exhaustive
+ //nolint:exhaustive
switch k {
case model.CommandBuffer:
return p.styles.Prompt().Border.CommandColor.Color()
diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go
index e29434ff..e6267c3d 100644
--- a/internal/ui/prompt_test.go
+++ b/internal/ui/prompt_test.go
@@ -99,7 +99,7 @@ func TestPromptColor(t *testing.T) {
model.AddListener(prompt)
model.SetActive(true)
- assert.Equal(t, prompt.GetBorderColor(), testCase.expectedColor)
+ assert.Equal(t, testCase.expectedColor, prompt.GetBorderColor())
}
}
@@ -146,6 +146,6 @@ func TestPromptStyleChanged(t *testing.T) {
prompt.StylesChanged(newStyles)
model.SetActive(true)
- assert.Equal(t, prompt.GetBorderColor(), testCase.expectedColor)
+ assert.Equal(t, testCase.expectedColor, prompt.GetBorderColor())
}
}
diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go
index 155ef371..ef1d75c5 100644
--- a/internal/ui/select_table.go
+++ b/internal/ui/select_table.go
@@ -107,8 +107,8 @@ func (s *SelectTable) SelectRow(r, c int, broadcast bool) {
if !broadcast {
s.SetSelectionChangedFunc(nil)
}
- if c := s.model.RowCount(); c > 0 && r-1 > c {
- r = c + 1
+ if count := s.model.RowCount(); count > 0 && r-1 > count {
+ r = count + 1
}
defer s.SetSelectionChangedFunc(s.selectionChanged)
s.Select(r, c)
diff --git a/internal/ui/splash.go b/internal/ui/splash.go
index 3fa2f131..74dc7a35 100644
--- a/internal/ui/splash.go
+++ b/internal/ui/splash.go
@@ -58,7 +58,7 @@ func NewSplash(styles *config.Styles, version string) *Splash {
return &s
}
-func (s *Splash) layoutLogo(t *tview.TextView, styles *config.Styles) {
+func (*Splash) layoutLogo(t *tview.TextView, styles *config.Styles) {
logo := strings.Join(LogoBig, fmt.Sprintf("\n[%s::b]", styles.Body().LogoColor))
_, _ = fmt.Fprintf(t, "%s[%s::b]%s\n",
strings.Repeat("\n", 2),
@@ -66,6 +66,6 @@ func (s *Splash) layoutLogo(t *tview.TextView, styles *config.Styles) {
logo)
}
-func (s *Splash) layoutRev(t *tview.TextView, rev string, styles *config.Styles) {
+func (*Splash) layoutRev(t *tview.TextView, rev string, styles *config.Styles) {
_, _ = fmt.Fprintf(t, "[%s::b]Revision [red::b]%s", styles.Body().FgColor, rev)
}
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 5f41915c..ac515007 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -37,7 +37,7 @@ type (
// Table represents tabular data.
type Table struct {
*SelectTable
- gvr client.GVR
+ gvr *client.GVR
sortCol model1.SortColumn
manualSort bool
Path string
@@ -59,7 +59,7 @@ type Table struct {
}
// NewTable returns a new table view.
-func NewTable(gvr client.GVR) *Table {
+func NewTable(gvr *client.GVR) *Table {
return &Table{
SelectTable: &SelectTable{
Table: tview.NewTable(),
@@ -140,6 +140,7 @@ func (t *Table) SetViewSetting(vs *config.ViewSetting) bool {
if !t.viewSetting.Equals(vs) {
t.viewSetting = vs
+ slog.Debug("Updating custom view setting", slogs.GVR, t.gvr, slogs.ViewSetting, vs)
t.model.SetViewSetting(t.ctx, vs)
return true
}
@@ -178,7 +179,7 @@ func (t *Table) Init(ctx context.Context) {
}
// GVR returns a resource descriptor.
-func (t *Table) GVR() client.GVR { return t.gvr }
+func (t *Table) GVR() *client.GVR { return t.gvr }
// ViewSettingsChanged notifies listener the view configuration changed.
func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) {
@@ -250,7 +251,7 @@ func (t *Table) FilterInput(r rune) bool {
}
// Filter filters out table data.
-func (t *Table) Filter(q string) {
+func (t *Table) Filter(string) {
t.ClearSelection()
t.doUpdate(t.filtered(t.GetModel().Peek()))
t.UpdateTitle()
@@ -263,7 +264,7 @@ func (t *Table) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (t *Table) ExtraHints() map[string]string {
+func (*Table) ExtraHints() map[string]string {
return nil
}
@@ -433,7 +434,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M
// SortColCmd designates a sorted column.
func (t *Table) SortColCmd(name string, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
sc := t.getSortCol()
sc.ASC = !sc.ASC
if sc.Name != name {
@@ -448,7 +449,7 @@ func (t *Table) SortColCmd(name string, asc bool) func(evt *tcell.EventKey) *tce
}
// SortInvertCmd reverses sorting order.
-func (t *Table) SortInvertCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) SortInvertCmd(*tcell.EventKey) *tcell.EventKey {
t.toggleSortCol()
t.Refresh()
@@ -500,7 +501,8 @@ func (t *Table) NameColIndex() int {
func (t *Table) AddHeaderCell(col int, h model1.HeaderColumn) {
sc := t.getSortCol()
sortCol := h.Name == sc.Name
- c := tview.NewTableCell(sortIndicator(sortCol, sc.ASC, t.styles.Table(), h.Name))
+ styles := t.styles.Table()
+ c := tview.NewTableCell(sortIndicator(sortCol, sc.ASC, &styles, h.Name))
c.SetExpansion(1)
c.SetSelectable(false)
c.SetAlign(h.Align)
@@ -523,7 +525,7 @@ func (t *Table) CmdBuff() *model.FishBuff {
func (t *Table) ShowDeleted() {
r, _ := t.GetSelection()
cols := t.GetColumnCount()
- for x := 0; x < cols; x++ {
+ for x := range cols {
t.GetCell(r, x).SetAttributes(tcell.AttrDim)
}
}
@@ -561,14 +563,14 @@ func (t *Table) styleTitle() string {
resource = t.gvr.String()
}
- var title string
+ var (
+ title string
+ styles = t.styles.Frame()
+ )
if ns == client.ClusterScope {
- title = SkinTitle(fmt.Sprintf(TitleFmt, resource, render.AsThousands(rc)), t.styles.Frame())
+ title = SkinTitle(fmt.Sprintf(TitleFmt, resource, render.AsThousands(rc)), &styles)
} else {
- title = SkinTitle(fmt.Sprintf(NSTitleFmt, resource, ns, render.AsThousands(rc)), t.styles.Frame())
- }
- if ic := ROIndicator(t.readOnly, t.noIcon); ic != "" {
- title = " " + ic + title
+ title = SkinTitle(fmt.Sprintf(NSTitleFmt, resource, ns, render.AsThousands(rc)), &styles)
}
buff := t.cmdBuff.GetText()
@@ -582,7 +584,7 @@ func (t *Table) styleTitle() string {
return title
}
- return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), t.styles.Frame())
+ return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), &styles)
}
// ROIndicator returns an icon showing whether the session is in readonly mode or not.
diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go
index 04ed6462..9c783cac 100644
--- a/internal/ui/table_helper.go
+++ b/internal/ui/table_helper.go
@@ -67,7 +67,7 @@ func TrimLabelSelector(s string) string {
}
// SkinTitle decorates a title.
-func SkinTitle(fmat string, style config.Frame) string {
+func SkinTitle(fmat string, style *config.Frame) string {
bgColor := style.Title.BgColor
if bgColor == config.DefaultColor {
bgColor = config.TransparentColor
@@ -82,7 +82,7 @@ func SkinTitle(fmat string, style config.Frame) string {
return fmat
}
-func sortIndicator(sort, asc bool, style config.Table, name string) string {
+func sortIndicator(sort, asc bool, style *config.Table, name string) string {
if !sort {
return name
}
diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go
index b93c5507..1c86d459 100644
--- a/internal/ui/table_test.go
+++ b/internal/ui/table_test.go
@@ -70,35 +70,35 @@ type mockModel struct{}
var _ ui.Tabular = &mockModel{}
-func (t *mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
-func (t *mockModel) SetInstance(string) {}
-func (t *mockModel) SetLabelFilter(string) {}
-func (t *mockModel) GetLabelFilter() string { return "" }
-func (t *mockModel) Empty() bool { return false }
-func (t *mockModel) RowCount() int { return 1 }
-func (t *mockModel) HasMetrics() bool { return true }
-func (t *mockModel) Peek() *model1.TableData { return makeTableData() }
-func (t *mockModel) Refresh(context.Context) error { return nil }
-func (t *mockModel) ClusterWide() bool { return false }
-func (t *mockModel) GetNamespace() string { return "blee" }
-func (t *mockModel) SetNamespace(string) {}
-func (t *mockModel) ToggleToast() {}
-func (t *mockModel) AddListener(model.TableListener) {}
-func (t *mockModel) RemoveListener(model.TableListener) {}
-func (t *mockModel) Watch(context.Context) error { return nil }
-func (t *mockModel) Get(ctx context.Context, path string) (runtime.Object, error) { return nil, nil }
-func (t *mockModel) InNamespace(string) bool { return true }
-func (t *mockModel) SetRefreshRate(time.Duration) {}
+func (*mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
+func (*mockModel) SetInstance(string) {}
+func (*mockModel) SetLabelFilter(string) {}
+func (*mockModel) GetLabelFilter() string { return "" }
+func (*mockModel) Empty() bool { return false }
+func (*mockModel) RowCount() int { return 1 }
+func (*mockModel) HasMetrics() bool { return true }
+func (*mockModel) Peek() *model1.TableData { return makeTableData() }
+func (*mockModel) Refresh(context.Context) error { return nil }
+func (*mockModel) ClusterWide() bool { return false }
+func (*mockModel) GetNamespace() string { return "blee" }
+func (*mockModel) SetNamespace(string) {}
+func (*mockModel) ToggleToast() {}
+func (*mockModel) AddListener(model.TableListener) {}
+func (*mockModel) RemoveListener(model.TableListener) {}
+func (*mockModel) Watch(context.Context) error { return nil }
+func (*mockModel) Get(context.Context, string) (runtime.Object, error) { return nil, nil }
+func (*mockModel) InNamespace(string) bool { return true }
+func (*mockModel) SetRefreshRate(time.Duration) {}
-func (t *mockModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
+func (*mockModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
return nil
}
-func (t *mockModel) Describe(context.Context, string) (string, error) {
+func (*mockModel) Describe(context.Context, string) (string, error) {
return "", nil
}
-func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) {
+func (*mockModel) ToYAML(context.Context, string) (string, error) {
return "", nil
}
diff --git a/internal/ui/tree.go b/internal/ui/tree.go
index 5af3046b..fd339cd4 100644
--- a/internal/ui/tree.go
+++ b/internal/ui/tree.go
@@ -37,7 +37,7 @@ func NewTree() *Tree {
}
// Init initializes the view.
-func (t *Tree) Init(ctx context.Context) error {
+func (t *Tree) Init(context.Context) error {
t.BindKeys()
t.SetBorder(true)
t.SetBorderAttributes(tcell.AttrBold)
@@ -85,7 +85,7 @@ func (t *Tree) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (t *Tree) ExtraHints() map[string]string {
+func (*Tree) ExtraHints() map[string]string {
return nil
}
@@ -105,11 +105,11 @@ func (t *Tree) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
-func (t *Tree) noopCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (*Tree) noopCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
-func (t *Tree) toggleCollapseCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Tree) toggleCollapseCmd(*tcell.EventKey) *tcell.EventKey {
t.expandNodes = !t.expandNodes
t.GetRoot().Walk(func(node, parent *tview.TreeNode) bool {
if parent != nil {
diff --git a/internal/view/actions.go b/internal/view/actions.go
index b15bf7d1..01dd5628 100644
--- a/internal/view/actions.go
+++ b/internal/view/actions.go
@@ -14,6 +14,7 @@ import (
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/ui/dialog"
"github.com/derailed/tcell/v2"
+ "k8s.io/apimachinery/pkg/util/sets"
)
// AllScopes represents actions available for all views.
@@ -21,9 +22,16 @@ const AllScopes = "all"
// Runner represents a runnable action handler.
type Runner interface {
+ // App returns the current app.
App() *App
+
+ // GetSelectedItem returns the current selected item.
GetSelectedItem() string
- Aliases() map[string]struct{}
+
+ // Aliases returns all aliases assoxciated with the view GVR.
+ Aliases() sets.Set[string]
+
+ // EnvFn returns the current environment function.
EnvFn() EnvFunc
}
@@ -45,7 +53,7 @@ func includes(aliases []string, s string) bool {
return false
}
-func inScope(scopes []string, aliases map[string]struct{}) bool {
+func inScope(scopes []string, aliases sets.Set[string]) bool {
if hasAll(scopes) {
return true
}
@@ -107,7 +115,7 @@ func hotKeyActions(r Runner, aa *ui.KeyActions) error {
}
func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
r.App().gotoResource(cmd, path, clearStack, true)
return nil
}
@@ -134,30 +142,30 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
aliases = r.Aliases()
ro = r.App().Config.IsReadOnly()
)
- for k, plugin := range pp.Plugins {
- if !inScope(plugin.Scopes, aliases) || (ro && plugin.Dangerous) {
+ for k := range pp.Plugins {
+ if !inScope(pp.Plugins[k].Scopes, aliases) || (ro && pp.Plugins[k].Dangerous) {
continue
}
-
- key, err := asKey(plugin.ShortCut)
+ key, err := asKey(pp.Plugins[k].ShortCut)
if err != nil {
errs = errors.Join(errs, err)
continue
}
if _, ok := aa.Get(key); ok {
- if !plugin.Override {
- errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k))
+ if !pp.Plugins[k].Override {
+ errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", pp.Plugins[k].ShortCut, k))
continue
}
slog.Debug("Plugin overrode action shortcut",
slogs.Plugin, k,
- slogs.Key, plugin.ShortCut,
+ slogs.Key, pp.Plugins[k].ShortCut,
)
}
+ plugin := pp.Plugins[k]
aa.Add(key, ui.NewKeyActionWithOpts(
- plugin.Description,
- pluginAction(r, plugin),
+ pp.Plugins[k].Description,
+ pluginAction(r, &plugin),
ui.ActionOpts{
Visible: true,
Plugin: true,
@@ -169,7 +177,7 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
return errs
}
-func pluginAction(r Runner, p config.Plugin) ui.ActionHandler {
+func pluginAction(r Runner, p *config.Plugin) ui.ActionHandler {
return func(evt *tcell.EventKey) *tcell.EventKey {
path := r.GetSelectedItem()
if path == "" {
@@ -196,7 +204,7 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler {
pipes: p.Pipes,
args: args,
}
- suspend, errChan, statusChan := run(r.App(), opts)
+ suspend, errChan, statusChan := run(r.App(), &opts)
if !suspend {
r.App().Flash().Infof("Plugin command failed: %q", p.Description)
return
@@ -220,11 +228,11 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler {
}
}
}()
-
}
if p.Confirm {
msg := fmt.Sprintf("Run?\n%s %s", p.Command, strings.Join(args, " "))
- dialog.ShowConfirm(r.App().Styles.Dialog(), r.App().Content.Pages, "Confirm "+p.Description, msg, cb, func() {})
+ d := r.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, r.App().Content.Pages, "Confirm "+p.Description, msg, cb, func() {})
return nil
}
cb()
diff --git a/internal/view/actions_test.go b/internal/view/actions_test.go
index 051927fc..b64e446c 100644
--- a/internal/view/actions_test.go
+++ b/internal/view/actions_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "k8s.io/apimachinery/pkg/util/sets"
)
func init() {
@@ -19,13 +20,19 @@ func TestHasAll(t *testing.T) {
scopes []string
e bool
}{
- "empty": {},
- "all": {scopes: []string{"blee", "duh", AllScopes}, e: true},
- "no-all": {scopes: []string{"blee", "duh", "alla"}},
+ "empty": {},
+
+ "all": {
+ scopes: []string{"blee", "duh", AllScopes},
+ e: true,
+ },
+
+ "none": {
+ scopes: []string{"blee", "duh", "alla"},
+ },
}
- for k := range uu {
- u := uu[k]
+ for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, hasAll(u.scopes))
})
@@ -39,12 +46,20 @@ func TestIncludes(t *testing.T) {
e bool
}{
"empty": {},
- "yes": {s: "blee", ss: []string{"yo", "duh", "blee"}, e: true},
- "no": {s: "blue", ss: []string{"yo", "duh", "blee"}},
+
+ "yes": {
+ s: "blee",
+ ss: []string{"yo", "duh", "blee"},
+ e: true,
+ },
+
+ "no": {
+ s: "blue",
+ ss: []string{"yo", "duh", "blee"},
+ },
}
- for k := range uu {
- u := uu[k]
+ for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, includes(u.ss, u.s))
})
@@ -54,19 +69,38 @@ func TestIncludes(t *testing.T) {
func TestInScope(t *testing.T) {
uu := map[string]struct {
ss []string
- aa map[string]struct{}
+ aa sets.Set[string]
e bool
}{
- "empty": {},
- "yes": {e: true, ss: []string{"blee", "duh", "fred"}, aa: map[string]struct{}{"blee": {}, "fred": {}, "duh": {}}},
- "no": {ss: []string{"blee", "duh", "fred"}, aa: map[string]struct{}{"blee1": {}, "fred1": {}}},
- "empty scopes": {aa: map[string]struct{}{"blee1": {}, "fred1": {}}},
- "empty aliases": {ss: []string{"blee1", "fred1"}},
- "all": {e: true, ss: []string{AllScopes}, aa: map[string]struct{}{"blee1": {}, "fred1": {}}},
+ "empty": {},
+
+ "yes": {
+ e: true,
+ ss: []string{"blee", "duh", "fred"},
+ aa: sets.New("blee", "fred", "duh"),
+ },
+
+ "no": {
+ ss: []string{"blee", "duh", "fred"},
+ aa: sets.New("blee1", "fred1"),
+ },
+
+ "no-scopes": {
+ aa: sets.New("aa", "blee1", "fred1"),
+ },
+
+ "no-aliases": {
+ ss: []string{"blee1", "fred1"},
+ },
+
+ "all": {
+ e: true,
+ ss: []string{AllScopes},
+ aa: sets.New("blee1", "fred1"),
+ },
}
- for k := range uu {
- u := uu[k]
+ for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, inScope(u.ss, u.aa))
})
diff --git a/internal/view/alias.go b/internal/view/alias.go
index a8a99dfe..a676572b 100644
--- a/internal/view/alias.go
+++ b/internal/view/alias.go
@@ -20,7 +20,7 @@ type Alias struct {
}
// NewAlias returns a new alias view.
-func NewAlias(gvr client.GVR) ResourceViewer {
+func NewAlias(gvr *client.GVR) ResourceViewer {
a := Alias{
ResourceViewer: NewBrowser(gvr),
}
@@ -66,8 +66,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if path == "" {
return evt
}
- gvr := client.NewGVR(path)
- a.App().gotoResource(gvr.String(), "", true, true)
+ a.App().gotoResource(client.NewGVR(path).String(), "", true, true)
return nil
}
diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go
index ad16d412..06a611a6 100644
--- a/internal/view/alias_test.go
+++ b/internal/view/alias_test.go
@@ -19,21 +19,22 @@ import (
"github.com/derailed/k9s/internal/view"
"github.com/derailed/tcell/v2"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestAliasNew(t *testing.T) {
- v := view.NewAlias(client.NewGVR("aliases"))
+ v := view.NewAlias(client.AliGVR)
- assert.Nil(t, v.Init(makeContext()))
+ require.NoError(t, v.Init(makeContext()))
assert.Equal(t, "Aliases", v.Name())
- assert.Equal(t, 6, len(v.Hints()))
+ assert.Len(t, v.Hints(), 6)
}
func TestAliasSearch(t *testing.T) {
- v := view.NewAlias(client.NewGVR("aliases"))
- assert.Nil(t, v.Init(makeContext()))
+ v := view.NewAlias(client.AliGVR)
+ require.NoError(t, v.Init(makeContext()))
v.GetTable().SetModel(&mockModel{})
v.GetTable().Refresh()
v.App().Prompt().SetModel(v.GetTable().CmdBuff())
@@ -44,8 +45,8 @@ func TestAliasSearch(t *testing.T) {
}
func TestAliasGoto(t *testing.T) {
- v := view.NewAlias(client.NewGVR("aliases"))
- assert.Nil(t, v.Init(makeContext()))
+ v := view.NewAlias(client.AliGVR)
+ require.NoError(t, v.Init(makeContext()))
v.GetTable().Select(0, 0)
b := buffL{}
@@ -64,12 +65,12 @@ type buffL struct {
changed int
}
-func (b *buffL) BufferChanged(_, _ string) {
+func (b *buffL) BufferChanged(string, string) {
b.changed++
}
-func (b *buffL) BufferCompleted(_, _ string) {}
+func (*buffL) BufferCompleted(string, string) {}
-func (b *buffL) BufferActive(state bool, kind model.BufferKind) {
+func (b *buffL) BufferActive(bool, model.BufferKind) {
b.active++
}
@@ -86,44 +87,44 @@ var (
_ ui.Suggester = (*mockModel)(nil)
)
-func (t *mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
-func (t *mockModel) CurrentSuggestion() (string, bool) { return "", false }
-func (t *mockModel) NextSuggestion() (string, bool) { return "", false }
-func (t *mockModel) PrevSuggestion() (string, bool) { return "", false }
-func (t *mockModel) ClearSuggestions() {}
-func (t *mockModel) SetInstance(string) {}
-func (t *mockModel) SetLabelFilter(string) {}
-func (t *mockModel) GetLabelFilter() string { return "" }
-func (t *mockModel) Empty() bool { return false }
-func (t *mockModel) RowCount() int { return 1 }
-func (t *mockModel) HasMetrics() bool { return true }
-func (t *mockModel) Peek() *model1.TableData { return makeTableData() }
-func (t *mockModel) ClusterWide() bool { return false }
-func (t *mockModel) GetNamespace() string { return "blee" }
-func (t *mockModel) SetNamespace(string) {}
-func (t *mockModel) ToggleToast() {}
-func (t *mockModel) AddListener(model.TableListener) {}
-func (t *mockModel) RemoveListener(model.TableListener) {}
-func (t *mockModel) Watch(context.Context) error { return nil }
-func (t *mockModel) Refresh(context.Context) error { return nil }
-func (t *mockModel) Get(context.Context, string) (runtime.Object, error) {
+func (*mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
+func (*mockModel) CurrentSuggestion() (string, bool) { return "", false }
+func (*mockModel) NextSuggestion() (string, bool) { return "", false }
+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) Empty() bool { return false }
+func (*mockModel) RowCount() int { return 1 }
+func (*mockModel) HasMetrics() bool { return true }
+func (*mockModel) Peek() *model1.TableData { return makeTableData() }
+func (*mockModel) ClusterWide() bool { return false }
+func (*mockModel) GetNamespace() string { return "blee" }
+func (*mockModel) SetNamespace(string) {}
+func (*mockModel) ToggleToast() {}
+func (*mockModel) AddListener(model.TableListener) {}
+func (*mockModel) RemoveListener(model.TableListener) {}
+func (*mockModel) Watch(context.Context) error { return nil }
+func (*mockModel) Refresh(context.Context) error { return nil }
+func (*mockModel) Get(context.Context, string) (runtime.Object, error) {
return nil, nil
}
-func (t *mockModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
+func (*mockModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
return nil
}
-func (t *mockModel) Describe(context.Context, string) (string, error) {
+func (*mockModel) Describe(context.Context, string) (string, error) {
return "", nil
}
-func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) {
+func (*mockModel) ToYAML(context.Context, string) (string, error) {
return "", nil
}
-func (t *mockModel) InNamespace(string) bool { return true }
-func (t *mockModel) SetRefreshRate(time.Duration) {}
+func (*mockModel) InNamespace(string) bool { return true }
+func (*mockModel) SetRefreshRate(time.Duration) {}
func makeTableData() *model1.TableData {
return model1.NewTableDataWithRows(
diff --git a/internal/view/app.go b/internal/view/app.go
index aa2b6659..8735b41c 100644
--- a/internal/view/app.go
+++ b/internal/view/app.go
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"log/slog"
+ "maps"
"os"
"os/signal"
"runtime"
@@ -93,7 +94,7 @@ func (a *App) ConOK() bool {
}
// Init initializes the application.
-func (a *App) Init(version string, rate int) error {
+func (a *App) Init(version string, _ int) error {
a.version = model.NormalizeVersion(version)
ctx := context.WithValue(context.Background(), internal.KeyApp, a)
@@ -139,7 +140,7 @@ func (a *App) Init(version string, rate int) error {
return nil
}
-func (a *App) stopImgScanner() {
+func (*App) stopImgScanner() {
if vul.ImgScanner != nil {
vul.ImgScanner.Stop()
}
@@ -173,7 +174,7 @@ func (a *App) layout(ctx context.Context) {
}
}
-func (a *App) initSignals() {
+func (*App) initSignals() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGHUP)
@@ -198,8 +199,8 @@ func (a *App) suggestCommand() model.SuggestionFunc {
}
ls := strings.ToLower(s)
- for _, k := range a.command.alias.Keys() {
- if suggest, ok := cmd.ShouldAddSuggest(ls, k); ok {
+ for alias := range maps.Keys(a.command.alias.Alias) {
+ if suggest, ok := cmd.ShouldAddSuggest(ls, alias); ok {
entries = append(entries, suggest)
}
}
@@ -253,7 +254,7 @@ func (a *App) bindKeys() {
}))
}
-func (a *App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey {
+func (*App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey {
slog.Debug("GOR", slogs.GOR, runtime.NumGoroutine())
bb := make([]byte, 5_000_000)
runtime.Stack(bb, true)
@@ -401,7 +402,7 @@ func (a *App) refreshCluster(context.Context) error {
c.Stop()
}
- count, maxConnRetry := atomic.LoadInt32(&a.conRetry), int32(a.Config.K9s.MaxConnRetry)
+ count, maxConnRetry := atomic.LoadInt32(&a.conRetry), a.Config.K9s.MaxConnRetry
if count >= maxConnRetry {
slog.Error("Conn check failed. Bailing out!",
slogs.Retry, count,
@@ -601,7 +602,7 @@ func (a *App) setIndicator(l model.FlashLevel, msg string) {
}
// PrevCmd pops the command stack.
-func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (a *App) PrevCmd(*tcell.EventKey) *tcell.EventKey {
if !a.Content.IsLast() {
a.Content.Pop()
}
@@ -646,7 +647,8 @@ func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (a *App) cowCmd(msg string) {
- dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, msg)
+ d := a.Styles.Dialog()
+ dialog.ShowError(&d, a.Content.Pages, msg)
}
func (a *App) dirCmd(path string, pushCmd bool) error {
@@ -739,30 +741,31 @@ func (a *App) lastCommand(evt *tcell.EventKey) *tcell.EventKey {
if len(cmds) < 1 {
a.App.Flash().Warn("No previous view to switch to")
return evt
- } else {
- a.cmdHistory.Last()
- a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
}
+ a.cmdHistory.Last()
+ a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
+
return nil
}
-func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (a *App) aliasCmd(*tcell.EventKey) *tcell.EventKey {
if a.Content.Top() != nil && a.Content.Top().Name() == aliasTitle {
a.Content.Pop()
return nil
}
- if err := a.inject(NewAlias(client.NewGVR("aliases")), false); err != nil {
+ if err := a.inject(NewAlias(client.AliGVR), false); err != nil {
a.Flash().Err(err)
}
return nil
}
-func (a *App) gotoResource(c, path string, clearStack bool, pushCmd bool) {
+func (a *App) gotoResource(c, path string, clearStack, pushCmd bool) {
err := a.command.run(cmd.NewInterpreter(c), path, clearStack, pushCmd)
if err != nil {
- dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error())
+ d := a.Styles.Dialog()
+ dialog.ShowError(&d, a.Content.Pages, err.Error())
}
}
diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go
index 386241e7..e7043917 100644
--- a/internal/view/benchmark.go
+++ b/internal/view/benchmark.go
@@ -26,7 +26,7 @@ type Benchmark struct {
}
// NewBenchmark returns a new viewer.
-func NewBenchmark(gvr client.GVR) ResourceViewer {
+func NewBenchmark(gvr *client.GVR) ResourceViewer {
b := Benchmark{
ResourceViewer: NewBrowser(gvr),
}
@@ -43,7 +43,7 @@ func (b *Benchmark) benchContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config))
}
-func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr client.GVR, path string) {
+func (b *Benchmark) viewBench(app *App, _ ui.Tabular, _ *client.GVR, path string) {
data, err := readBenchFile(app.Config, b.benchFile())
if err != nil {
app.Flash().Errf("Unable to load bench file %s", err)
diff --git a/internal/view/browser.go b/internal/view/browser.go
index 64667c58..92dd4009 100644
--- a/internal/view/browser.go
+++ b/internal/view/browser.go
@@ -25,6 +25,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/util/sets"
)
// Browser represents a generic resource browser.
@@ -32,7 +33,7 @@ type Browser struct {
*Table
namespaces map[int]string
- meta metav1.APIResource
+ meta *metav1.APIResource
accessor dao.Accessor
contextFn ContextFunc
cancelFn context.CancelFunc
@@ -41,7 +42,7 @@ type Browser struct {
}
// NewBrowser returns a new browser.
-func NewBrowser(gvr client.GVR) ResourceViewer {
+func NewBrowser(gvr *client.GVR) ResourceViewer {
return &Browser{
Table: NewTable(gvr),
}
@@ -62,7 +63,6 @@ func (b *Browser) getUpdating() bool {
// SetCommand sets the current command.
func (b *Browser) SetCommand(cmd *cmd.Interpreter) {
b.GetTable().SetCommand(cmd)
- //b.Table.SetViewSetting(b.app.CustomView().VSFor(cmd)
}
// Init watches all running pods in given namespace.
@@ -79,12 +79,12 @@ func (b *Browser) Init(ctx context.Context) error {
}
b.GetTable().SetColorerFn(colorerFn)
- if err = b.Table.Init(ctx); err != nil {
- return err
+ if e := b.Table.Init(ctx); e != nil {
+ return e
}
ns := client.CleanseNamespace(b.app.Config.ActiveNamespace())
if dao.IsK8sMeta(b.meta) && b.app.ConOK() {
- if _, e := b.app.factory.CanForResource(ns, b.GVR().String(), client.ListAccess); e != nil {
+ if _, e := b.app.factory.CanForResource(ns, b.GVR(), client.ListAccess); e != nil {
return e
}
}
@@ -178,11 +178,9 @@ func (b *Browser) Start() {
// Stop terminates browser updates.
func (b *Browser) Stop() {
b.mx.Lock()
- {
- if b.cancelFn != nil {
- b.cancelFn()
- b.cancelFn = nil
- }
+ if b.cancelFn != nil {
+ b.cancelFn()
+ b.cancelFn = nil
}
b.mx.Unlock()
b.GetModel().RemoveListener(b)
@@ -200,7 +198,7 @@ func (b *Browser) SetLabelFilter(labels map[string]string) {
}
// BufferChanged indicates the buffer was changed.
-func (b *Browser) BufferChanged(_, _ string) {}
+func (*Browser) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted.
func (b *Browser) BufferCompleted(text, _ string) {
@@ -212,7 +210,7 @@ func (b *Browser) BufferCompleted(text, _ string) {
}
// BufferActive indicates the buff activity changed.
-func (b *Browser) BufferActive(state bool, k model.BufferKind) {
+func (b *Browser) BufferActive(state bool, _ model.BufferKind) {
if state {
return
}
@@ -234,7 +232,6 @@ func (b *Browser) BufferActive(state bool, k model.BufferKind) {
if b.GetRowCount() > 1 {
b.App().filterHistory.Push(b.CmdBuff().GetText())
}
-
})
}
@@ -242,12 +239,10 @@ func (b *Browser) prepareContext() context.Context {
ctx := b.defaultContext()
b.mx.Lock()
- {
- if b.cancelFn != nil {
- b.cancelFn()
- }
- ctx, b.cancelFn = context.WithCancel(ctx)
+ if b.cancelFn != nil {
+ b.cancelFn()
}
+ ctx, b.cancelFn = context.WithCancel(ctx)
b.mx.Unlock()
if b.contextFn != nil {
@@ -257,9 +252,7 @@ func (b *Browser) prepareContext() context.Context {
b.Path = path
}
b.mx.Lock()
- {
- b.SetContext(ctx)
- }
+ b.SetContext(ctx)
b.mx.Unlock()
return ctx
@@ -279,8 +272,8 @@ func (b *Browser) SetContextFn(f ContextFunc) { b.contextFn = f }
func (b *Browser) GetTable() *Table { return b.Table }
// Aliases returns all available aliases.
-func (b *Browser) Aliases() map[string]struct{} {
- return aliasesFor(b.meta, b.app.command.AliasesFor(b.meta.Name))
+func (b *Browser) Aliases() sets.Set[string] {
+ return aliases(b.meta, b.app.command.AliasesFor(client.NewGVRFromMeta(b.meta)))
}
// ----------------------------------------------------------------------------
@@ -446,7 +439,7 @@ func (b *Browser) editCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func editRes(app *App, gvr client.GVR, path string) error {
+func editRes(app *App, gvr *client.GVR, path string) error {
if path == "" {
return fmt.Errorf("nothing selected %q", path)
}
@@ -454,20 +447,19 @@ func editRes(app *App, gvr client.GVR, path string) error {
if client.IsClusterScoped(ns) {
ns = client.BlankNamespace
}
- if gvr.String() == "v1/namespaces" {
+ if gvr == client.NsGVR {
ns = n
}
- if ok, err := app.Conn().CanI(ns, gvr.String(), n, client.PatchAccess); !ok || err != nil {
+ if ok, err := app.Conn().CanI(ns, gvr, n, client.PatchAccess); !ok || err != nil {
return fmt.Errorf("current user can't edit resource %s", gvr)
}
args := make([]string, 0, 10)
- args = append(args, "edit")
- args = append(args, gvr.FQN(n))
+ args = append(args, "edit", gvr.FQN(n))
if ns != client.BlankNamespace {
args = append(args, "-n", ns)
}
- if err := runK(app, shellOpts{clear: true, args: args}); err != nil {
+ if err := runK(app, &shellOpts{clear: true, args: args}); err != nil {
app.Flash().Errf("Edit command failed: %s", err)
}
@@ -482,7 +474,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
}
ns := b.namespaces[i]
- auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), "", client.ListAccess)
+ auth, err := b.App().factory.Client().CanI(ns, b.GVR(), "", client.ListAccess)
if !auth {
if err == nil {
err = fmt.Errorf("current user can't access namespace %s", ns)
@@ -617,7 +609,8 @@ func (b *Browser) namespaceActions(aa *ui.KeyActions) {
}
func (b *Browser) simpleDelete(selections []string, msg string) {
- dialog.ShowConfirm(b.app.Styles.Dialog(), b.app.Content.Pages, "Confirm Delete", msg, func() {
+ d := b.app.Styles.Dialog()
+ dialog.ShowConfirm(&d, b.app.Content.Pages, "Confirm Delete", msg, func() {
b.ShowDeleted()
if len(selections) > 1 {
b.app.Flash().Infof("Delete %d marked %s", len(selections), b.GVR().R())
@@ -663,5 +656,6 @@ func (b *Browser) resourceDelete(selections []string, msg string) {
}
b.refresh()
}
- dialog.ShowDelete(b.app.Styles.Dialog(), b.app.Content.Pages, msg, okFn, func() {})
+ d := b.app.Styles.Dialog()
+ dialog.ShowDelete(&d, b.app.Content.Pages, msg, okFn, func() {})
}
diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go
index f11c541e..314aa566 100644
--- a/internal/view/cluster_info.go
+++ b/internal/view/cluster_info.go
@@ -54,7 +54,7 @@ func (c *ClusterInfo) StylesChanged(s *config.Styles) {
func (c *ClusterInfo) hasMetrics() bool {
mx := c.app.Conn().HasMetrics()
if mx {
- auth, err := c.app.Conn().CanI("", "metrics.k8s.io/v1beta1/nodes", "", client.ListAccess)
+ auth, err := c.app.Conn().CanI("", client.NmxGVR, "", client.ListAccess)
if err != nil {
slog.Warn("No nodes metrics access", slogs.Error, err)
}
@@ -97,11 +97,11 @@ func (c *ClusterInfo) setCell(row int, s string) int {
}
// ClusterInfoUpdated notifies the cluster meta was updated.
-func (c *ClusterInfo) ClusterInfoUpdated(data model.ClusterMeta) {
+func (c *ClusterInfo) ClusterInfoUpdated(data *model.ClusterMeta) {
c.ClusterInfoChanged(data, data)
}
-func (c *ClusterInfo) warnCell(s string, w bool) string {
+func (*ClusterInfo) warnCell(s string, w bool) string {
if w {
return fmt.Sprintf("[orangered::b]%s", s)
}
@@ -110,11 +110,16 @@ func (c *ClusterInfo) warnCell(s string, w bool) string {
}
// ClusterInfoChanged notifies the cluster meta was changed.
-func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) {
+func (c *ClusterInfo) ClusterInfoChanged(prev, curr *model.ClusterMeta) {
c.app.QueueUpdateDraw(func() {
c.Clear()
c.layout()
- row := c.setCell(0, curr.Context)
+
+ context := curr.Context
+ if ic := ui.ROIndicator(c.app.Config.IsReadOnly(), c.app.Config.K9s.UI.NoIcons); ic != "" {
+ context += " " + ic
+ }
+ row := c.setCell(0, context)
row = c.setCell(row, curr.Cluster)
row = c.setCell(row, curr.User)
if curr.K9sLatest != "" {
@@ -139,12 +144,12 @@ const defconFmt = "%s %s level!"
func (c *ClusterInfo) setDefCon(cpu, mem int) {
var set bool
- l := c.app.Config.K9s.Thresholds.LevelFor("cpu", cpu)
+ l := c.app.Config.K9s.Thresholds.LevelFor(config.CPU, cpu)
if l > config.SeverityLow {
c.app.Status(flashLevel(l), fmt.Sprintf(defconFmt, flashMessage(l), "CPU"))
set = true
}
- l = c.app.Config.K9s.Thresholds.LevelFor("memory", mem)
+ l = c.app.Config.K9s.Thresholds.LevelFor(config.MEM, mem)
if l > config.SeverityLow {
c.app.Status(flashLevel(l), fmt.Sprintf(defconFmt, flashMessage(l), "Memory"))
set = true
@@ -155,7 +160,7 @@ func (c *ClusterInfo) setDefCon(cpu, mem int) {
}
func (c *ClusterInfo) updateStyle() {
- for row := 0; row < c.GetRowCount(); row++ {
+ for row := range c.GetRowCount() {
c.GetCell(row, 0).SetTextColor(c.styles.K9s.Info.FgColor.Color())
c.GetCell(row, 0).SetBackgroundColor(c.styles.BgColor())
var s tcell.Style
@@ -170,7 +175,7 @@ func (c *ClusterInfo) updateStyle() {
// Helpers...
func flashLevel(l config.SeverityLevel) model.FlashLevel {
- // nolint:exhaustive
+ //nolint:exhaustive
switch l {
case config.SeverityHigh:
return model.FlashErr
@@ -182,7 +187,7 @@ func flashLevel(l config.SeverityLevel) model.FlashLevel {
}
func flashMessage(l config.SeverityLevel) string {
- // nolint:exhaustive
+ //nolint:exhaustive
switch l {
case config.SeverityHigh:
return "Critical"
diff --git a/internal/view/cm.go b/internal/view/cm.go
index 3a18477d..f7850df1 100644
--- a/internal/view/cm.go
+++ b/internal/view/cm.go
@@ -19,7 +19,7 @@ type ConfigMap struct {
}
// NewConfigMap returns a new viewer.
-func NewConfigMap(gvr client.GVR) ResourceViewer {
+func NewConfigMap(gvr *client.GVR) ResourceViewer {
s := ConfigMap{
ResourceViewer: NewOwnerExtender(
NewBrowser(gvr),
@@ -35,10 +35,10 @@ func (s *ConfigMap) bindKeys(aa *ui.KeyActions) {
}
func (s *ConfigMap) refCmd(evt *tcell.EventKey) *tcell.EventKey {
- return scanRefs(evt, s.App(), s.GetTable(), dao.CmGVR)
+ return scanRefs(evt, s.App(), s.GetTable(), client.CmGVR)
}
-func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.EventKey {
+func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr *client.GVR) *tcell.EventKey {
path := t.GetSelectedItem()
if path == "" {
return evt
@@ -55,7 +55,7 @@ func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.Even
return nil
}
a.Flash().Infof("Viewing references for %s::%s", gvr, path)
- view := NewReference(client.NewGVR("references"))
+ view := NewReference(client.RefGVR)
view.SetContextFn(refContext(gvr, path, false))
if err := a.inject(view, false); err != nil {
a.Flash().Err(err)
@@ -64,7 +64,7 @@ func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.Even
return nil
}
-func refContext(gvr client.GVR, path string, wait bool) ContextFunc {
+func refContext(gvr *client.GVR, path string, wait bool) ContextFunc {
return func(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, path)
ctx = context.WithValue(ctx, internal.KeyGVR, gvr)
diff --git a/internal/view/cm_test.go b/internal/view/cm_test.go
index 6e787def..158484f8 100644
--- a/internal/view/cm_test.go
+++ b/internal/view/cm_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestConfigMapNew(t *testing.T) {
- s := view.NewConfigMap(client.NewGVR("v1/configmaps"))
+ s := view.NewConfigMap(client.CmGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "ConfigMaps", s.Name())
- assert.Equal(t, 7, len(s.Hints()))
+ assert.Len(t, s.Hints(), 7)
}
diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go
index 7f8a8f3b..5aad75d1 100644
--- a/internal/view/cmd/args.go
+++ b/internal/view/cmd/args.go
@@ -19,61 +19,62 @@ const (
type args map[string]string
func newArgs(p *Interpreter, aa []string) args {
- args := make(args, len(aa))
+ arguments := make(args, len(aa))
if len(aa) == 0 {
- return args
+ return arguments
}
for i := 0; i < len(aa); i++ {
a := strings.TrimSpace(aa[i])
switch {
case strings.Index(a, contextFlag) == 0:
- args[contextKey] = a[1:]
+ arguments[contextKey] = a[1:]
case strings.Index(a, fuzzyFlag) == 0:
if a == fuzzyFlag {
- if i++; i < len(aa) {
- args[fuzzyKey] = strings.ToLower(strings.TrimSpace(aa[i]))
+ i++
+ if i < len(aa) {
+ arguments[fuzzyKey] = strings.ToLower(strings.TrimSpace(aa[i]))
}
} else {
- args[fuzzyKey] = strings.ToLower(a[2:])
+ arguments[fuzzyKey] = strings.ToLower(a[2:])
}
case strings.Index(a, filterFlag) == 0:
if p.IsDirCmd() {
- if _, ok := args[topicKey]; !ok {
- args[topicKey] = a
+ if _, ok := arguments[topicKey]; !ok {
+ arguments[topicKey] = a
}
} else {
- args[filterKey] = strings.ToLower(a[1:])
+ arguments[filterKey] = strings.ToLower(a[1:])
}
case strings.Contains(a, labelFlag):
if ll := ToLabels(a); len(ll) != 0 {
- args[labelKey] = strings.ToLower(a)
+ arguments[labelKey] = strings.ToLower(a)
}
default:
switch {
case p.IsContextCmd():
- args[contextKey] = a
+ arguments[contextKey] = a
case p.IsDirCmd():
- if _, ok := args[topicKey]; !ok {
- args[topicKey] = a
+ if _, ok := arguments[topicKey]; !ok {
+ arguments[topicKey] = a
}
case p.IsXrayCmd():
- if _, ok := args[topicKey]; ok {
- args[nsKey] = strings.ToLower(a)
+ if _, ok := arguments[topicKey]; ok {
+ arguments[nsKey] = strings.ToLower(a)
} else {
- args[topicKey] = strings.ToLower(a)
+ arguments[topicKey] = strings.ToLower(a)
}
default:
- args[nsKey] = strings.ToLower(a)
+ arguments[nsKey] = strings.ToLower(a)
}
}
}
- return args
+ return arguments
}
func (a args) hasFilters() bool {
diff --git a/internal/view/cmd/args_test.go b/internal/view/cmd/args_test.go
index 157d6f60..962a24bb 100644
--- a/internal/view/cmd/args_test.go
+++ b/internal/view/cmd/args_test.go
@@ -121,7 +121,6 @@ func TestFlagsNew(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
l := newArgs(u.i, u.aa)
- assert.Equal(t, len(u.ll), len(l))
assert.Equal(t, u.ll, l)
})
}
diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go
index 0068dcbe..13637f91 100644
--- a/internal/view/cmd/helpers.go
+++ b/internal/view/cmd/helpers.go
@@ -43,17 +43,7 @@ func SuggestSubCommand(command string, namespaces client.NamespaceNames, context
p := NewInterpreter(command)
var suggests []string
switch {
- case p.IsCowCmd():
- fallthrough
- case p.IsHelpCmd():
- fallthrough
- case p.IsAliasCmd():
- fallthrough
- case p.IsBailCmd():
- fallthrough
- case p.IsDirCmd():
- fallthrough
- case p.IsAliasCmd():
+ case p.IsCowCmd(), p.IsHelpCmd(), p.IsAliasCmd(), p.IsBailCmd(), p.IsDirCmd():
return nil
case p.IsXrayCmd():
@@ -109,11 +99,11 @@ func completeNS(s string, nn client.NamespaceNames) []string {
return suggests
}
-func completeCtx(s string, cc []string) []string {
+func completeCtx(s string, contexts []string) []string {
var suggests []string
- for _, ctxName := range cc {
+ for _, ctxName := range contexts {
if suggest, ok := ShouldAddSuggest(s, ctxName); ok {
- if len(s) == 0 {
+ if s == "" {
suggests = append(suggests, " "+suggest)
continue
}
diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go
index fade256e..beaf8c1f 100644
--- a/internal/view/cmd/interpreter.go
+++ b/internal/view/cmd/interpreter.go
@@ -86,47 +86,37 @@ func (c *Interpreter) IsCowCmd() bool {
// IsHelpCmd returns true if help cmd is detected.
func (c *Interpreter) IsHelpCmd() bool {
- _, ok := helpCmd[c.cmd]
- return ok
+ return helpCmd.Has(c.cmd)
}
// IsBailCmd returns true if quit cmd is detected.
func (c *Interpreter) IsBailCmd() bool {
- _, ok := bailCmd[c.cmd]
- return ok
+ return bailCmd.Has(c.cmd)
}
// IsAliasCmd returns true if alias cmd is detected.
func (c *Interpreter) IsAliasCmd() bool {
- _, ok := aliasCmd[c.cmd]
- return ok
+ return aliasCmd.Has(c.cmd)
}
// IsXrayCmd returns true if xray cmd is detected.
func (c *Interpreter) IsXrayCmd() bool {
- _, ok := xrayCmd[c.cmd]
-
- return ok
+ return xrayCmd.Has(c.cmd)
}
// IsContextCmd returns true if context cmd is detected.
func (c *Interpreter) IsContextCmd() bool {
- _, ok := contextCmd[c.cmd]
-
- return ok
+ return contextCmd.Has(c.cmd)
}
// IsNamespaceCmd returns true if ns cmd is detected.
func (c *Interpreter) IsNamespaceCmd() bool {
- _, ok := namespaceCmd[c.cmd]
-
- return ok
+ return namespaceCmd.Has(c.cmd)
}
// IsDirCmd returns true if dir cmd is detected.
func (c *Interpreter) IsDirCmd() bool {
- _, ok := dirCmd[c.cmd]
- return ok
+ return dirCmd.Has(c.cmd)
}
// IsRBACCmd returns true if rbac cmd is detected.
@@ -169,37 +159,40 @@ func (c *Interpreter) CowArg() (string, bool) {
}
// RBACArgs returns the subject and topic is any.
-func (c *Interpreter) RBACArgs() (string, string, bool) {
+func (c *Interpreter) RBACArgs() (subject, verb string, ok bool) {
if !c.IsRBACCmd() {
- return "", "", false
+ return
}
tt := rbacRX.FindStringSubmatch(c.line)
if len(tt) < 3 {
- return "", "", false
+ return
}
+ subject, verb, ok = tt[1], tt[2], true
- return tt[1], tt[2], true
+ return
}
// XRayArgs return the gvr and ns if any.
-func (c *Interpreter) XrayArgs() (string, string, bool) {
+func (c *Interpreter) XrayArgs() (cmd, namespace string, ok bool) {
if !c.IsXrayCmd() {
- return "", "", false
+ return
}
gvr, ok1 := c.args[topicKey]
if !ok1 {
- return "", "", false
+ return
}
ns, ok2 := c.args[nsKey]
switch {
case ok1 && ok2:
- return gvr, ns, true
+ cmd, namespace, ok = gvr, ns, true
case ok1 && !ok2:
- return gvr, "", true
+ cmd, namespace, ok = gvr, "", true
default:
- return "", "", false
+ return
}
+
+ return
}
// FilterArg returns the current filter if any.
diff --git a/internal/view/cmd/types.go b/internal/view/cmd/types.go
index 740a59c7..522f6c68 100644
--- a/internal/view/cmd/types.go
+++ b/internal/view/cmd/types.go
@@ -3,7 +3,11 @@
package cmd
-import "regexp"
+import (
+ "regexp"
+
+ "k8s.io/apimachinery/pkg/util/sets"
+)
const (
cowCmd = "cow"
@@ -16,44 +20,45 @@ const (
)
var (
- rbacRX = regexp.MustCompile(`^can\s+([u|g|s]):\s*([\w-:]+)\s*$`)
+ rbacRX = regexp.MustCompile(`^can\s+([ugs]):\s*([\w-:]+)\s*$`)
- contextCmd = map[string]struct{}{
- "ctx": {},
- "context": {},
- "contexts": {},
- }
- namespaceCmd = map[string]struct{}{
- "ns": {},
- "namespace": {},
- "namespaces": {},
- }
- dirCmd = map[string]struct{}{
- "dir": {},
- "d": {},
- "ls": {},
- }
- bailCmd = map[string]struct{}{
- "q": {},
- "q!": {},
- "qa": {},
- "Q": {},
- "quit": {},
- "exit": {},
- }
- helpCmd = map[string]struct{}{
- "?": {},
- "h": {},
- "help": {},
- }
- aliasCmd = map[string]struct{}{
- "a": {},
- "alias": {},
- "aliases": {},
- }
- xrayCmd = map[string]struct{}{
- "x": {},
- "xr": {},
- "xray": {},
- }
+ contextCmd = sets.New(
+ "ctx",
+ "context",
+ "contexts",
+ )
+ namespaceCmd = sets.New(
+ "ns",
+ "namespace",
+ "namespaces",
+ )
+ dirCmd = sets.New(
+ "dir",
+ "dirs",
+ "d",
+ "ls",
+ )
+ bailCmd = sets.New(
+ "q",
+ "q!",
+ "qa",
+ "Q",
+ "quit",
+ "exit",
+ )
+ helpCmd = sets.New(
+ "?",
+ "h",
+ "help",
+ )
+ aliasCmd = sets.New(
+ "a",
+ "alias",
+ "aliases",
+ )
+ xrayCmd = sets.New(
+ "x",
+ "xr",
+ "xray",
+ )
)
diff --git a/internal/view/command.go b/internal/view/command.go
index be074f3e..4d44c842 100644
--- a/internal/view/command.go
+++ b/internal/view/command.go
@@ -17,6 +17,12 @@ import (
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd"
+ "k8s.io/apimachinery/pkg/util/sets"
+)
+
+const (
+ podCmd = "pod"
+ ctxCmd = "ctx"
)
var (
@@ -39,8 +45,8 @@ func NewCommand(app *App) *Command {
}
// AliasesFor gather all known aliases for a given resource.
-func (c *Command) AliasesFor(s string) []string {
- return c.alias.AliasesFor(s)
+func (c *Command) AliasesFor(gvr *client.GVR) sets.Set[string] {
+ return c.alias.AliasesFor(gvr)
}
// Init initializes the command.
@@ -56,11 +62,11 @@ func (c *Command) Init(path string) error {
}
// Reset resets Command and reload aliases.
-func (c *Command) Reset(path string, clear bool) error {
+func (c *Command) Reset(path string, nuke bool) error {
c.mx.Lock()
defer c.mx.Unlock()
- if clear {
+ if nuke {
c.alias.Clear()
}
if _, err := c.alias.Ensure(path); err != nil {
@@ -70,18 +76,17 @@ func (c *Command) Reset(path string, clear bool) error {
return nil
}
-func allowedXRay(gvr client.GVR) bool {
- gg := map[string]struct{}{
- "v1/pods": {},
- "v1/services": {},
- "apps/v1/deployments": {},
- "apps/v1/daemonsets": {},
- "apps/v1/statefulsets": {},
- "apps/v1/replicasets": {},
- }
- _, ok := gg[gvr.String()]
+var allowedCmds = sets.New[*client.GVR](
+ client.PodGVR,
+ client.SvcGVR,
+ client.DpGVR,
+ client.DsGVR,
+ client.StsGVR,
+ client.RsGVR,
+)
- return ok
+func allowedXRay(gvr *client.GVR) bool {
+ return allowedCmds.Has(gvr)
}
func (c *Command) contextCmd(p *cmd.Interpreter, pushCmd bool) error {
@@ -102,7 +107,7 @@ func (c *Command) contextCmd(p *cmd.Interpreter, pushCmd bool) error {
return c.exec(p, gvr, c.componentFor(gvr, ct, v), true, pushCmd)
}
-func (c *Command) namespaceCmd(p *cmd.Interpreter) bool {
+func (*Command) namespaceCmd(p *cmd.Interpreter) bool {
ns, ok := p.NSArg()
if !ok {
return false
@@ -118,11 +123,10 @@ func (c *Command) namespaceCmd(p *cmd.Interpreter) bool {
func (c *Command) aliasCmd(p *cmd.Interpreter, pushCmd bool) error {
filter, _ := p.FilterArg()
- gvr := client.NewGVR("aliases")
- v := NewAlias(gvr)
+ v := NewAlias(client.AliGVR)
v.SetFilter(filter)
- return c.exec(p, gvr, v, false, pushCmd)
+ return c.exec(p, client.AliGVR, v, false, pushCmd)
}
func (c *Command) xrayCmd(p *cmd.Interpreter, pushCmd bool) error {
@@ -152,7 +156,7 @@ func (c *Command) xrayCmd(p *cmd.Interpreter, pushCmd bool) error {
}
// Run execs the command by showing associated display.
-func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool, pushCmd bool) error {
+func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack, pushCmd bool) error {
if c.specialCmd(p, pushCmd) {
return nil
}
@@ -169,7 +173,7 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool, pushCmd b
slog.Debug("Successfully saved config", slogs.Context, context)
}
}
- res, err := dao.AccessorFor(c.app.factory, client.NewGVR("contexts"))
+ res, err := dao.AccessorFor(c.app.factory, client.CtGVR)
if err != nil {
return err
}
@@ -215,9 +219,9 @@ func (c *Command) defaultCmd(isRoot bool) error {
return c.run(cmd.NewInterpreter("context"), "", true, true)
}
- defCmd := "pod"
+ defCmd := podCmd
if isRoot {
- defCmd = "ctx"
+ defCmd = ctxCmd
}
p := cmd.NewInterpreter(c.app.Config.ActiveView())
if p.IsBlank() {
@@ -281,22 +285,21 @@ func (c *Command) specialCmd(p *cmd.Interpreter, pushCmd bool) bool {
return true
}
-func (c *Command) viewMetaFor(p *cmd.Interpreter) (client.GVR, *MetaViewer, error) {
- agvr, exp, ok := c.alias.AsGVR(p.Cmd())
+func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, error) {
+ gvr, exp, ok := c.alias.AsGVR(p.Cmd())
if !ok {
return client.NoGVR, nil, fmt.Errorf("`%s` command not found", p.Cmd())
}
- gvr := agvr
if exp != "" {
ff := strings.Fields(exp)
- ff[0] = agvr.String()
+ ff[0] = gvr.String()
ap := cmd.NewInterpreter(strings.Join(ff, " "))
gvr = client.NewGVR(ap.Cmd())
p.Amend(ap)
}
v := MetaViewer{
- viewerFn: func(gvr client.GVR) ResourceViewer {
+ viewerFn: func(gvr *client.GVR) ResourceViewer {
return NewScaleExtender(NewOwnerExtender(NewBrowser(gvr)))
},
}
@@ -307,7 +310,7 @@ func (c *Command) viewMetaFor(p *cmd.Interpreter) (client.GVR, *MetaViewer, erro
return gvr, &v, nil
}
-func (c *Command) componentFor(gvr client.GVR, fqn string, v *MetaViewer) ResourceViewer {
+func (*Command) componentFor(gvr *client.GVR, fqn string, v *MetaViewer) ResourceViewer {
var view ResourceViewer
if v.viewerFn != nil {
view = v.viewerFn(gvr)
@@ -323,7 +326,7 @@ func (c *Command) componentFor(gvr client.GVR, fqn string, v *MetaViewer) Resour
return view
}
-func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component, clearStack bool, pushCmd bool) (err error) {
+func (c *Command) exec(p *cmd.Interpreter, gvr *client.GVR, comp model.Component, clearStack, pushCmd bool) (err error) {
defer func() {
if e := recover(); e != nil {
slog.Error("Failure detected during command exec", slogs.Error, e)
@@ -331,13 +334,13 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component,
slog.Debug("Dumping history buffer", slogs.CmdHist, c.app.cmdHistory.List())
slog.Error("Dumping stack", slogs.Stack, string(debug.Stack()))
- p := cmd.NewInterpreter("pod")
+ ci := cmd.NewInterpreter(podCmd)
cmds := c.app.cmdHistory.List()
currentCommand := cmds[c.app.cmdHistory.CurrentIndex()]
- if currentCommand != "pod" {
- p = p.Reset(currentCommand)
+ if currentCommand != podCmd {
+ ci = ci.Reset(currentCommand)
}
- err = c.run(p, "", true, true)
+ err = c.run(ci, "", true, true)
}
}()
diff --git a/internal/view/container.go b/internal/view/container.go
index f6b7ad0e..26135f8e 100644
--- a/internal/view/container.go
+++ b/internal/view/container.go
@@ -27,7 +27,7 @@ type Container struct {
}
// NewContainer returns a new container view.
-func NewContainer(gvr client.GVR) ResourceViewer {
+func NewContainer(gvr *client.GVR) ResourceViewer {
c := Container{}
c.ResourceViewer = NewLogsExtender(NewBrowser(gvr), c.logOptions)
c.SetEnvFn(c.k9sEnv)
@@ -59,7 +59,7 @@ func (c *Container) decorateRows(data *model1.TableData) {
}
// Name returns the component name.
-func (c *Container) Name() string { return containerTitle }
+func (*Container) Name() string { return containerTitle }
func (c *Container) bindDangerousKeys(aa *ui.KeyActions) {
aa.Bulk(ui.KeyMap{
@@ -115,7 +115,7 @@ func (c *Container) logOptions(prev bool) (*dao.LogOptions, error) {
opts := dao.LogOptions{
Path: c.GetTable().Path,
Container: path,
- Lines: int64(cfg.TailCount),
+ Lines: cfg.TailCount,
SinceSeconds: cfg.SinceSeconds,
SingleContainer: true,
ShowTimestamp: cfg.ShowTime,
@@ -125,7 +125,7 @@ func (c *Container) logOptions(prev bool) (*dao.LogOptions, error) {
return &opts, nil
}
-func (c *Container) viewLogs(app *App, model ui.Tabular, gvr client.GVR, path string) {
+func (c *Container) viewLogs(*App, ui.Tabular, *client.GVR, string) {
c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false)
}
@@ -141,7 +141,7 @@ func (c *Container) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
c.App().Flash().Errf("no port-forward defined")
return nil
}
- pf := NewPortForward(client.NewGVR("portforwards"))
+ pf := NewPortForward(client.PfGVR)
pf.SetContextFn(c.portForwardContext)
if err := c.App().inject(pf, false); err != nil {
c.App().Flash().Err(err)
diff --git a/internal/view/container_test.go b/internal/view/container_test.go
index 84787f6d..39c3abab 100644
--- a/internal/view/container_test.go
+++ b/internal/view/container_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestContainerNew(t *testing.T) {
- c := view.NewContainer(client.NewGVR("containers"))
+ c := view.NewContainer(client.CoGVR)
- assert.Nil(t, c.Init(makeCtx()))
+ require.NoError(t, c.Init(makeCtx()))
assert.Equal(t, "Containers", c.Name())
- assert.Equal(t, 19, len(c.Hints()))
+ assert.Len(t, c.Hints(), 19)
}
diff --git a/internal/view/context.go b/internal/view/context.go
index 6b8f9864..c37f88d6 100644
--- a/internal/view/context.go
+++ b/internal/view/context.go
@@ -28,7 +28,7 @@ type Context struct {
}
// NewContext returns a new viewer.
-func NewContext(gvr client.GVR) ResourceViewer {
+func NewContext(gvr *client.GVR) ResourceViewer {
c := Context{
ResourceViewer: NewBrowser(gvr),
}
@@ -96,14 +96,14 @@ func (c *Context) showRenameModal(name string, ok func(form *tview.Form, context
app.Content.AddPage(renamePage, m, false, false)
app.Content.ShowPage(renamePage)
- for i := 0; i < f.GetButtonCount(); i++ {
+ for i := range f.GetButtonCount() {
f.GetButton(i).
SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
}
}
-func (c *Context) useCtx(app *App, model ui.Tabular, gvr client.GVR, path string) {
+func (c *Context) useCtx(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
slog.Debug("Using context",
slogs.GVR, gvr,
slogs.FQN, path,
@@ -120,7 +120,7 @@ func useContext(app *App, name string) error {
if app.Content.Top() != nil {
app.Content.Top().Stop()
}
- res, err := dao.AccessorFor(app.factory, client.NewGVR("contexts"))
+ res, err := dao.AccessorFor(app.factory, client.CtGVR)
if err != nil {
return err
}
diff --git a/internal/view/context_test.go b/internal/view/context_test.go
index a265459a..42fe5e60 100644
--- a/internal/view/context_test.go
+++ b/internal/view/context_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestContext(t *testing.T) {
- ctx := view.NewContext(client.NewGVR("contexts"))
+ ctx := view.NewContext(client.CtGVR)
- assert.Nil(t, ctx.Init(makeCtx()))
+ require.NoError(t, ctx.Init(makeCtx()))
assert.Equal(t, "Contexts", ctx.Name())
- assert.Equal(t, 5, len(ctx.Hints()))
+ assert.Len(t, ctx.Hints(), 5)
}
diff --git a/internal/view/cow.go b/internal/view/cow.go
index 75b71b11..b65a7a7a 100644
--- a/internal/view/cow.go
+++ b/internal/view/cow.go
@@ -63,7 +63,7 @@ func (*Cow) InCmdMode() bool {
func (c *Cow) talk() {
says := c.says
- if len(says) == 0 {
+ if says == "" {
says = "Nothing to report here. Please move along..."
}
x, _, w, _ := c.GetRect()
@@ -73,9 +73,11 @@ func (c *Cow) talk() {
func cowTalk(says string, w int) string {
msg := fmt.Sprintf("[red::]< [::b]Ruroh? %s[::-] >", says)
buff := make([]string, 0, len(cow)+3)
- buff = append(buff, "[red::] "+strings.Repeat("โ", len(says)+8))
- buff = append(buff, strings.TrimSuffix(msg, "\n"))
- buff = append(buff, " "+strings.Repeat("โ", len(says)+8))
+ buff = append(buff,
+ "[red::] "+strings.Repeat("โ", len(says)+8),
+ strings.TrimSuffix(msg, "\n"),
+ " "+strings.Repeat("โ", len(says)+8),
+ )
rCount := w/2 - 8
if rCount < 0 {
rCount = w / 2
@@ -101,9 +103,9 @@ func (c *Cow) keyboard(evt *tcell.EventKey) *tcell.EventKey {
// StylesChanged notifies the skin changes.
func (c *Cow) StylesChanged(s *config.Styles) {
- c.SetBackgroundColor(c.app.Styles.BgColor())
- c.SetTextColor(c.app.Styles.FgColor())
- c.SetBorderFocusColor(c.app.Styles.Frame().Border.FocusColor.Color())
+ c.SetBackgroundColor(s.BgColor())
+ c.SetTextColor(s.FgColor())
+ c.SetBorderFocusColor(s.Frame().Border.FocusColor.Color())
}
func (c *Cow) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
@@ -116,10 +118,10 @@ func (c *Cow) Actions() *ui.KeyActions {
}
// Name returns the component name.
-func (c *Cow) Name() string { return "cow" }
+func (*Cow) Name() string { return "cow" }
// Start starts the view updater.
-func (c *Cow) Start() {}
+func (*Cow) Start() {}
// Stop terminates the updater.
func (c *Cow) Stop() {
@@ -132,7 +134,7 @@ func (c *Cow) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (c *Cow) ExtraHints() map[string]string {
+func (*Cow) ExtraHints() map[string]string {
return nil
}
diff --git a/internal/view/crd.go b/internal/view/crd.go
index b3a113aa..1f6c7b37 100644
--- a/internal/view/crd.go
+++ b/internal/view/crd.go
@@ -14,7 +14,7 @@ type CRD struct {
}
// NewCRD returns a new viewer.
-func NewCRD(gvr client.GVR) ResourceViewer {
+func NewCRD(gvr *client.GVR) ResourceViewer {
s := CRD{
ResourceViewer: NewOwnerExtender(NewBrowser(gvr)),
}
@@ -32,7 +32,7 @@ func (s *CRD) bindKeys(aa *ui.KeyActions) {
})
}
-func (s *CRD) showCRD(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (*CRD) showCRD(app *App, _ ui.Tabular, _ *client.GVR, path string) {
_, crd := client.Namespaced(path)
app.gotoResource(crd, "", false, true)
}
diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go
index 0862e041..9f80f09b 100644
--- a/internal/view/cronjob.go
+++ b/internal/view/cronjob.go
@@ -35,7 +35,7 @@ type CronJob struct {
}
// NewCronJob returns a new viewer.
-func NewCronJob(gvr client.GVR) ResourceViewer {
+func NewCronJob(gvr *client.GVR) ResourceViewer {
c := CronJob{ResourceViewer: NewVulnerabilityExtender(
NewOwnerExtender(NewBrowser(gvr)),
)}
@@ -45,9 +45,9 @@ func NewCronJob(gvr client.GVR) ResourceViewer {
return &c
}
-func (c *CronJob) showJobs(app *App, _ ui.Tabular, gvr client.GVR, fqn string) {
+func (*CronJob) showJobs(app *App, _ ui.Tabular, gvr *client.GVR, fqn string) {
slog.Debug("Showing Jobs", slogs.GVR, gvr, slogs.FQN, fqn)
- o, err := app.factory.Get(gvr.String(), fqn, true, labels.Everything())
+ o, err := app.factory.Get(gvr, fqn, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return
@@ -64,7 +64,7 @@ func (c *CronJob) showJobs(app *App, _ ui.Tabular, gvr client.GVR, fqn string) {
if err := app.Config.SetActiveNamespace(ns); err != nil {
slog.Error("Unable to set active namespace during show pods", slogs.Error, err)
}
- v := NewJob(client.NewGVR("batch/v1/jobs"))
+ v := NewJob(client.JobGVR)
v.SetContextFn(jobCtx(fqn, string(cj.UID)))
if err := app.inject(v, false); err != nil {
app.Flash().Err(err)
@@ -95,7 +95,8 @@ func (c *CronJob) triggerCmd(evt *tcell.EventKey) *tcell.EventKey {
if len(fqns) > 1 {
msg = fmt.Sprintf("Trigger %d CronJobs?", len(fqns))
}
- dialog.ShowConfirm(c.App().Styles.Dialog(), c.App().Content.Pages, "Confirm Job Trigger", msg, func() {
+ d := c.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, c.App().Content.Pages, "Confirm Job Trigger", msg, func() {
res, err := dao.AccessorFor(c.App().factory, c.GVR())
if err != nil {
c.App().Flash().Err(fmt.Errorf("no accessor for %q", c.GVR()))
@@ -149,7 +150,8 @@ func (c *CronJob) showSuspendDialog(cell *tview.TableCell, sel string) {
title = "Resume"
}
- dialog.ShowConfirm(c.App().Styles.Dialog(), c.App().Content.Pages, title, sel, func() {
+ d := c.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, c.App().Content.Pages, title, sel, func() {
ctx, cancel := context.WithTimeout(context.Background(), c.App().Conn().Config().CallTimeout())
defer cancel()
diff --git a/internal/view/details.go b/internal/view/details.go
index 18db2883..f785db74 100644
--- a/internal/view/details.go
+++ b/internal/view/details.go
@@ -59,9 +59,9 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) *
return &d
}
-func (d *Details) SetCommand(*cmd.Interpreter) {}
-func (d *Details) SetFilter(string) {}
-func (d *Details) SetLabelFilter(map[string]string) {}
+func (*Details) SetCommand(*cmd.Interpreter) {}
+func (*Details) SetFilter(string) {}
+func (*Details) SetLabelFilter(map[string]string) {}
// Init initializes the viewer.
func (d *Details) Init(_ context.Context) error {
@@ -120,7 +120,7 @@ func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
}
// BufferChanged indicates the buffer was changed.
-func (d *Details) BufferChanged(_, _ string) {}
+func (*Details) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted.
func (d *Details) BufferCompleted(text, _ string) {
@@ -161,9 +161,9 @@ func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey {
// StylesChanged notifies the skin changed.
func (d *Details) StylesChanged(s *config.Styles) {
- d.SetBackgroundColor(d.app.Styles.BgColor())
- d.text.SetTextColor(d.app.Styles.FgColor())
- d.SetBorderFocusColor(d.app.Styles.Frame().Border.FocusColor.Color())
+ d.SetBackgroundColor(s.BgColor())
+ d.text.SetTextColor(s.FgColor())
+ d.SetBorderFocusColor(s.Frame().Border.FocusColor.Color())
d.TextChanged(d.model.Peek())
}
@@ -192,7 +192,7 @@ func (d *Details) Actions() *ui.KeyActions {
func (d *Details) Name() string { return d.title }
// Start starts the view updater.
-func (d *Details) Start() {}
+func (*Details) Start() {}
// Stop terminates the updater.
func (d *Details) Stop() {
@@ -205,7 +205,7 @@ func (d *Details) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (d *Details) ExtraHints() map[string]string {
+func (*Details) ExtraHints() map[string]string {
return nil
}
@@ -262,7 +262,7 @@ func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (d *Details) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (d *Details) filterCmd(*tcell.EventKey) *tcell.EventKey {
d.model.Filter(d.cmdBuff.GetText())
d.cmdBuff.SetActive(false)
d.updateTitle()
@@ -279,7 +279,7 @@ func (d *Details) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (d *Details) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (d *Details) eraseCmd(*tcell.EventKey) *tcell.EventKey {
if !d.cmdBuff.IsActive() {
return nil
}
@@ -304,7 +304,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (d *Details) saveCmd(*tcell.EventKey) *tcell.EventKey {
if path, err := saveYAML(d.app.Config.K9s.ContextScreenDumpDir(), d.title, d.text.GetText(true)); err != nil {
d.app.Flash().Err(err)
} else {
@@ -320,9 +320,12 @@ func (d *Details) updateTitle() {
}
fmat := fmt.Sprintf(detailsTitleFmt, d.title, d.subject)
- buff := d.cmdBuff.GetText()
+ var (
+ buff = d.cmdBuff.GetText()
+ styles = d.app.Styles.Frame()
+ )
if buff == "" {
- d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
+ d.SetTitle(ui.SkinTitle(fmat, &styles))
return
}
@@ -330,5 +333,5 @@ func (d *Details) updateTitle() {
buff += fmt.Sprintf("[%d:%d]", d.currentRegion+1, d.maxRegions)
}
fmat += fmt.Sprintf(ui.SearchFmt, buff)
- d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
+ d.SetTitle(ui.SkinTitle(fmat, &styles))
}
diff --git a/internal/view/dir.go b/internal/view/dir.go
index 825d79ec..58de96ec 100644
--- a/internal/view/dir.go
+++ b/internal/view/dir.go
@@ -36,7 +36,7 @@ type Dir struct {
// NewDir returns a new instance.
func NewDir(path string) ResourceViewer {
d := Dir{
- ResourceViewer: NewBrowser(client.NewGVR("dir")),
+ ResourceViewer: NewBrowser(client.DirGVR),
path: path,
}
d.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
@@ -132,7 +132,7 @@ func (d *Dir) editCmd(evt *tcell.EventKey) *tcell.EventKey {
d.Stop()
defer d.Start()
- if !edit(d.App(), shellOpts{clear: true, args: []string{sel}}) {
+ if !edit(d.App(), &shellOpts{clear: true, args: []string{sel}}) {
d.App().Flash().Errf("Failed to launch editor")
}
@@ -219,7 +219,7 @@ func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey {
args = append(args, "apply")
args = append(args, opts...)
args = append(args, sel)
- res, err := runKu(d.App(), shellOpts{clear: false, args: args})
+ res, err := runKu(d.App(), &shellOpts{clear: false, args: args})
if err != nil {
res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res)
} else {
@@ -254,12 +254,13 @@ func (d *Dir) delCmd(evt *tcell.EventKey) *tcell.EventKey {
d.Stop()
defer d.Start()
msg := fmt.Sprintf("Delete resource(s) in %s %s", msgResource, sel)
- dialog.ShowConfirm(d.App().Styles.Dialog(), d.App().Content.Pages, "Confirm Delete", msg, func() {
+ dlg := d.App().Styles.Dialog()
+ dialog.ShowConfirm(&dlg, d.App().Content.Pages, "Confirm Delete", msg, func() {
args := make([]string, 0, 10)
args = append(args, "delete")
args = append(args, opts...)
args = append(args, sel)
- res, err := runKu(d.App(), shellOpts{clear: false, args: args})
+ res, err := runKu(d.App(), &shellOpts{clear: false, args: args})
if err != nil {
res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res)
} else {
diff --git a/internal/view/dir_test.go b/internal/view/dir_test.go
index 7757eb8a..f00dc355 100644
--- a/internal/view/dir_test.go
+++ b/internal/view/dir_test.go
@@ -8,12 +8,13 @@ import (
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDir(t *testing.T) {
v := view.NewDir("/fred")
- assert.Nil(t, v.Init(makeCtx()))
+ require.NoError(t, v.Init(makeCtx()))
assert.Equal(t, "Directory", v.Name())
- assert.Equal(t, 7, len(v.Hints()))
+ assert.Len(t, v.Hints(), 7)
}
diff --git a/internal/view/dp.go b/internal/view/dp.go
index 3c6760c8..02f80076 100644
--- a/internal/view/dp.go
+++ b/internal/view/dp.go
@@ -21,7 +21,7 @@ type Deploy struct {
}
// NewDeploy returns a new deployment view.
-func NewDeploy(gvr client.GVR) ResourceViewer {
+func NewDeploy(gvr *client.GVR) ResourceViewer {
var d Deploy
d.ResourceViewer = NewPortForwardExtender(
NewVulnerabilityExtender(
@@ -60,10 +60,10 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err
}
- return podLogOptions(d.App(), path, prev, dp.ObjectMeta, dp.Spec.Template.Spec), nil
+ return podLogOptions(d.App(), path, prev, &dp.ObjectMeta, &dp.Spec.Template.Spec), nil
}
-func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string) {
+func (d *Deploy) showPods(app *App, _ ui.Tabular, _ *client.GVR, fqn string) {
dp, err := d.getInstance(fqn)
if err != nil {
app.Flash().Err(err)
diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go
index a3e8a51f..270646d1 100644
--- a/internal/view/dp_test.go
+++ b/internal/view/dp_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDeploy(t *testing.T) {
- v := view.NewDeploy(client.NewGVR("apps/v1/deployments"))
+ v := view.NewDeploy(client.DpGVR)
- assert.Nil(t, v.Init(makeCtx()))
+ require.NoError(t, v.Init(makeCtx()))
assert.Equal(t, "Deployments", v.Name())
- assert.Equal(t, 16, len(v.Hints()))
+ assert.Len(t, v.Hints(), 16)
}
diff --git a/internal/view/drain_dialog.go b/internal/view/drain_dialog.go
index 544c9eb9..103b575a 100644
--- a/internal/view/drain_dialog.go
+++ b/internal/view/drain_dialog.go
@@ -80,7 +80,7 @@ func ShowDrain(view ResourceViewer, sels []string, opts dao.DrainOptions, okFn D
}
path += "?"
modal.SetText(path)
- modal.SetDoneFunc(func(_ int, b string) {
+ modal.SetDoneFunc(func(int, string) {
DismissDrain(view, pages)
})
diff --git a/internal/view/ds.go b/internal/view/ds.go
index 08aad213..bbe93484 100644
--- a/internal/view/ds.go
+++ b/internal/view/ds.go
@@ -18,7 +18,7 @@ type DaemonSet struct {
}
// NewDaemonSet returns a new viewer.
-func NewDaemonSet(gvr client.GVR) ResourceViewer {
+func NewDaemonSet(gvr *client.GVR) ResourceViewer {
var d DaemonSet
d.ResourceViewer = NewPortForwardExtender(
NewVulnerabilityExtender(
@@ -47,7 +47,7 @@ func (d *DaemonSet) bindKeys(aa *ui.KeyActions) {
})
}
-func (d *DaemonSet) showPods(app *App, model ui.Tabular, _ client.GVR, path string) {
+func (d *DaemonSet) showPods(app *App, _ ui.Tabular, _ *client.GVR, path string) {
var res dao.DaemonSet
res.Init(app.factory, d.GVR())
@@ -70,12 +70,12 @@ func (d *DaemonSet) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err
}
- return podLogOptions(d.App(), path, prev, ds.ObjectMeta, ds.Spec.Template.Spec), nil
+ return podLogOptions(d.App(), path, prev, &ds.ObjectMeta, &ds.Spec.Template.Spec), nil
}
func (d *DaemonSet) getInstance(fqn string) (*appsv1.DaemonSet, error) {
var ds dao.DaemonSet
- ds.Init(d.App().factory, client.NewGVR("apps/v1/daemonsets"))
+ ds.Init(d.App().factory, client.DsGVR)
return ds.GetInstance(fqn)
}
diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go
index 94545218..62e8da20 100644
--- a/internal/view/ds_test.go
+++ b/internal/view/ds_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestDaemonSet(t *testing.T) {
- v := view.NewDaemonSet(client.NewGVR("apps/v1/daemonsets"))
+ v := view.NewDaemonSet(client.DsGVR)
- assert.Nil(t, v.Init(makeCtx()))
+ require.NoError(t, v.Init(makeCtx()))
assert.Equal(t, "DaemonSets", v.Name())
- assert.Equal(t, 17, len(v.Hints()))
+ assert.Len(t, v.Hints(), 17)
}
diff --git a/internal/view/event.go b/internal/view/event.go
index b75c975a..8e4cf897 100644
--- a/internal/view/event.go
+++ b/internal/view/event.go
@@ -15,7 +15,7 @@ type Event struct {
}
// NewEvent returns a new alias view.
-func NewEvent(gvr client.GVR) ResourceViewer {
+func NewEvent(gvr *client.GVR) ResourceViewer {
e := Event{
ResourceViewer: NewBrowser(gvr),
}
diff --git a/internal/view/exec.go b/internal/view/exec.go
index 5a9a6abb..35b041b2 100644
--- a/internal/view/exec.go
+++ b/internal/view/exec.go
@@ -53,7 +53,7 @@ func (s shellOpts) String() string {
return fmt.Sprintf("%s %s", s.binary, strings.Join(s.args, " "))
}
-func runK(a *App, opts shellOpts) error {
+func runK(a *App, opts *shellOpts) error {
bin, err := exec.LookPath("kubectl")
if errors.Is(err, exec.ErrDot) {
return fmt.Errorf("kubectl command must not be in the current working directory: %w", err)
@@ -95,7 +95,7 @@ func runK(a *App, opts shellOpts) error {
return errs
}
-func run(a *App, opts shellOpts) (bool, chan error, chan string) {
+func run(a *App, opts *shellOpts) (ok bool, errC chan error, outC chan string) {
errChan := make(chan error, 1)
statusChan := make(chan string, 1)
@@ -120,7 +120,7 @@ func run(a *App, opts shellOpts) (bool, chan error, chan string) {
}), errChan, statusChan
}
-func edit(a *App, opts shellOpts) bool {
+func edit(a *App, opts *shellOpts) bool {
var (
bin string
err error
@@ -168,7 +168,7 @@ func edit(a *App, opts shellOpts) bool {
return status
}
-func execute(opts shellOpts, statusChan chan<- string) error {
+func execute(opts *shellOpts, statusChan chan<- string) error {
if opts.clear {
clearScreen()
}
@@ -235,7 +235,7 @@ func execute(opts shellOpts, statusChan chan<- string) error {
return nil
}
-func runKu(a *App, opts shellOpts) (string, error) {
+func runKu(a *App, opts *shellOpts) (string, error) {
bin, err := exec.LookPath("kubectl")
if errors.Is(err, exec.ErrDot) {
slog.Error("Kubectl exec can not reside in current working directory", slogs.Error, err)
@@ -264,7 +264,7 @@ func runKu(a *App, opts shellOpts) (string, error) {
return oneShoot(opts)
}
-func oneShoot(opts shellOpts) (string, error) {
+func oneShoot(opts *shellOpts) (string, error) {
if opts.clear {
clearScreen()
}
@@ -301,7 +301,8 @@ func launchNodeShell(v model.Igniter, a *App, node string) {
}
msg := fmt.Sprintf("Launching node shell on %s...", node)
- dialog.ShowPrompt(a.Styles.Dialog(), a.Content.Pages, "Launching", msg, func(ctx context.Context) {
+ d := a.Styles.Dialog()
+ dialog.ShowPrompt(&d, a.Content.Pages, "Launching", msg, func(ctx context.Context) {
err := launchShellPod(ctx, a, node)
if err != nil {
if !errors.Is(err, context.Canceled) {
@@ -357,7 +358,11 @@ func sshIn(a *App, fqn, co string) error {
slog.Debug("Running command with args", slogs.Args, args)
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
- err = runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, fqn, co), args: args})
+ err = runK(a, &shellOpts{
+ clear: true,
+ banner: c.Sprintf(bannerFmt, fqn, co),
+ args: args},
+ )
if err != nil {
return fmt.Errorf("shell exec failed: %w", err)
}
@@ -407,8 +412,8 @@ func launchShellPod(ctx context.Context, a *App, node string) error {
return err
}
- for i := 0; i < k9sShellRetryCount; i++ {
- o, err := a.factory.Get("v1/pods", client.FQN(spo.Namespace, k9sShellPodName()), true, labels.Everything())
+ for i := range k9sShellRetryCount {
+ o, err := a.factory.Get(client.PodGVR, client.FQN(spo.Namespace, k9sShellPodName()), true, labels.Everything())
if err != nil {
select {
case <-ctx.Done():
@@ -444,7 +449,7 @@ func k9sShellPodName() string {
return fmt.Sprintf("%s-%d", k9sShell, os.Getpid())
}
-func k9sShellPod(node string, cfg config.ShellPod) *v1.Pod {
+func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod {
var grace int64
var priv = true
@@ -516,7 +521,7 @@ func asResource(r config.Limits) v1.ResourceRequirements {
}
}
-func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *bytes.Buffer, cmds ...*exec.Cmd) error {
+func pipe(_ context.Context, opts *shellOpts, statusChan chan<- string, w, e *bytes.Buffer, cmds ...*exec.Cmd) error {
if len(cmds) == 0 {
return nil
}
@@ -556,7 +561,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt
}
last := len(cmds) - 1
- for i := 0; i < len(cmds); i++ {
+ for i := range cmds {
cmds[i].Stderr = os.Stderr
if i+1 < len(cmds) {
r, w := io.Pipe()
diff --git a/internal/view/group.go b/internal/view/group.go
index 0cfe42dd..eb5026cc 100644
--- a/internal/view/group.go
+++ b/internal/view/group.go
@@ -18,7 +18,7 @@ type Group struct {
}
// NewGroup returns a new subject viewer.
-func NewGroup(gvr client.GVR) ResourceViewer {
+func NewGroup(gvr *client.GVR) ResourceViewer {
g := Group{ResourceViewer: NewBrowser(gvr)}
g.AddBindKeysFn(g.bindKeys)
g.SetContextFn(g.subjectCtx)
@@ -34,7 +34,7 @@ func (g *Group) bindKeys(aa *ui.KeyActions) {
})
}
-func (g *Group) subjectCtx(ctx context.Context) context.Context {
+func (*Group) subjectCtx(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeySubjectKind, "Group")
}
diff --git a/internal/view/helm_chart.go b/internal/view/helm_chart.go
index c3d595ba..f2537b01 100644
--- a/internal/view/helm_chart.go
+++ b/internal/view/helm_chart.go
@@ -18,7 +18,7 @@ type HelmChart struct {
}
// NewHelmChart returns a new helm-chart view.
-func NewHelmChart(gvr client.GVR) ResourceViewer {
+func NewHelmChart(gvr *client.GVR) ResourceViewer {
c := HelmChart{
ResourceViewer: NewValueExtender(NewBrowser(gvr)),
}
@@ -33,7 +33,7 @@ func NewHelmChart(gvr client.GVR) ResourceViewer {
return &c
}
-func (c *HelmChart) chartContext(ctx context.Context) context.Context {
+func (*HelmChart) chartContext(ctx context.Context) context.Context {
return ctx
}
@@ -45,8 +45,8 @@ func (c *HelmChart) bindKeys(aa *ui.KeyActions) {
})
}
-func (c *HelmChart) viewReleases(app *App, model ui.Tabular, _ client.GVR, path string) {
- v := NewHistory(client.NewGVR("helm-history"))
+func (c *HelmChart) viewReleases(app *App, _ ui.Tabular, _ *client.GVR, _ string) {
+ v := NewHistory(client.HmhGVR)
v.SetContextFn(c.helmContext)
if err := app.inject(v, false); err != nil {
app.Flash().Err(err)
diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go
index 4bbd7c31..41b669a8 100644
--- a/internal/view/helm_history.go
+++ b/internal/view/helm_history.go
@@ -25,7 +25,7 @@ type History struct {
}
// NewHistory returns a new helm-history view.
-func NewHistory(gvr client.GVR) ResourceViewer {
+func NewHistory(gvr *client.GVR) ResourceViewer {
h := History{
ResourceViewer: NewValueExtender(NewBrowser(gvr)),
}
@@ -49,7 +49,7 @@ func (h *History) Init(ctx context.Context) error {
return nil
}
-func (h *History) HistoryContext(ctx context.Context) context.Context {
+func (*History) HistoryContext(ctx context.Context) context.Context {
return ctx
}
@@ -66,7 +66,7 @@ func (h *History) bindKeys(aa *ui.KeyActions) {
})
}
-func (h *History) getValsCmd(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (h *History) getValsCmd(app *App, _ ui.Tabular, _ *client.GVR, path string) {
ns, n := client.Namespaced(path)
tt := strings.Split(n, ":")
if len(tt) < 2 {
diff --git a/internal/view/help.go b/internal/view/help.go
index dd3609f1..e610c074 100644
--- a/internal/view/help.go
+++ b/internal/view/help.go
@@ -39,7 +39,7 @@ type Help struct {
// NewHelp returns a new help viewer.
func NewHelp(app *App) *Help {
return &Help{
- Table: NewTable(client.NewGVR("help")),
+ Table: NewTable(client.HlpGVR),
hints: app.Content.Top().Hints,
}
}
@@ -154,7 +154,7 @@ func (h *Help) addExtras(extras map[string]string, col, size int) {
}
}
-func (h *Help) showNav() model.MenuHints {
+func (*Help) showNav() model.MenuHints {
return model.MenuHints{
{
Mnemonic: "g",
@@ -224,7 +224,7 @@ func (h *Help) showHotKeys() (model.MenuHints, error) {
return mm, nil
}
-func (h *Help) showGeneral() model.MenuHints {
+func (*Help) showGeneral() model.MenuHints {
return model.MenuHints{
{
Mnemonic: "?",
@@ -341,8 +341,8 @@ func (h *Help) updateStyle() {
info = style.Foreground(h.app.Styles.K9s.Help.FgColor.Color())
heading = style.Foreground(h.app.Styles.K9s.Help.SectionColor.Color())
)
- for col := 0; col < h.GetColumnCount(); col++ {
- for row := 0; row < h.GetRowCount(); row++ {
+ for col := range h.GetColumnCount() {
+ for row := range h.GetRowCount() {
c := h.GetCell(row, col)
if c == nil {
continue
@@ -384,7 +384,7 @@ func (h *Help) titleCell(title string) *tview.TableCell {
return c
}
-func padCellWithRef(s string, width int, ref interface{}) *tview.TableCell {
+func padCellWithRef(s string, width int, ref any) *tview.TableCell {
return padCell(s, width).SetReference(ref)
}
diff --git a/internal/view/help_test.go b/internal/view/help_test.go
index b6f19c78..1f850169 100644
--- a/internal/view/help_test.go
+++ b/internal/view/help_test.go
@@ -11,19 +11,20 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestHelp(t *testing.T) {
ctx := makeCtx()
app := ctx.Value(internal.KeyApp).(*view.App)
- po := view.NewPod(client.NewGVR("v1/pods"))
- assert.NoError(t, po.Init(ctx))
+ po := view.NewPod(client.PodGVR)
+ require.NoError(t, po.Init(ctx))
app.Content.Push(po)
v := view.NewHelp(app)
- assert.Nil(t, v.Init(ctx))
+ require.NoError(t, v.Init(ctx))
assert.Equal(t, 29, v.GetRowCount())
assert.Equal(t, 8, v.GetColumnCount())
assert.Equal(t, "", strings.TrimSpace(v.GetCell(1, 0).Text))
diff --git a/internal/view/helpers.go b/internal/view/helpers.go
index 11df3c02..2e39d07b 100644
--- a/internal/view/helpers.go
+++ b/internal/view/helpers.go
@@ -28,24 +28,17 @@ import (
"github.com/derailed/tview"
"github.com/sahilm/fuzzy"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/sets"
)
-func aliasesFor(m v1.APIResource, aa []string) map[string]struct{} {
- rr := make(map[string]struct{})
- rr[m.Name] = struct{}{}
- for _, a := range aa {
- rr[a] = struct{}{}
- }
- if m.ShortNames != nil {
- for _, a := range m.ShortNames {
- rr[a] = struct{}{}
- }
- }
+func aliases(m *v1.APIResource, aa sets.Set[string]) sets.Set[string] {
+ ss := sets.New(aa.UnsortedList()...)
+ ss.Insert(m.ShortNames...)
if m.SingularName != "" {
- rr[m.SingularName] = struct{}{}
+ ss.Insert(m.SingularName)
}
- return rr
+ return ss
}
func clipboardWrite(text string) error {
@@ -74,10 +67,10 @@ func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.E
}
}
-func parsePFAnn(s string) (string, string, bool) {
+func parsePFAnn(s string) (port, lport string, ok bool) {
tokens := strings.Split(s, ":")
if len(tokens) != 2 {
- return "", "", false
+ return
}
return tokens[0], tokens[1], true
@@ -134,7 +127,7 @@ func defaultEnv(c *client.Config, path string, header model1.Header, row *model1
return env
}
-func describeResource(app *App, m ui.Tabular, gvr client.GVR, path string) {
+func describeResource(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
v := NewLiveView(app, "Describe", model.NewDescribe(gvr, path))
if err := app.inject(v, false); err != nil {
app.Flash().Err(err)
@@ -151,7 +144,7 @@ func toLabelsStr(labels map[string]string) string {
}
func showPods(app *App, path, labelSel, fieldSel string) {
- v := NewPod(client.NewGVR("v1/pods"))
+ v := NewPod(client.PodGVR)
v.SetContextFn(podCtx(app, path, fieldSel))
v.SetLabelFilter(cmd.ToLabels(labelSel))
@@ -209,7 +202,7 @@ func containerID(path, co string) string {
}
// UrlFor computes fq url for a given benchmark configuration.
-func urlFor(cfg config.BenchConfig, port string) string {
+func urlFor(cfg *config.BenchConfig, port string) string {
host := "localhost"
if cfg.HTTP.Host != "" {
host = cfg.HTTP.Host
@@ -234,12 +227,12 @@ func decorateCpuMemHeaderRows(app *App, data *model1.TableData) {
for colIndex, header := range data.Header() {
var check string
if header.Name == "%CPU/L" {
- check = "cpu"
+ check = config.CPU
}
if header.Name == "%MEM/L" {
- check = "memory"
+ check = config.MEM
}
- if len(check) == 0 {
+ if check == "" {
continue
}
data.RowsRange(func(_ int, re model1.RowEvent) bool {
@@ -258,7 +251,7 @@ func decorateCpuMemHeaderRows(app *App, data *model1.TableData) {
return true
}
color := app.Config.K9s.Thresholds.SeverityColor(check, n)
- if len(color) > 0 {
+ if color != "" {
re.Row.Fields[colIndex] = "[" + color + "::b]" + re.Row.Fields[colIndex]
}
diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go
index 87500f92..9f941178 100644
--- a/internal/view/helpers_test.go
+++ b/internal/view/helpers_test.go
@@ -133,7 +133,7 @@ func TestK8sEnv(t *testing.T) {
c := client.NewConfig(&flags)
env := k8sEnv(c)
- assert.Equal(t, 5, len(env))
+ assert.Len(t, env, 5)
assert.Equal(t, cl, env["CLUSTER"])
assert.Equal(t, ctx, env["CONTEXT"])
assert.Equal(t, u, env["USER"])
@@ -160,7 +160,7 @@ func TestK9sEnv(t *testing.T) {
}
env := defaultEnv(c, "fred/blee", h, &r)
- assert.Equal(t, 10, len(env))
+ assert.Len(t, env, 10)
assert.Equal(t, cl, env["CLUSTER"])
assert.Equal(t, ctx, env["CONTEXT"])
assert.Equal(t, u, env["USER"])
@@ -241,7 +241,7 @@ func TestUrlFor(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, urlFor(u.cfg, u.port))
+ assert.Equal(t, u.e, urlFor(&u.cfg, u.port))
})
}
}
diff --git a/internal/view/image_extender.go b/internal/view/image_extender.go
index c3675acc..eeb87920 100644
--- a/internal/view/image_extender.go
+++ b/internal/view/image_extender.go
@@ -64,7 +64,7 @@ func (s *ImageExtender) bindKeys(aa *ui.KeyActions) {
aa.Add(ui.KeyI, ui.NewKeyAction("Set Image", s.setImageCmd, false))
}
-func (s *ImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (s *ImageExtender) setImageCmd(*tcell.EventKey) *tcell.EventKey {
path := s.GetTable().GetSelectedItem()
if path == "" {
return nil
@@ -102,10 +102,12 @@ func (s *ImageExtender) makeSetImageForm(fqn string) (*tview.Form, error) {
}
formContainerLines := make([]*imageFormSpec, 0, len(podSpec.InitContainers)+len(podSpec.Containers))
- for _, spec := range podSpec.InitContainers {
+ for i := range podSpec.InitContainers {
+ spec := podSpec.InitContainers[i]
formContainerLines = append(formContainerLines, &imageFormSpec{init: true, name: spec.Name, dockerImage: spec.Image})
}
- for _, spec := range podSpec.Containers {
+ for i := range podSpec.Containers {
+ spec := podSpec.Containers[i]
formContainerLines = append(formContainerLines, &imageFormSpec{name: spec.Name, dockerImage: spec.Image})
}
@@ -148,7 +150,7 @@ func (s *ImageExtender) makeSetImageForm(fqn string) (*tview.Form, error) {
})
}
- for i := 0; i < f.GetButtonCount(); i++ {
+ for i := range f.GetButtonCount() {
f.GetButton(i).
SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
diff --git a/internal/view/img_scan.go b/internal/view/img_scan.go
index 58b1002d..e548e590 100644
--- a/internal/view/img_scan.go
+++ b/internal/view/img_scan.go
@@ -28,7 +28,7 @@ type ImageScan struct {
}
// NewImageScan returns a new scans view.
-func NewImageScan(gvr client.GVR) ResourceViewer {
+func NewImageScan(gvr *client.GVR) ResourceViewer {
v := ImageScan{}
v.ResourceViewer = NewBrowser(gvr)
v.AddBindKeysFn(v.bindKeys)
@@ -39,20 +39,19 @@ func NewImageScan(gvr client.GVR) ResourceViewer {
}
// Name returns the component name.
-func (s *ImageScan) Name() string { return imgScanTitle }
+func (*ImageScan) Name() string { return imgScanTitle }
-func (c *ImageScan) bindKeys(aa *ui.KeyActions) {
+func (i *ImageScan) bindKeys(aa *ui.KeyActions) {
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlZ, tcell.KeyCtrlW)
-
aa.Bulk(ui.KeyMap{
- ui.KeyShiftL: ui.NewKeyAction("Sort Lib", c.GetTable().SortColCmd("LIBRARY", false), true),
- ui.KeyShiftS: ui.NewKeyAction("Sort Severity", c.GetTable().SortColCmd("SEVERITY", false), true),
- ui.KeyShiftF: ui.NewKeyAction("Sort Fixed-in", c.GetTable().SortColCmd("FIXED-IN", false), true),
- ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerability", c.GetTable().SortColCmd("VULNERABILITY", false), true),
+ ui.KeyShiftL: ui.NewKeyAction("Sort Lib", i.GetTable().SortColCmd("LIBRARY", false), true),
+ ui.KeyShiftS: ui.NewKeyAction("Sort Severity", i.GetTable().SortColCmd("SEVERITY", false), true),
+ ui.KeyShiftF: ui.NewKeyAction("Sort Fixed-in", i.GetTable().SortColCmd("FIXED-IN", false), true),
+ ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerability", i.GetTable().SortColCmd("VULNERABILITY", false), true),
})
}
-func (s *ImageScan) viewCVE(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (*ImageScan) viewCVE(app *App, _ ui.Tabular, _ *client.GVR, path string) {
bin := browseLinux
if runtime.GOOS == "darwin" {
bin = browseOSX
@@ -69,7 +68,7 @@ func (s *ImageScan) viewCVE(app *App, _ ui.Tabular, _ client.GVR, path string) {
}
site += cve
- ok, errChan, _ := run(app, shellOpts{
+ ok, errChan, _ := run(app, &shellOpts{
background: true,
binary: bin,
args: []string{site},
diff --git a/internal/view/job.go b/internal/view/job.go
index c4562273..cccb65b0 100644
--- a/internal/view/job.go
+++ b/internal/view/job.go
@@ -21,7 +21,7 @@ type Job struct {
}
// NewJob returns a new viewer.
-func NewJob(gvr client.GVR) ResourceViewer {
+func NewJob(gvr *client.GVR) ResourceViewer {
var j Job
j.ResourceViewer = NewVulnerabilityExtender(
@@ -35,8 +35,8 @@ func NewJob(gvr client.GVR) ResourceViewer {
return &j
}
-func (*Job) showPods(app *App, model ui.Tabular, gvr client.GVR, path string) {
- o, err := app.factory.Get(gvr.String(), path, true, labels.Everything())
+func (*Job) showPods(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
+ o, err := app.factory.Get(gvr, path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return
@@ -62,12 +62,12 @@ func (j *Job) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err
}
- return podLogOptions(j.App(), path, prev, job.ObjectMeta, job.Spec.Template.Spec), nil
+ return podLogOptions(j.App(), path, prev, &job.ObjectMeta, &job.Spec.Template.Spec), nil
}
func (j *Job) getInstance(fqn string) (*batchv1.Job, error) {
var job dao.Job
- job.Init(j.App().factory, client.NewGVR("batch/v1/jobs"))
+ job.Init(j.App().factory, client.JobGVR)
return job.GetInstance(fqn)
}
diff --git a/internal/view/live_view.go b/internal/view/live_view.go
index 69746178..dcbbd070 100644
--- a/internal/view/live_view.go
+++ b/internal/view/live_view.go
@@ -62,9 +62,9 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView {
return &v
}
-func (v *LiveView) SetCommand(*cmd.Interpreter) {}
-func (v *LiveView) SetFilter(string) {}
-func (v *LiveView) SetLabelFilter(map[string]string) {}
+func (*LiveView) SetCommand(*cmd.Interpreter) {}
+func (*LiveView) SetFilter(string) {}
+func (*LiveView) SetLabelFilter(map[string]string) {}
// Init initializes the viewer.
func (v *LiveView) Init(_ context.Context) error {
@@ -129,7 +129,7 @@ func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) {
}
// BufferChanged indicates the buffer was changed.
-func (v *LiveView) BufferChanged(_, _ string) {}
+func (*LiveView) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted.
func (v *LiveView) BufferCompleted(text, _ string) {
@@ -193,7 +193,7 @@ func (v *LiveView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
}
// ToggleRefreshCmd is used for pausing the refreshing of data on config map and secrets.
-func (v *LiveView) toggleRefreshCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (v *LiveView) toggleRefreshCmd(*tcell.EventKey) *tcell.EventKey {
v.autoRefresh = !v.autoRefresh
if v.autoRefresh {
v.Start()
@@ -216,9 +216,9 @@ func (v *LiveView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
// StylesChanged notifies the skin changed.
func (v *LiveView) StylesChanged(s *config.Styles) {
- v.SetBackgroundColor(v.app.Styles.BgColor())
- v.text.SetTextColor(v.app.Styles.FgColor())
- v.SetBorderFocusColor(v.app.Styles.Frame().Border.FocusColor.Color())
+ v.SetBackgroundColor(s.BgColor())
+ v.text.SetTextColor(s.FgColor())
+ v.SetBorderFocusColor(s.Frame().Border.FocusColor.Color())
}
// Actions returns menu actions.
@@ -264,7 +264,7 @@ func (v *LiveView) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (v *LiveView) ExtraHints() map[string]string {
+func (*LiveView) ExtraHints() map[string]string {
return nil
}
@@ -332,7 +332,7 @@ func (v *LiveView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (v *LiveView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (v *LiveView) filterCmd(*tcell.EventKey) *tcell.EventKey {
v.model.Filter(v.cmdBuff.GetText())
v.cmdBuff.SetActive(false)
v.updateTitle()
@@ -349,7 +349,7 @@ func (v *LiveView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (v *LiveView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (v *LiveView) eraseCmd(*tcell.EventKey) *tcell.EventKey {
if !v.cmdBuff.IsActive() {
return nil
}
@@ -374,7 +374,7 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (v *LiveView) saveCmd(*tcell.EventKey) *tcell.EventKey {
name := fmt.Sprintf("%s--%s", strings.Replace(v.model.GetPath(), "/", "-", 1), strings.ToLower(v.title))
if _, err := saveYAML(v.app.Config.K9s.ContextScreenDumpDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil {
v.app.Flash().Err(err)
@@ -394,9 +394,12 @@ func (v *LiveView) updateTitle() {
fmat = fmt.Sprintf(liveViewTitleFmt, v.title, v.model.GetPath())
}
- buff := v.cmdBuff.GetText()
+ var (
+ buff = v.cmdBuff.GetText()
+ styles = v.app.Styles.Frame()
+ )
if buff == "" {
- v.SetTitle(ui.SkinTitle(fmat, v.app.Styles.Frame()))
+ v.SetTitle(ui.SkinTitle(fmat, &styles))
return
}
@@ -404,5 +407,5 @@ func (v *LiveView) updateTitle() {
buff += fmt.Sprintf("[%d:%d]", v.currentRegion+1, v.maxRegions)
}
fmat += fmt.Sprintf(ui.SearchFmt, buff)
- v.SetTitle(ui.SkinTitle(fmat, v.app.Styles.Frame()))
+ v.SetTitle(ui.SkinTitle(fmat, &styles))
}
diff --git a/internal/view/live_view_test.go b/internal/view/live_view_test.go
index 923ed3a8..d3771bdc 100644
--- a/internal/view/live_view_test.go
+++ b/internal/view/live_view_test.go
@@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestLiveViewSetText(t *testing.T) {
@@ -20,7 +21,7 @@ apiVersion: v1
`
v := NewLiveView(NewApp(mock.NewMockConfig()), "fred", nil)
- assert.NoError(t, v.Init(context.Background()))
+ require.NoError(t, v.Init(context.Background()))
v.text.SetText(colorizeYAML(config.Yaml{}, s))
assert.Equal(t, s, sanitizeEsc(v.text.GetText(true)))
diff --git a/internal/view/log.go b/internal/view/log.go
index 2134ea62..5bd71783 100644
--- a/internal/view/log.go
+++ b/internal/view/log.go
@@ -54,7 +54,7 @@ type Log struct {
var _ model.Component = (*Log)(nil)
// NewLog returns a new viewer.
-func NewLog(gvr client.GVR, opts *dao.LogOptions) *Log {
+func NewLog(gvr *client.GVR, opts *dao.LogOptions) *Log {
l := Log{
Flex: tview.NewFlex(),
model: model.NewLog(gvr, opts, defaultFlushTimeout),
@@ -86,8 +86,8 @@ func (l *Log) Init(ctx context.Context) (err error) {
l.indicator.Refresh()
l.logs = NewLogger(l.app)
- if err = l.logs.Init(ctx); err != nil {
- return err
+ if e := l.logs.Init(ctx); e != nil {
+ return e
}
l.logs.SetBorderPadding(0, 0, 1, 1)
l.logs.SetText("[orange::d]" + logMessage)
@@ -174,7 +174,7 @@ func (l *Log) BufferCompleted(text, _ string) {
}
// BufferChanged indicates the buffer was changed.
-func (l *Log) BufferChanged(_, _ string) {}
+func (*Log) BufferChanged(_, _ string) {}
// BufferActive indicates the buff activity changed.
func (l *Log) BufferActive(state bool, k model.BufferKind) {
@@ -199,7 +199,7 @@ func (l *Log) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (l *Log) ExtraHints() map[string]string {
+func (*Log) ExtraHints() map[string]string {
return nil
}
@@ -241,7 +241,7 @@ func (l *Log) Stop() {
}
// Name returns the component name.
-func (l *Log) Name() string { return logTitle }
+func (*Log) Name() string { return logTitle }
func (l *Log) bindKeys() {
l.logs.Actions().Bulk(ui.KeyMap{
@@ -316,16 +316,19 @@ func (l *Log) updateTitle() {
if l.model.LogOptions().Previous {
title = " Previous Logs"
}
- path, co := l.model.GetPath(), l.model.GetContainer()
+ var (
+ path, co = l.model.GetPath(), l.model.GetContainer()
+ styles = l.app.Styles.Frame()
+ )
if co == "" {
- title += ui.SkinTitle(fmt.Sprintf(logFmt, path, since), l.app.Styles.Frame())
+ title += ui.SkinTitle(fmt.Sprintf(logFmt, path, since), &styles)
} else {
- title += ui.SkinTitle(fmt.Sprintf(logCoFmt, path, co, since), l.app.Styles.Frame())
+ title += ui.SkinTitle(fmt.Sprintf(logCoFmt, path, co, since), &styles)
}
buff := l.logs.cmdBuff.GetText()
if buff != "" {
- title += ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), l.app.Styles.Frame())
+ title += ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), &styles)
}
l.SetTitle(title)
}
@@ -352,7 +355,7 @@ func (l *Log) Flush(lines [][]byte) {
if l.requestOneRefresh {
l.requestOneRefresh = false
}
- for i := 0; i < len(lines); i++ {
+ for i := range lines {
if l.cancelUpdates {
break
}
@@ -367,7 +370,7 @@ func (l *Log) Flush(lines [][]byte) {
// Actions...
func (l *Log) sinceCmd(n int) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
l.logs.Clear()
ctx := l.getContext()
if n == 0 {
diff --git a/internal/view/log_indicator_test.go b/internal/view/log_indicator_test.go
index 0c793f59..a49d658a 100644
--- a/internal/view/log_indicator_test.go
+++ b/internal/view/log_indicator_test.go
@@ -40,7 +40,7 @@ func BenchmarkLogIndicatorRefresh(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
v.Refresh()
}
}
diff --git a/internal/view/log_int_test.go b/internal/view/log_int_test.go
index f6b5e4ae..64b3ae22 100644
--- a/internal/view/log_int_test.go
+++ b/internal/view/log_int_test.go
@@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestLogAutoScroll(t *testing.T) {
@@ -19,14 +20,14 @@ func TestLogAutoScroll(t *testing.T) {
Container: "blee",
SingleContainer: true,
}
- v := NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
ii := dao.NewLogItems()
ii.Add(dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo"))
v.GetModel().Set(ii)
v.GetModel().Notify()
- assert.Equal(t, 16, len(v.Hints()))
+ assert.Len(t, v.Hints(), 16)
v.toggleAutoScrollCmd(nil)
assert.Equal(t, "Autoscroll:Off FullScreen:Off Timestamps:Off Wrap:Off", v.Indicator().GetText(true))
@@ -37,11 +38,11 @@ func TestLogViewNav(t *testing.T) {
Path: "fred/p1",
Container: "blee",
}
- v := NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
buff := dao.NewLogItems()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
buff.Add(dao.NewLogItemFromString(fmt.Sprintf("line-%d\n", i)))
}
v.GetModel().Set(buff)
@@ -56,14 +57,14 @@ func TestLogViewClear(t *testing.T) {
Path: "fred/p1",
Container: "blee",
}
- v := NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
v.toggleAutoScrollCmd(nil)
v.Logs().SetText("blee\nblah")
v.Logs().Clear()
- assert.Equal(t, "", v.Logs().GetText(true))
+ assert.Empty(t, v.Logs().GetText(true))
}
func TestLogTimestamp(t *testing.T) {
@@ -72,7 +73,7 @@ func TestLogTimestamp(t *testing.T) {
Container: "c1",
}
l := NewLog(client.NewGVR("test"), &opts)
- assert.NoError(t, l.Init(makeContext()))
+ require.NoError(t, l.Init(makeContext()))
ii := dao.NewLogItems()
ii.Add(
&dao.LogItem{
@@ -102,7 +103,7 @@ func TestLogFilter(t *testing.T) {
Container: "c1",
}
l := NewLog(client.NewGVR("test"), &opts)
- assert.NoError(t, l.Init(makeContext()))
+ require.NoError(t, l.Init(makeContext()))
buff := dao.NewLogItems()
buff.Add(
dao.NewLogItemFromString("duh"),
@@ -135,8 +136,8 @@ func (l *logList) LogChanged(ll [][]byte) {
l.lines += string(line)
}
}
-func (l *logList) LogCanceled() {}
-func (l *logList) LogStop() {}
-func (l *logList) LogResume() {}
+func (*logList) LogCanceled() {}
+func (*logList) LogStop() {}
+func (*logList) LogResume() {}
func (l *logList) LogCleared() { l.clear++ }
func (l *logList) LogFailed(error) { l.fail++ }
diff --git a/internal/view/log_test.go b/internal/view/log_test.go
index fc463d1a..0708960a 100644
--- a/internal/view/log_test.go
+++ b/internal/view/log_test.go
@@ -19,6 +19,7 @@ import (
"github.com/derailed/k9s/internal/view"
"github.com/derailed/tview"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestLog(t *testing.T) {
@@ -26,8 +27,8 @@ func TestLog(t *testing.T) {
Path: "fred/p1",
Container: "blee",
}
- v := view.NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := view.NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
ii := dao.NewLogItems()
ii.Add(dao.NewLogItemFromString("blee\n"), dao.NewLogItemFromString("bozo\n"))
@@ -43,8 +44,8 @@ func TestLogFlush(t *testing.T) {
Path: "fred/p1",
Container: "blee",
}
- v := view.NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := view.NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
items := dao.NewLogItems()
items.Add(
@@ -63,7 +64,7 @@ func BenchmarkLogFlush(b *testing.B) {
Path: "fred/p1",
Container: "blee",
}
- v := view.NewLog(client.NewGVR("v1/pods"), &opts)
+ v := view.NewLog(client.PodGVR, &opts)
_ = v.Init(makeContext())
items := dao.NewLogItems()
@@ -77,7 +78,7 @@ func BenchmarkLogFlush(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for range b.N {
v.Flush(ll)
}
}
@@ -101,8 +102,8 @@ func TestLogViewSave(t *testing.T) {
Path: "fred/p1",
Container: "blee",
}
- v := view.NewLog(client.NewGVR("v1/pods"), &opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := view.NewLog(client.PodGVR, &opts)
+ require.NoError(t, v.Init(makeContext()))
app := makeApp()
ii := dao.NewLogItems()
@@ -112,15 +113,15 @@ func TestLogViewSave(t *testing.T) {
v.Flush(ll)
dd := "/tmp/test-dumps/na"
- assert.NoError(t, ensureDumpDir(dd))
+ require.NoError(t, ensureDumpDir(dd))
app.Config.K9s.ScreenDumpDir = "/tmp/test-dumps"
dir := app.Config.K9s.ContextScreenDumpDir()
c1, err := os.ReadDir(dir)
- assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir))
+ require.NoError(t, err, "Dir: %q", dir)
v.SaveCmd(nil)
c2, err := os.ReadDir(dir)
- assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir))
- assert.Equal(t, len(c2), len(c1)+1)
+ require.NoError(t, err, "Dir: %q", dir)
+ assert.Len(t, c2, len(c1)+1)
}
func TestAllContainerKeyBinding(t *testing.T) {
@@ -139,8 +140,8 @@ func TestAllContainerKeyBinding(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- v := view.NewLog(client.NewGVR("v1/pods"), u.opts)
- assert.NoError(t, v.Init(makeContext()))
+ v := view.NewLog(client.PodGVR, u.opts)
+ require.NoError(t, v.Init(makeContext()))
_, got := v.Logs().Actions().Get(ui.KeyA)
assert.Equal(t, u.e, got)
})
diff --git a/internal/view/logger.go b/internal/view/logger.go
index 7fc649f0..cf56606d 100644
--- a/internal/view/logger.go
+++ b/internal/view/logger.go
@@ -58,10 +58,10 @@ func (l *Logger) Init(_ context.Context) error {
}
// BufferChanged indicates the buffer was changed.
-func (l *Logger) BufferChanged(_, _ string) {}
+func (*Logger) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted.
-func (l *Logger) BufferCompleted(_, _ string) {}
+func (*Logger) BufferCompleted(_, _ string) {}
// BufferActive indicates the buff activity changed.
func (l *Logger) BufferActive(state bool, k model.BufferKind) {
@@ -87,7 +87,7 @@ func (l *Logger) keyboard(evt *tcell.EventKey) *tcell.EventKey {
}
// StylesChanged notifies the skin changed.
-func (l *Logger) StylesChanged(s *config.Styles) {
+func (l *Logger) StylesChanged(*config.Styles) {
l.SetBackgroundColor(l.app.Styles.BgColor())
l.SetTextColor(l.app.Styles.FgColor())
l.SetBorderFocusColor(l.app.Styles.Frame().Border.FocusColor.Color())
@@ -107,7 +107,7 @@ func (l *Logger) Actions() *ui.KeyActions {
func (l *Logger) Name() string { return l.title }
// Start starts the view updater.
-func (l *Logger) Start() {}
+func (*Logger) Start() {}
// Stop terminates the updater.
func (l *Logger) Stop() {
@@ -120,7 +120,7 @@ func (l *Logger) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (l *Logger) ExtraHints() map[string]string {
+func (*Logger) ExtraHints() map[string]string {
return nil
}
@@ -133,7 +133,7 @@ func (l *Logger) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (l *Logger) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (l *Logger) eraseCmd(*tcell.EventKey) *tcell.EventKey {
if !l.cmdBuff.IsActive() {
return nil
}
@@ -153,7 +153,7 @@ func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (l *Logger) saveCmd(*tcell.EventKey) *tcell.EventKey {
if path, err := saveYAML(l.app.Config.K9s.ContextScreenDumpDir(), l.title, l.GetText(true)); err != nil {
l.app.Flash().Err(err)
} else {
diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go
index d2a8ad4f..a60313bd 100644
--- a/internal/view/logs_extender.go
+++ b/internal/view/logs_extender.go
@@ -39,7 +39,7 @@ func (l *LogsExtender) bindKeys(aa *ui.KeyActions) {
}
func (l *LogsExtender) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
path := l.GetTable().GetSelectedItem()
if path == "" {
return nil
@@ -60,7 +60,7 @@ func isResourcePath(p string) bool {
func (l *LogsExtender) showLogs(path string, prev bool) {
ns, _ := client.Namespaced(path)
- _, err := l.App().factory.CanForResource(ns, "v1/pods", client.ListAccess)
+ _, err := l.App().factory.CanForResource(ns, client.PodGVR, client.ListAccess)
if err != nil {
l.App().Flash().Err(err)
return
@@ -83,7 +83,7 @@ func (l *LogsExtender) buildLogOpts(path, co string, prevLogs bool) *dao.LogOpti
opts := dao.LogOptions{
Path: path,
Container: co,
- Lines: int64(cfg.TailCount),
+ Lines: cfg.TailCount,
Previous: prevLogs,
ShowTimestamp: cfg.ShowTime,
}
@@ -94,13 +94,13 @@ func (l *LogsExtender) buildLogOpts(path, co string, prevLogs bool) *dao.LogOpti
return &opts
}
-func podLogOptions(app *App, fqn string, prev bool, m metav1.ObjectMeta, spec v1.PodSpec) *dao.LogOptions {
+func podLogOptions(app *App, fqn string, prev bool, m *metav1.ObjectMeta, spec *v1.PodSpec) *dao.LogOptions {
var (
cc = fetchContainers(m, spec, true)
cfg = app.Config.K9s.Logger
opts = dao.LogOptions{
Path: fqn,
- Lines: int64(cfg.TailCount),
+ Lines: cfg.TailCount,
SinceSeconds: cfg.SinceSeconds,
SingleContainer: len(cc) == 1,
ShowTimestamp: cfg.ShowTime,
diff --git a/internal/view/node.go b/internal/view/node.go
index 8d024630..25daea71 100644
--- a/internal/view/node.go
+++ b/internal/view/node.go
@@ -25,7 +25,7 @@ type Node struct {
}
// NewNode returns a new node view.
-func NewNode(gvr client.GVR) ResourceViewer {
+func NewNode(gvr *client.GVR) ResourceViewer {
n := Node{
ResourceViewer: NewBrowser(gvr),
}
@@ -91,7 +91,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)
}
@@ -156,7 +156,8 @@ func (n *Node) toggleCordonCmd(cordon bool) func(evt *tcell.EventKey) *tcell.Eve
} else {
msg += fmt.Sprintf("(%d) marked %s?", len(sels), n.GVR().R())
}
- dialog.ShowConfirm(n.App().Styles.Dialog(), n.App().Content.Pages, title, msg, func() {
+ d := n.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, n.App().Content.Pages, title, msg, func() {
res, err := dao.AccessorFor(n.App().factory, n.GVR())
if err != nil {
n.App().Flash().Err(err)
diff --git a/internal/view/ns.go b/internal/view/ns.go
index ffde8a42..6453176b 100644
--- a/internal/view/ns.go
+++ b/internal/view/ns.go
@@ -21,7 +21,7 @@ type Namespace struct {
}
// NewNamespace returns a new viewer.
-func NewNamespace(gvr client.GVR) ResourceViewer {
+func NewNamespace(gvr *client.GVR) ResourceViewer {
n := Namespace{
ResourceViewer: NewBrowser(gvr),
}
@@ -39,12 +39,12 @@ func (n *Namespace) bindKeys(aa *ui.KeyActions) {
})
}
-func (n *Namespace) switchNs(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (n *Namespace) switchNs(app *App, _ ui.Tabular, _ *client.GVR, path string) {
n.useNamespace(path)
app.gotoResource("pods", "", false, true)
}
-func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (n *Namespace) useNsCmd(*tcell.EventKey) *tcell.EventKey {
path := n.GetTable().GetSelectedItem()
if path == "" {
return nil
diff --git a/internal/view/ns_test.go b/internal/view/ns_test.go
index 3c06101f..5ab3e281 100644
--- a/internal/view/ns_test.go
+++ b/internal/view/ns_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNSCleanser(t *testing.T) {
- ns := view.NewNamespace(client.NewGVR("v1/namespaces"))
+ ns := view.NewNamespace(client.NsGVR)
- assert.Nil(t, ns.Init(makeCtx()))
+ require.NoError(t, ns.Init(makeCtx()))
assert.Equal(t, "Namespaces", ns.Name())
- assert.Equal(t, 7, len(ns.Hints()))
+ assert.Len(t, ns.Hints(), 7)
}
diff --git a/internal/view/owner_extender.go b/internal/view/owner_extender.go
index 1425e88c..f09ba79b 100644
--- a/internal/view/owner_extender.go
+++ b/internal/view/owner_extender.go
@@ -75,16 +75,17 @@ func (v *OwnerExtender) findOwnerFor(path string) error {
ns, _ := client.Namespaced(path)
ownerReferences := u.GetOwnerReferences()
if len(ownerReferences) == 1 {
- return v.jumpOwner(ns, ownerReferences[0])
+ return v.jumpOwner(ns, &ownerReferences[0])
} else if len(ownerReferences) > 1 {
owners := make([]string, 0, len(ownerReferences))
for idx, ownerRef := range ownerReferences {
owners = append(owners, fmt.Sprintf("%d: %s", idx, ownerRef.Kind))
}
- dialog.ShowSelection(v.App().Styles.Dialog(), v.App().Content.Pages, "Jump To", owners, func(index int) {
+ d := v.App().Styles.Dialog()
+ dialog.ShowSelection(&d, v.App().Content.Pages, "Jump To", owners, func(index int) {
if index >= 0 {
- err = v.jumpOwner(ns, ownerReferences[index])
+ err = v.jumpOwner(ns, &ownerReferences[index])
}
})
return err
@@ -93,7 +94,7 @@ func (v *OwnerExtender) findOwnerFor(path string) error {
return errors.Errorf("no owner found")
}
-func (v *OwnerExtender) jumpOwner(ns string, owner metav1.OwnerReference) error {
+func (v *OwnerExtender) jumpOwner(ns string, owner *metav1.OwnerReference) error {
gv, err := schema.ParseGroupVersion(owner.APIVersion)
if err != nil {
return err
@@ -119,7 +120,7 @@ func (v *OwnerExtender) defaultCtx() context.Context {
return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory)
}
-func (v *OwnerExtender) asUnstructuredObject(o runtime.Object) (*unstructured.Unstructured, bool) {
+func (*OwnerExtender) asUnstructuredObject(o runtime.Object) (*unstructured.Unstructured, bool) {
switch v := o.(type) {
case *unstructured.Unstructured:
return v, true
diff --git a/internal/view/pf.go b/internal/view/pf.go
index eaa7c86a..a1c00f24 100644
--- a/internal/view/pf.go
+++ b/internal/view/pf.go
@@ -29,7 +29,7 @@ type PortForward struct {
}
// NewPortForward returns a new viewer.
-func NewPortForward(gvr client.GVR) ResourceViewer {
+func NewPortForward(gvr *client.GVR) ResourceViewer {
p := PortForward{
ResourceViewer: NewBrowser(gvr),
}
@@ -60,8 +60,8 @@ func (p *PortForward) bindKeys(aa *ui.KeyActions) {
})
}
-func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
- b := NewBenchmark(client.NewGVR("benchmarks"))
+func (p *PortForward) showBenchCmd(*tcell.EventKey) *tcell.EventKey {
+ b := NewBenchmark(client.BeGVR)
b.SetContextFn(p.getContext)
if err := p.App().inject(b, false); err != nil {
p.App().Flash().Err(err)
@@ -80,7 +80,7 @@ func (p *PortForward) getContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyPath, path)
}
-func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (p *PortForward) toggleBenchCmd(*tcell.EventKey) *tcell.EventKey {
if p.bench != nil {
p.App().Status(model.FlashErr, "Benchmark Canceled!")
p.bench.Cancel()
@@ -103,7 +103,7 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
}
base := ui.TrimCell(p.GetTable().SelectTable, r, col)
var err error
- p.bench, err = perf.NewBenchmark(base, p.App().version, cfg)
+ p.bench, err = perf.NewBenchmark(base, p.App().version, &cfg)
if err != nil {
p.App().Flash().Errf("Bench failed %v", err)
p.App().ClearStatus(false)
@@ -171,10 +171,11 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
- dialog.ShowConfirm(p.App().Styles.Dialog(), p.App().Content.Pages, "Delete", msg, func() {
+ d := p.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, p.App().Content.Pages, "Delete", msg, func() {
for _, s := range selections {
var pf dao.PortForward
- pf.Init(p.App().factory, client.NewGVR("portforwards"))
+ pf.Init(p.App().factory, client.PfGVR)
if err := pf.Delete(context.Background(), s, nil, dao.DefaultGrace); err != nil {
p.App().Flash().Err(err)
return
diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go
index 5fdbd81b..6bb53024 100644
--- a/internal/view/pf_dialog.go
+++ b/internal/view/pf_dialog.go
@@ -63,7 +63,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
address = h
})
- for i := 0; i < 3; i++ {
+ for i := range 3 {
if field, ok := f.GetFormItem(i).(*tview.InputField); ok {
field.SetLabelColor(styles.LabelFgColor.Color())
field.SetFieldTextColor(styles.FieldFgColor.Color())
@@ -88,7 +88,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
f.AddButton("Cancel", func() {
DismissPortForwards(v, pages)
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
if b := f.GetButton(i); b != nil {
b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color())
b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
@@ -103,7 +103,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
modal.SetText(msg)
modal.SetTextColor(styles.FgColor.Color())
modal.SetBackgroundColor(styles.BgColor.Color())
- modal.SetDoneFunc(func(_ int, b string) {
+ modal.SetDoneFunc(func(int, string) {
DismissPortForwards(v, pages)
})
diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go
index 6a0072a7..893c5c30 100644
--- a/internal/view/pf_extender.go
+++ b/internal/view/pf_extender.go
@@ -83,7 +83,7 @@ func (p *PortForwardExtender) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
- pf := NewPortForward(client.NewGVR("portforwards"))
+ pf := NewPortForward(client.PfGVR)
pf.SetContextFn(p.portForwardContext)
if err := p.App().inject(pf, false); err != nil {
p.App().Flash().Err(err)
@@ -209,9 +209,9 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error {
return nil
}
-func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort, map[string]string, error) {
+func fetchPodPorts(f *watch.Factory, path string) (ports map[string][]v1.ContainerPort, anns map[string]string, err error) {
slog.Debug("Fetching ports on pod", slogs.FQN, path)
- o, err := f.Get("v1/pods", path, true, labels.Everything())
+ o, err := f.Get(client.PodGVR, path, true, labels.Everything())
if err != nil {
return nil, nil, err
}
@@ -223,8 +223,8 @@ func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort
}
pp := make(map[string][]v1.ContainerPort, len(pod.Spec.Containers))
- for _, co := range pod.Spec.Containers {
- pp[co.Name] = co.Ports
+ for i := range pod.Spec.Containers {
+ pp[pod.Spec.Containers[i].Name] = pod.Spec.Containers[i].Ports
}
return pp, pod.Annotations, nil
diff --git a/internal/view/pf_extender_test.go b/internal/view/pf_extender_test.go
index 23c6b60d..32a5b95d 100644
--- a/internal/view/pf_extender_test.go
+++ b/internal/view/pf_extender_test.go
@@ -45,8 +45,8 @@ func TestEnsurePodPortFwdAllowed(t *testing.T) {
f := testFactory{}
if u.podExists {
f.expectedGet = &unstructured.Unstructured{
- Object: map[string]interface{}{
- "status": map[string]interface{}{
+ Object: map[string]any{
+ "status": map[string]any{
"phase": u.podPhase,
},
},
@@ -69,34 +69,27 @@ type testFactory struct {
var _ dao.Factory = testFactory{}
-func (t testFactory) Client() client.Connection {
+func (testFactory) Client() client.Connection {
return nil
}
-
-func (t testFactory) Get(string, string, bool, labels.Selector) (runtime.Object, error) {
+func (t testFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
if t.expectedGet != nil {
return t.expectedGet, nil
}
return nil, errors.New("not found")
}
-
-func (t testFactory) List(string, string, bool, labels.Selector) ([]runtime.Object, error) {
+func (testFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
-
-func (t testFactory) ForResource(string, string) (informers.GenericInformer, error) {
+func (testFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-
-func (t testFactory) CanForResource(string, string, []string) (informers.GenericInformer, error) {
+func (testFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
-
-func (t testFactory) Forwarders() watch.Forwarders {
+func (testFactory) Forwarders() watch.Forwarders {
return nil
}
-
-func (t testFactory) WaitForCacheSync() {}
-
-func (t testFactory) DeleteForwarder(string) {}
+func (testFactory) WaitForCacheSync() {}
+func (testFactory) DeleteForwarder(string) {}
diff --git a/internal/view/pf_test.go b/internal/view/pf_test.go
index 245e5939..ef04b9e3 100644
--- a/internal/view/pf_test.go
+++ b/internal/view/pf_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPortForwardNew(t *testing.T) {
- pf := view.NewPortForward(client.NewGVR("portforwards"))
+ pf := view.NewPortForward(client.PfGVR)
- assert.Nil(t, pf.Init(makeCtx()))
+ require.NoError(t, pf.Init(makeCtx()))
assert.Equal(t, "PortForwards", pf.Name())
- assert.Equal(t, 10, len(pf.Hints()))
+ assert.Len(t, pf.Hints(), 10)
}
diff --git a/internal/view/picker.go b/internal/view/picker.go
index aa2e5b46..9abb4b7a 100644
--- a/internal/view/picker.go
+++ b/internal/view/picker.go
@@ -28,9 +28,9 @@ func NewPicker() *Picker {
}
}
-func (p *Picker) SetCommand(*cmd.Interpreter) {}
-func (p *Picker) SetFilter(string) {}
-func (p *Picker) SetLabelFilter(map[string]string) {}
+func (*Picker) SetCommand(*cmd.Interpreter) {}
+func (*Picker) SetFilter(string) {}
+func (*Picker) SetLabelFilter(map[string]string) {}
// Init initializes the view.
func (p *Picker) Init(ctx context.Context) error {
@@ -66,13 +66,13 @@ func (*Picker) InCmdMode() bool {
}
// Start starts the view.
-func (p *Picker) Start() {}
+func (*Picker) Start() {}
// Stop stops the view.
-func (p *Picker) Stop() {}
+func (*Picker) Stop() {}
// Name returns the component name.
-func (p *Picker) Name() string { return "picker" }
+func (*Picker) Name() string { return "picker" }
// Hints returns the view hints.
func (p *Picker) Hints() model.MenuHints {
@@ -80,7 +80,7 @@ func (p *Picker) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (p *Picker) ExtraHints() map[string]string {
+func (*Picker) ExtraHints() map[string]string {
return nil
}
diff --git a/internal/view/pod.go b/internal/view/pod.go
index cff95690..5782ff0f 100644
--- a/internal/view/pod.go
+++ b/internal/view/pod.go
@@ -48,7 +48,7 @@ type Pod struct {
}
// NewPod returns a new viewer.
-func NewPod(gvr client.GVR) ResourceViewer {
+func NewPod(gvr *client.GVR) ResourceViewer {
var p Pod
p.ResourceViewer = NewPortForwardExtender(
NewOwnerExtender(
@@ -150,11 +150,11 @@ func (p *Pod) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err
}
- return podLogOptions(p.App(), path, prev, pod.ObjectMeta, pod.Spec), nil
+ return podLogOptions(p.App(), path, prev, &pod.ObjectMeta, &pod.Spec), nil
}
-func (p *Pod) showContainers(app *App, _ ui.Tabular, _ client.GVR, _ string) {
- co := NewContainer(client.NewGVR("containers"))
+func (p *Pod) showContainers(app *App, _ ui.Tabular, _ *client.GVR, _ string) {
+ co := NewContainer(client.CoGVR)
co.SetContextFn(p.coContext)
if err := app.inject(co, false); err != nil {
app.Flash().Err(err)
@@ -181,9 +181,8 @@ func (p *Pod) showNode(evt *tcell.EventKey) *tcell.EventKey {
p.App().Flash().Err(errors.New("no node assigned"))
return nil
}
- no := NewNode(client.NewGVR("v1/nodes"))
+ no := NewNode(client.NodeGVR)
no.SetInstance(pod.Spec.NodeName)
- //no.SetContextFn(nodeContext(pod.Spec.NodeName))
if err := p.App().inject(no, false); err != nil {
p.App().Flash().Err(err)
}
@@ -262,7 +261,7 @@ func (p *Pod) attachCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (p *Pod) sanitizeCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (p *Pod) sanitizeCmd(*tcell.EventKey) *tcell.EventKey {
res, err := dao.AccessorFor(p.App().factory, p.GVR())
if err != nil {
p.App().Flash().Err(err)
@@ -290,7 +289,7 @@ func (p *Pod) sanitizeCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (p *Pod) transferCmd(*tcell.EventKey) *tcell.EventKey {
path := p.GetTable().GetSelectedItem()
if path == "" {
return nil
@@ -308,11 +307,13 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
}
opts := make([]string, 0, 10)
- opts = append(opts, "cp")
- opts = append(opts, strings.TrimSpace(args.From))
- opts = append(opts, strings.TrimSpace(args.To))
- opts = append(opts, fmt.Sprintf("--no-preserve=%t", args.NoPreserve))
- opts = append(opts, fmt.Sprintf("--retries=%d", args.Retries))
+ opts = append(opts,
+ "cp",
+ strings.TrimSpace(args.From),
+ strings.TrimSpace(args.To),
+ fmt.Sprintf("--no-preserve=%t", args.NoPreserve),
+ fmt.Sprintf("--retries=%d", args.Retries),
+ )
if args.CO != "" {
opts = append(opts, "-c="+args.CO)
}
@@ -328,7 +329,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
}
fqn := path + ":" + args.CO
- if err := runK(p.App(), cliOpts); err != nil {
+ if err := runK(p.App(), &cliOpts); err != nil {
p.App().cowCmd(err.Error())
} else {
p.App().Flash().Infof("%s successful on %s!", op, fqn)
@@ -344,14 +345,15 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
opts := dialog.TransferDialogOpts{
Title: "Transfer",
- Containers: fetchContainers(pod.ObjectMeta, pod.Spec, false),
+ Containers: fetchContainers(&pod.ObjectMeta, &pod.Spec, false),
Message: "Download Files",
Pod: fmt.Sprintf("%s/%s:", ns, n),
Ack: ack,
Retries: defaultTxRetries,
Cancel: func() {},
}
- dialog.ShowUploads(p.App().Styles.Dialog(), p.App().Content.Pages, opts)
+ d := p.App().Styles.Dialog()
+ dialog.ShowUploads(&d, p.App().Content.Pages, &opts)
return nil
}
@@ -369,12 +371,12 @@ func containerShellIn(a *App, comp model.Component, path, co string) error {
if err != nil {
return err
}
- if dco, ok := dao.GetDefaultContainer(pod.ObjectMeta, pod.Spec); ok {
+ if dco, ok := dao.GetDefaultContainer(&pod.ObjectMeta, &pod.Spec); ok {
resumeShellIn(a, comp, path, dco)
return nil
}
- cc := fetchContainers(pod.ObjectMeta, pod.Spec, false)
+ cc := fetchContainers(&pod.ObjectMeta, &pod.Spec, false)
if len(cc) == 1 {
resumeShellIn(a, comp, path, cc[0])
return nil
@@ -404,7 +406,11 @@ func shellIn(a *App, fqn, co string) {
args := computeShellArgs(fqn, co, a.Conn().Config().Flags().KubeConfig, os)
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
- err = runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, fqn, co), args: args})
+ err = runK(a, &shellOpts{
+ clear: true,
+ banner: c.Sprintf(bannerFmt, fqn, co),
+ args: args},
+ )
if err != nil {
a.Flash().Errf("Shell exec failed: %s", err)
}
@@ -420,7 +426,7 @@ func containerAttachIn(a *App, comp model.Component, path, co string) error {
if err != nil {
return err
}
- cc := fetchContainers(pod.ObjectMeta, pod.Spec, false)
+ cc := fetchContainers(&pod.ObjectMeta, &pod.Spec, false)
if len(cc) == 1 {
resumeAttachIn(a, comp, path, cc[0])
return nil
@@ -447,7 +453,7 @@ func resumeAttachIn(a *App, c model.Component, path, co string) {
func attachIn(a *App, path, co string) {
args := buildShellArgs("attach", path, co, a.Conn().Config().Flags().KubeConfig)
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
- if err := runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, path, co), args: args}); err != nil {
+ if err := runK(a, &shellOpts{clear: true, banner: c.Sprintf(bannerFmt, path, co), args: args}); err != nil {
a.Flash().Errf("Attach exec failed: %s", err)
}
}
@@ -478,7 +484,7 @@ func buildShellArgs(cmd, path, co string, kcfg *string) []string {
return args
}
-func fetchContainers(meta metav1.ObjectMeta, spec v1.PodSpec, allContainers bool) []string {
+func fetchContainers(meta *metav1.ObjectMeta, spec *v1.PodSpec, allContainers bool) []string {
nn := make([]string, 0, len(spec.Containers)+len(spec.InitContainers))
// put the default container as the first entry
defaultContainer, ok := dao.GetDefaultContainer(meta, spec)
@@ -486,26 +492,27 @@ func fetchContainers(meta metav1.ObjectMeta, spec v1.PodSpec, allContainers bool
nn = append(nn, defaultContainer)
}
- for _, c := range spec.Containers {
- if c.Name != defaultContainer {
- nn = append(nn, c.Name)
+ for i := range spec.Containers {
+ if spec.Containers[i].Name != defaultContainer {
+ nn = append(nn, spec.Containers[i].Name)
}
}
if !allContainers {
return nn
}
- for _, c := range spec.InitContainers {
- nn = append(nn, c.Name)
+
+ for i := range spec.InitContainers {
+ nn = append(nn, spec.InitContainers[i].Name)
}
- for _, c := range spec.EphemeralContainers {
- nn = append(nn, c.Name)
+ for i := range spec.EphemeralContainers {
+ nn = append(nn, spec.EphemeralContainers[i].Name)
}
return nn
}
func fetchPod(f dao.Factory, path string) (*v1.Pod, error) {
- o, err := f.Get("v1/pods", path, true, labels.Everything())
+ o, err := f.Get(client.PodGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
@@ -530,7 +537,7 @@ func podIsRunning(f dao.Factory, fqn string) bool {
}
var re render.Pod
- return re.Phase(po) == render.Running
+ return re.Phase(po.DeletionTimestamp, &po.Spec, &po.Status) == render.Running
}
func getPodOS(f dao.Factory, fqn string) (string, error) {
diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go
index 23bdebaf..c1aee845 100644
--- a/internal/view/pod_test.go
+++ b/internal/view/pod_test.go
@@ -12,14 +12,15 @@ import (
"github.com/derailed/k9s/internal/config/mock"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPodNew(t *testing.T) {
- po := view.NewPod(client.NewGVR("v1/pods"))
+ po := view.NewPod(client.PodGVR)
- assert.Nil(t, po.Init(makeCtx()))
+ require.NoError(t, po.Init(makeCtx()))
assert.Equal(t, "Pods", po.Name())
- assert.Equal(t, 28, len(po.Hints()))
+ assert.Len(t, po.Hints(), 28)
}
// Helpers...
diff --git a/internal/view/policy.go b/internal/view/policy.go
index b412b388..92a92324 100644
--- a/internal/view/policy.go
+++ b/internal/view/policy.go
@@ -26,9 +26,9 @@ type Policy struct {
}
// NewPolicy returns a new viewer.
-func NewPolicy(app *App, subject, name string) *Policy {
+func NewPolicy(_ *App, subject, name string) *Policy {
p := Policy{
- ResourceViewer: NewBrowser(client.NewGVR("policy")),
+ ResourceViewer: NewBrowser(client.PolGVR),
subjectKind: subject,
subjectName: name,
}
diff --git a/internal/view/popeye.go b/internal/view/popeye.go
deleted file mode 100644
index e3c6355b..00000000
--- a/internal/view/popeye.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of K9s
-
-package view
-
-// import (
-// "context"
-// "fmt"
-// "strconv"
-// "time"
-
-// "github.com/derailed/k9s/internal"
-// "github.com/derailed/k9s/internal/client"
-// "github.com/derailed/k9s/internal/render"
-// "github.com/derailed/k9s/internal/ui"
-// "github.com/derailed/tcell/v2"
-// )
-
-// // Popeye represents a sanitizer view.
-// type Popeye struct {
-// ResourceViewer
-// }
-
-// // NewPopeye returns a new view.
-// func NewPopeye(gvr client.GVR) ResourceViewer {
-// p := Popeye{
-// ResourceViewer: NewBrowser(gvr),
-// }
-// p.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
-// p.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone))
-// p.GetTable().SetSortCol("SCORE%", true)
-// p.GetTable().SetDecorateFn(p.decorateRows)
-// p.AddBindKeysFn(p.bindKeys)
-
-// return &p
-// }
-
-// // Init initializes the view.
-// func (p *Popeye) Init(ctx context.Context) error {
-// if err := p.ResourceViewer.Init(ctx); err != nil {
-// return err
-// }
-// p.GetTable().GetModel().SetRefreshRate(5 * time.Second)
-
-// return nil
-// }
-
-// func (p *Popeye) decorateRows(data *model1.TableData) {
-// var sum int
-// for _, re := range data.RowEvents {
-// n, err := strconv.Atoi(re.Row.Fields[1])
-// if err != nil {
-// continue
-// }
-// sum += n
-// }
-// score, letter := 0, render.NAValue
-// if len(data.RowEvents) > 0 {
-// score = sum / len(data.RowEvents)
-// letter = grade(score)
-// }
-// p.GetTable().Extras = fmt.Sprintf("Score %d -- %s", score, letter)
-// }
-
-// func (p *Popeye) bindKeys(aa ui.KeyActions) {
-// aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
-// aa.Add(ui.KeyActions{
-// tcell.KeyEnter: ui.NewKeyAction("Goto", p.gotoCmd, true),
-// ui.KeyShiftR: ui.NewKeyAction("Sort Resource", p.GetTable().SortColCmd("RESOURCE", true), false),
-// ui.KeyShiftS: ui.NewKeyAction("Sort Score", p.GetTable().SortColCmd("SCORE%", true), false),
-// ui.KeyShiftO: ui.NewKeyAction("Sort OK", p.GetTable().SortColCmd("OK", true), false),
-// ui.KeyShiftI: ui.NewKeyAction("Sort Info", p.GetTable().SortColCmd("INFO", true), false),
-// ui.KeyShiftW: ui.NewKeyAction("Sort Warning", p.GetTable().SortColCmd("WARNING", true), false),
-// ui.KeyShiftE: ui.NewKeyAction("Sort Error", p.GetTable().SortColCmd("ERROR", true), false),
-// })
-// }
-
-// func (p *Popeye) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
-// path := p.GetTable().GetSelectedItem()
-// if path == "" {
-// return evt
-// }
-// v := NewSanitizer(client.NewGVR("sanitizer"))
-// v.SetContextFn(sanitizerCtx(path))
-// if err := p.App().inject(v, false); err != nil {
-// p.App().Flash().Err(err)
-// }
-
-// return nil
-// }
-
-// func sanitizerCtx(path string) ContextFunc {
-// return func(ctx context.Context) context.Context {
-// ctx = context.WithValue(ctx, internal.KeyPath, path)
-// return ctx
-// }
-// }
-
-// // Helpers...
-
-// func grade(score int) string {
-// switch {
-// case score >= 90:
-// return "A"
-// case score >= 80:
-// return "B"
-// case score >= 70:
-// return "C"
-// case score >= 60:
-// return "D"
-// case score >= 50:
-// return "E"
-// default:
-// return "F"
-// }
-// }
diff --git a/internal/view/priorityclass.go b/internal/view/priorityclass.go
index 7f9ef909..dec0b0a2 100644
--- a/internal/view/priorityclass.go
+++ b/internal/view/priorityclass.go
@@ -5,7 +5,6 @@ package view
import (
"github.com/derailed/k9s/internal/client"
- "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2"
)
@@ -16,7 +15,7 @@ type PriorityClass struct {
}
// NewPriorityClass returns a new viewer.
-func NewPriorityClass(gvr client.GVR) ResourceViewer {
+func NewPriorityClass(gvr *client.GVR) ResourceViewer {
s := PriorityClass{
ResourceViewer: NewBrowser(gvr),
}
@@ -30,5 +29,5 @@ func (s *PriorityClass) bindKeys(aa *ui.KeyActions) {
}
func (s *PriorityClass) refCmd(evt *tcell.EventKey) *tcell.EventKey {
- return scanRefs(evt, s.App(), s.GetTable(), dao.PcGVR)
+ return scanRefs(evt, s.App(), s.GetTable(), client.PcGVR)
}
diff --git a/internal/view/priorityclass_test.go b/internal/view/priorityclass_test.go
index 60cef2ab..0819ce67 100644
--- a/internal/view/priorityclass_test.go
+++ b/internal/view/priorityclass_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPriorityClassNew(t *testing.T) {
- s := view.NewPriorityClass(client.NewGVR("scheduling.k8s.io/v1/priorityclasses"))
+ s := view.NewPriorityClass(client.PcGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "PriorityClass", s.Name())
- assert.Equal(t, 6, len(s.Hints()))
+ assert.Len(t, s.Hints(), 6)
}
diff --git a/internal/view/pulse.go b/internal/view/pulse.go
index 18500987..81c140ad 100644
--- a/internal/view/pulse.go
+++ b/internal/view/pulse.go
@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"image"
+ "strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
@@ -61,7 +62,7 @@ type Pulse struct {
*tview.Grid
app *App
- gvr client.GVR
+ gvr *client.GVR
model *model.Pulse
cancelFn context.CancelFunc
actions *ui.KeyActions
@@ -69,7 +70,7 @@ type Pulse struct {
}
// NewPulse returns a new alias view.
-func NewPulse(gvr client.GVR) ResourceViewer {
+func NewPulse(gvr *client.GVR) ResourceViewer {
return &Pulse{
Grid: tview.NewGrid(),
model: model.NewPulse(gvr.String()),
@@ -77,9 +78,9 @@ func NewPulse(gvr client.GVR) ResourceViewer {
}
}
-func (p *Pulse) SetCommand(*cmd.Interpreter) {}
-func (p *Pulse) SetFilter(string) {}
-func (p *Pulse) SetLabelFilter(map[string]string) {}
+func (*Pulse) SetCommand(*cmd.Interpreter) {}
+func (*Pulse) SetFilter(string) {}
+func (*Pulse) SetLabelFilter(map[string]string) {}
// Init initializes the view.
func (p *Pulse) Init(ctx context.Context) error {
@@ -93,19 +94,19 @@ func (p *Pulse) Init(ctx context.Context) error {
}
p.charts = []Graphable{
- p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 2, Y: 2}, "apps/v1/deployments"),
- p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 2, Y: 2}, "apps/v1/replicasets"),
- p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 2, Y: 2}, "apps/v1/statefulsets"),
- p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 2, Y: 2}, "apps/v1/daemonsets"),
- p.makeSP(image.Point{X: 2, Y: 0}, image.Point{X: 3, Y: 2}, "v1/pods"),
- p.makeSP(image.Point{X: 2, Y: 2}, image.Point{X: 3, Y: 2}, "v1/events"),
- p.makeSP(image.Point{X: 2, Y: 4}, image.Point{X: 3, Y: 2}, "batch/v1/jobs"),
- p.makeSP(image.Point{X: 2, Y: 6}, image.Point{X: 3, Y: 2}, "v1/persistentvolumes"),
+ p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 2, Y: 2}, client.DpGVR),
+ p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 2, Y: 2}, client.RsGVR),
+ p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 2, Y: 2}, client.StsGVR),
+ p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 2, Y: 2}, client.DsGVR),
+ p.makeSP(image.Point{X: 2, Y: 0}, image.Point{X: 3, Y: 2}, client.PodGVR),
+ p.makeSP(image.Point{X: 2, Y: 2}, image.Point{X: 3, Y: 2}, client.EvGVR),
+ p.makeSP(image.Point{X: 2, Y: 4}, image.Point{X: 3, Y: 2}, client.JobGVR),
+ p.makeSP(image.Point{X: 2, Y: 6}, image.Point{X: 3, Y: 2}, client.PvGVR),
}
if p.app.Conn().HasMetrics() {
p.charts = append(p.charts,
- p.makeSP(image.Point{X: 5, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"),
- p.makeSP(image.Point{X: 5, Y: 4}, image.Point{X: 2, Y: 4}, "mem"),
+ p.makeSP(image.Point{X: 5, Y: 0}, image.Point{X: 2, Y: 4}, client.CpuGVR),
+ p.makeSP(image.Point{X: 5, Y: 4}, image.Point{X: 2, Y: 4}, client.MemGVR),
)
}
p.bindKeys()
@@ -166,24 +167,23 @@ func (p *Pulse) PulseChanged(c *health.Check) {
nn[1] = "gray"
}
- gvr := client.NewGVR(c.GVR)
switch c.GVR {
- case "cpu":
+ case client.CpuGVR:
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
v.SetLegend(fmt.Sprintf(cpuFmt,
- cases.Title(language.Und, cases.NoLower).String(gvr.R()),
- p.app.Config.K9s.Thresholds.SeverityColor("cpu", perc),
+ cases.Title(language.Und, cases.NoLower).String(c.GVR.R()),
+ p.app.Config.K9s.Thresholds.SeverityColor(config.CPU, perc),
render.PrintPerc(perc),
nn[0],
render.AsThousands(c.Tally(health.S1)),
nn[1],
render.AsThousands(c.Tally(health.S2)),
))
- case "mem":
+ case client.MemGVR:
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
v.SetLegend(fmt.Sprintf(memFmt,
- cases.Title(language.Und, cases.NoLower).String(gvr.R()),
- p.app.Config.K9s.Thresholds.SeverityColor("memory", perc),
+ cases.Title(language.Und, cases.NoLower).String(c.GVR.R()),
+ p.app.Config.K9s.Thresholds.SeverityColor(config.MEM, perc),
render.PrintPerc(perc),
nn[0],
render.AsThousands(c.Tally(health.S1)),
@@ -192,7 +192,7 @@ func (p *Pulse) PulseChanged(c *health.Check) {
))
default:
v.SetLegend(fmt.Sprintf(genFmat,
- cases.Title(language.Und, cases.NoLower).String(gvr.R()),
+ cases.Title(language.Und, cases.NoLower).String(c.GVR.R()),
nn[0],
c.Tally(health.S1),
nn[1],
@@ -215,7 +215,8 @@ func (p *Pulse) bindKeys() {
}))
for i, v := range p.charts {
- t := cases.Title(language.Und, cases.NoLower).String(client.NewGVR(v.ID()).R())
+ tt := strings.Split(v.ID(), "/")
+ t := cases.Title(language.Und, cases.NoLower).String(tt[len(tt)-1])
p.actions.Add(ui.NumKeys[i], ui.NewKeyAction(t, p.sparkFocusCmd(i), true))
}
}
@@ -255,15 +256,15 @@ func (p *Pulse) Stop() {
}
// Refresh updates the view.
-func (p *Pulse) Refresh() {}
+func (*Pulse) Refresh() {}
// GVR returns a resource descriptor.
-func (p *Pulse) GVR() client.GVR {
+func (p *Pulse) GVR() *client.GVR {
return p.gvr
}
// Name returns the component name.
-func (p *Pulse) Name() string {
+func (*Pulse) Name() string {
return pulseTitle
}
@@ -273,19 +274,19 @@ func (p *Pulse) App() *App {
}
// SetInstance sets specific resource instance.
-func (p *Pulse) SetInstance(string) {}
+func (*Pulse) SetInstance(string) {}
// SetEnvFn sets the custom environment function.
-func (p *Pulse) SetEnvFn(EnvFunc) {}
+func (*Pulse) SetEnvFn(EnvFunc) {}
// AddBindKeysFn sets up extra key bindings.
-func (p *Pulse) AddBindKeysFn(BindKeysFunc) {}
+func (*Pulse) AddBindKeysFn(BindKeysFunc) {}
// SetContextFn sets custom context.
-func (p *Pulse) SetContextFn(ContextFunc) {}
+func (*Pulse) SetContextFn(ContextFunc) {}
// GetTable return the view table if any.
-func (p *Pulse) GetTable() *Table {
+func (*Pulse) GetTable() *Table {
return nil
}
@@ -300,26 +301,26 @@ func (p *Pulse) Hints() model.MenuHints {
}
// ExtraHints returns additional hints.
-func (p *Pulse) ExtraHints() map[string]string {
+func (*Pulse) ExtraHints() map[string]string {
return nil
}
func (p *Pulse) sparkFocusCmd(i int) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
p.app.SetFocus(p.charts[i])
return nil
}
}
-func (p *Pulse) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (p *Pulse) enterCmd(*tcell.EventKey) *tcell.EventKey {
v := p.App().GetFocus()
s, ok := v.(Graphable)
if !ok {
return nil
}
- res := client.NewGVR(s.ID()).R()
- if res == "cpu" || res == "mem" {
- res = "pod"
+ res := s.ID()
+ if res == client.CpuGVR.String() || res == client.MemGVR.String() {
+ res = client.PodGVR.String()
}
p.App().gotoResource(res+" all", "", false, true)
@@ -327,7 +328,7 @@ func (p *Pulse) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (p *Pulse) nextFocusCmd(direction int) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
v := p.app.GetFocus()
index := findIndex(p.charts, v)
p.GetItem(index).Focus = false
@@ -340,16 +341,16 @@ func (p *Pulse) nextFocusCmd(direction int) func(evt *tcell.EventKey) *tcell.Eve
}
}
-func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.SparkLine {
- s := tchart.NewSparkLine(gvr)
+func (p *Pulse) makeSP(loc, span image.Point, gvr *client.GVR) *tchart.SparkLine {
+ s := tchart.NewSparkLine(gvr.String())
s.SetBackgroundColor(p.app.Styles.Charts().BgColor.Color())
s.SetBorderPadding(0, 1, 0, 1)
- if cc, ok := p.app.Styles.Charts().ResourceColors[gvr]; ok {
+ if cc, ok := p.app.Styles.Charts().ResourceColors[gvr.String()]; ok {
s.SetSeriesColors(cc.Colors()...)
} else {
s.SetSeriesColors(p.app.Styles.Charts().DefaultChartColors.Colors()...)
}
- s.SetLegend(fmt.Sprintf(" %s ", cases.Title(language.Und, cases.NoLower).String(client.NewGVR(gvr).R())))
+ s.SetLegend(fmt.Sprintf(" %s ", cases.Title(language.Und, cases.NoLower).String(gvr.R())))
s.SetInputCapture(p.keyboard)
s.SetMultiSeries(true)
p.AddItem(s, loc.X, loc.Y, span.X, span.Y, 0, 0, true)
@@ -357,17 +358,15 @@ func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.Sp
return s
}
-func (p *Pulse) makeGA(loc image.Point, span image.Point, gvr string) *tchart.Gauge {
- g := tchart.NewGauge(gvr)
- // g.SetResolution(3)
+func (p *Pulse) makeGA(loc, span image.Point, gvr *client.GVR) *tchart.Gauge {
+ g := tchart.NewGauge(gvr.String())
g.SetBackgroundColor(p.app.Styles.Charts().BgColor.Color())
- // g.SetBorderPadding(0, 1, 0, 1)
- if cc, ok := p.app.Styles.Charts().ResourceColors[gvr]; ok {
+ if cc, ok := p.app.Styles.Charts().ResourceColors[gvr.String()]; ok {
g.SetSeriesColors(cc.Colors()...)
} else {
g.SetSeriesColors(p.app.Styles.Charts().DefaultDialColors.Colors()...)
}
- g.SetLegend(fmt.Sprintf(" %s ", cases.Title(language.Und, cases.NoLower).String(client.NewGVR(gvr).R())))
+ g.SetLegend(fmt.Sprintf(" %s ", cases.Title(language.Und, cases.NoLower).String(gvr.R())))
g.SetInputCapture(p.keyboard)
p.AddItem(g, loc.X, loc.Y, span.X, span.Y, 0, 0, true)
@@ -398,9 +397,9 @@ func findIndex(pp []Graphable, p tview.Primitive) int {
return 0
}
-func findIndexGVR(pp []Graphable, gvr string) (int, bool) {
+func findIndexGVR(pp []Graphable, gvr *client.GVR) (int, bool) {
for i, v := range pp {
- if v.ID() == gvr {
+ if v.ID() == gvr.String() {
return i, true
}
}
diff --git a/internal/view/pvc.go b/internal/view/pvc.go
index b071ca4c..63a73864 100644
--- a/internal/view/pvc.go
+++ b/internal/view/pvc.go
@@ -5,7 +5,6 @@ package view
import (
"github.com/derailed/k9s/internal/client"
- "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2"
)
@@ -16,7 +15,7 @@ type PersistentVolumeClaim struct {
}
// NewPersistentVolumeClaim returns a new viewer.
-func NewPersistentVolumeClaim(gvr client.GVR) ResourceViewer {
+func NewPersistentVolumeClaim(gvr *client.GVR) ResourceViewer {
v := PersistentVolumeClaim{
ResourceViewer: NewOwnerExtender(NewBrowser(gvr)),
}
@@ -36,5 +35,5 @@ func (p *PersistentVolumeClaim) bindKeys(aa *ui.KeyActions) {
}
func (p *PersistentVolumeClaim) refCmd(evt *tcell.EventKey) *tcell.EventKey {
- return scanRefs(evt, p.App(), p.GetTable(), dao.PvcGVR)
+ return scanRefs(evt, p.App(), p.GetTable(), client.PvcGVR)
}
diff --git a/internal/view/pvc_test.go b/internal/view/pvc_test.go
index 7434729b..bbd7112e 100644
--- a/internal/view/pvc_test.go
+++ b/internal/view/pvc_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPVCNew(t *testing.T) {
- v := view.NewPersistentVolumeClaim(client.NewGVR("v1/persistentvolumeclaims"))
+ v := view.NewPersistentVolumeClaim(client.PvcGVR)
- assert.Nil(t, v.Init(makeCtx()))
+ require.NoError(t, v.Init(makeCtx()))
assert.Equal(t, "PersistentVolumeClaims", v.Name())
- assert.Equal(t, 11, len(v.Hints()))
+ assert.Len(t, v.Hints(), 11)
}
diff --git a/internal/view/rbac.go b/internal/view/rbac.go
index 55831228..62f6ecdb 100644
--- a/internal/view/rbac.go
+++ b/internal/view/rbac.go
@@ -18,7 +18,7 @@ type Rbac struct {
}
// NewRbac returns a new viewer.
-func NewRbac(gvr client.GVR) ResourceViewer {
+func NewRbac(gvr *client.GVR) ResourceViewer {
r := Rbac{
ResourceViewer: NewBrowser(gvr),
}
@@ -34,8 +34,8 @@ func (r *Rbac) bindKeys(aa *ui.KeyActions) {
aa.Add(ui.KeyShiftA, ui.NewKeyAction("Sort API-Group", r.GetTable().SortColCmd("API-GROUP", true), false))
}
-func showRules(app *App, _ ui.Tabular, gvr client.GVR, path string) {
- v := NewRbac(client.NewGVR("rbac"))
+func showRules(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
+ v := NewRbac(client.RbacGVR)
v.SetContextFn(rbacCtx(gvr, path))
if err := app.inject(v, false); err != nil {
@@ -43,11 +43,11 @@ func showRules(app *App, _ ui.Tabular, gvr client.GVR, path string) {
}
}
-func rbacCtx(gvr client.GVR, path string) ContextFunc {
+func rbacCtx(gvr *client.GVR, path string) ContextFunc {
return func(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, path)
return context.WithValue(ctx, internal.KeyGVR, gvr)
}
}
-func blankEnterFn(_ *App, _ ui.Tabular, _ client.GVR, _ string) {}
+func blankEnterFn(_ *App, _ ui.Tabular, _ *client.GVR, _ string) {}
diff --git a/internal/view/rbac_test.go b/internal/view/rbac_test.go
index 5d72d358..169ec0c6 100644
--- a/internal/view/rbac_test.go
+++ b/internal/view/rbac_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRbacNew(t *testing.T) {
- v := view.NewRbac(client.NewGVR("rbac"))
+ v := view.NewRbac(client.RbacGVR)
- assert.Nil(t, v.Init(makeCtx()))
+ require.NoError(t, v.Init(makeCtx()))
assert.Equal(t, "Rbac", v.Name())
- assert.Equal(t, 5, len(v.Hints()))
+ assert.Len(t, v.Hints(), 5)
}
diff --git a/internal/view/reference.go b/internal/view/reference.go
index d3251e47..da248931 100644
--- a/internal/view/reference.go
+++ b/internal/view/reference.go
@@ -17,7 +17,7 @@ type Reference struct {
}
// NewReference returns a new alias view.
-func NewReference(gvr client.GVR) ResourceViewer {
+func NewReference(gvr *client.GVR) ResourceViewer {
r := Reference{
ResourceViewer: NewBrowser(gvr),
}
diff --git a/internal/view/reference_test.go b/internal/view/reference_test.go
index 0e3578f7..359e0037 100644
--- a/internal/view/reference_test.go
+++ b/internal/view/reference_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestReferenceNew(t *testing.T) {
- s := view.NewReference(client.NewGVR("references"))
+ s := view.NewReference(client.RefGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "References", s.Name())
- assert.Equal(t, 4, len(s.Hints()))
+ assert.Len(t, s.Hints(), 4)
}
diff --git a/internal/view/registrar.go b/internal/view/registrar.go
index e40e627a..655a4db6 100644
--- a/internal/view/registrar.go
+++ b/internal/view/registrar.go
@@ -21,133 +21,127 @@ func loadCustomViewers() MetaViewers {
}
func helmViewers(vv MetaViewers) {
- vv[client.NewGVR("helm")] = MetaViewer{
+ vv[client.HmGVR] = MetaViewer{
viewerFn: NewHelmChart,
}
}
func coreViewers(vv MetaViewers) {
- vv[client.NewGVR("v1/namespaces")] = MetaViewer{
+ vv[client.NsGVR] = MetaViewer{
viewerFn: NewNamespace,
}
- vv[client.NewGVR("v1/events")] = MetaViewer{
+ vv[client.EvGVR] = MetaViewer{
viewerFn: NewEvent,
}
- vv[client.NewGVR("v1/pods")] = MetaViewer{
+ vv[client.PodGVR] = MetaViewer{
viewerFn: NewPod,
}
- vv[client.NewGVR("v1/services")] = MetaViewer{
+ vv[client.SvcGVR] = MetaViewer{
viewerFn: NewService,
}
- vv[client.NewGVR("v1/nodes")] = MetaViewer{
+ vv[client.NodeGVR] = MetaViewer{
viewerFn: NewNode,
}
- vv[client.NewGVR("v1/secrets")] = MetaViewer{
+ vv[client.SecGVR] = MetaViewer{
viewerFn: NewSecret,
}
- vv[client.NewGVR("scheduling.k8s.io/v1/priorityclasses")] = MetaViewer{
+ vv[client.PcGVR] = MetaViewer{
viewerFn: NewPriorityClass,
}
- vv[client.NewGVR("v1/configmaps")] = MetaViewer{
+ vv[client.CmGVR] = MetaViewer{
viewerFn: NewConfigMap,
}
- vv[client.NewGVR("v1/serviceaccounts")] = MetaViewer{
+ vv[client.SaGVR] = MetaViewer{
viewerFn: NewServiceAccount,
}
- vv[client.NewGVR("v1/persistentvolumeclaims")] = MetaViewer{
+ vv[client.PvcGVR] = MetaViewer{
viewerFn: NewPersistentVolumeClaim,
}
}
func miscViewers(vv MetaViewers) {
- vv[client.NewGVR("workloads")] = MetaViewer{
+ vv[client.WkGVR] = MetaViewer{
viewerFn: NewWorkload,
}
- vv[client.NewGVR("contexts")] = MetaViewer{
+ vv[client.CtGVR] = MetaViewer{
viewerFn: NewContext,
}
- vv[client.NewGVR("containers")] = MetaViewer{
+ vv[client.CoGVR] = MetaViewer{
viewerFn: NewContainer,
}
- vv[client.NewGVR("scans")] = MetaViewer{
+ vv[client.ScGVR] = MetaViewer{
viewerFn: NewImageScan,
}
- vv[client.NewGVR("portforwards")] = MetaViewer{
+ vv[client.PfGVR] = MetaViewer{
viewerFn: NewPortForward,
}
- vv[client.NewGVR("screendumps")] = MetaViewer{
+ vv[client.SdGVR] = MetaViewer{
viewerFn: NewScreenDump,
}
- vv[client.NewGVR("benchmarks")] = MetaViewer{
+ vv[client.BeGVR] = MetaViewer{
viewerFn: NewBenchmark,
}
- vv[client.NewGVR("aliases")] = MetaViewer{
+ vv[client.AliGVR] = MetaViewer{
viewerFn: NewAlias,
}
- vv[client.NewGVR("references")] = MetaViewer{
+ vv[client.RefGVR] = MetaViewer{
viewerFn: NewReference,
}
- vv[client.NewGVR("pulses")] = MetaViewer{
+ vv[client.PuGVR] = MetaViewer{
viewerFn: NewPulse,
}
}
func appsViewers(vv MetaViewers) {
- vv[client.NewGVR("apps/v1/deployments")] = MetaViewer{
+ vv[client.DpGVR] = MetaViewer{
viewerFn: NewDeploy,
}
- vv[client.NewGVR("apps/v1/replicasets")] = MetaViewer{
+ vv[client.RsGVR] = MetaViewer{
viewerFn: NewReplicaSet,
}
- vv[client.NewGVR("apps/v1/statefulsets")] = MetaViewer{
+ vv[client.StsGVR] = MetaViewer{
viewerFn: NewStatefulSet,
}
- vv[client.NewGVR("apps/v1/daemonsets")] = MetaViewer{
- viewerFn: NewDaemonSet,
- }
- vv[client.NewGVR("apps/v1/daemonsets")] = MetaViewer{
+ vv[client.DsGVR] = MetaViewer{
viewerFn: NewDaemonSet,
}
}
func rbacViewers(vv MetaViewers) {
- vv[client.NewGVR("rbac")] = MetaViewer{
+ vv[client.RbacGVR] = MetaViewer{
enterFn: showRules,
}
- vv[client.NewGVR("users")] = MetaViewer{
+ vv[client.UsrGVR] = MetaViewer{
viewerFn: NewUser,
}
- vv[client.NewGVR("groups")] = MetaViewer{
+ vv[client.GrpGVR] = MetaViewer{
viewerFn: NewGroup,
}
- vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterroles")] = MetaViewer{
+ vv[client.CrGVR] = MetaViewer{
enterFn: showRules,
}
- vv[client.NewGVR("rbac.authorization.k8s.io/v1/roles")] = MetaViewer{
+ vv[client.CrbGVR] = MetaViewer{
enterFn: showRules,
}
- vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterrolebindings")] = MetaViewer{
+ vv[client.RoGVR] = MetaViewer{
enterFn: showRules,
}
- vv[client.NewGVR("rbac.authorization.k8s.io/v1/rolebindings")] = MetaViewer{
+ vv[client.RobGVR] = MetaViewer{
enterFn: showRules,
}
}
func batchViewers(vv MetaViewers) {
- vv[client.NewGVR("batch/v1beta1/cronjobs")] = MetaViewer{
+ vv[client.CjGVR] = MetaViewer{
viewerFn: NewCronJob,
}
- vv[client.NewGVR("batch/v1/cronjobs")] = MetaViewer{
- viewerFn: NewCronJob,
- }
- vv[client.NewGVR("batch/v1/jobs")] = MetaViewer{
+ vv[client.JobGVR] = MetaViewer{
viewerFn: NewJob,
}
}
func crdViewers(vv MetaViewers) {
- vv[client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")] = MetaViewer{
+ vv[client.CrdGVR] = MetaViewer{
viewerFn: NewCRD,
}
}
diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go
index b18550c5..0ca40297 100644
--- a/internal/view/restart_extender.go
+++ b/internal/view/restart_extender.go
@@ -41,7 +41,7 @@ func (r *RestartExtender) bindKeys(aa *ui.KeyActions) {
))
}
-func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (r *RestartExtender) restartCmd(*tcell.EventKey) *tcell.EventKey {
paths := r.GetTable().GetSelectedItems()
if len(paths) == 0 || paths[0] == "" {
return nil
@@ -53,7 +53,8 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
if len(paths) > 1 {
msg = fmt.Sprintf("Restart %d %s?", len(paths), r.GVR().R())
}
- dialog.ShowConfirm(r.App().Styles.Dialog(), r.App().Content.Pages, "Confirm Restart", msg, func() {
+ 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 {
diff --git a/internal/view/rs.go b/internal/view/rs.go
index 1c552478..40eff8a8 100644
--- a/internal/view/rs.go
+++ b/internal/view/rs.go
@@ -19,7 +19,7 @@ type ReplicaSet struct {
}
// NewReplicaSet returns a new viewer.
-func NewReplicaSet(gvr client.GVR) ResourceViewer {
+func NewReplicaSet(gvr *client.GVR) ResourceViewer {
r := ReplicaSet{
ResourceViewer: NewOwnerExtender(
NewVulnerabilityExtender(
@@ -42,7 +42,7 @@ func (r *ReplicaSet) bindKeys(aa *ui.KeyActions) {
})
}
-func (r *ReplicaSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (*ReplicaSet) showPods(app *App, _ ui.Tabular, _ *client.GVR, path string) {
var drs dao.ReplicaSet
rs, err := drs.Load(app.factory, path)
if err != nil {
@@ -61,7 +61,8 @@ func (r *ReplicaSet) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey {
msg := fmt.Sprintf("Rollback %s %s?", r.GVR(), path)
- dialog.ShowConfirm(r.App().Styles.Dialog(), r.App().Content.Pages, "Rollback", msg, func() {
+ d := r.App().Styles.Dialog()
+ dialog.ShowConfirm(&d, r.App().Content.Pages, "Rollback", msg, func() {
r.App().Flash().Infof("Rolling back %s %s", r.GVR(), path)
var drs dao.ReplicaSet
drs.Init(r.App().factory, r.GVR())
diff --git a/internal/view/sa.go b/internal/view/sa.go
index 358a0240..68ea71be 100644
--- a/internal/view/sa.go
+++ b/internal/view/sa.go
@@ -19,7 +19,7 @@ type ServiceAccount struct {
}
// NewServiceAccount returns a new viewer.
-func NewServiceAccount(gvr client.GVR) ResourceViewer {
+func NewServiceAccount(gvr *client.GVR) ResourceViewer {
s := ServiceAccount{
ResourceViewer: NewOwnerExtender(NewBrowser(gvr)),
}
@@ -36,12 +36,12 @@ func (s *ServiceAccount) bindKeys(aa *ui.KeyActions) {
})
}
-func (s *ServiceAccount) subjectCtx(ctx context.Context) context.Context {
+func (*ServiceAccount) subjectCtx(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeySubjectKind, sa)
}
func (s *ServiceAccount) refCmd(evt *tcell.EventKey) *tcell.EventKey {
- return scanSARefs(evt, s.App(), s.GetTable(), dao.SaGVR)
+ return scanSARefs(evt, s.App(), s.GetTable(), client.SaGVR)
}
func (s *ServiceAccount) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
@@ -56,7 +56,7 @@ func (s *ServiceAccount) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func scanSARefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.EventKey {
+func scanSARefs(evt *tcell.EventKey, a *App, t *Table, gvr *client.GVR) *tcell.EventKey {
path := t.GetSelectedItem()
if path == "" {
return evt
@@ -73,7 +73,7 @@ func scanSARefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.Ev
return nil
}
a.Flash().Infof("Viewing references for %s::%s", gvr, path)
- view := NewReference(client.NewGVR("references"))
+ view := NewReference(client.RefGVR)
view.SetContextFn(refContext(gvr, path, false))
if err := a.inject(view, false); err != nil {
a.Flash().Err(err)
diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go
index 008bc5bb..04ad9d54 100644
--- a/internal/view/scale_extender.go
+++ b/internal/view/scale_extender.go
@@ -54,7 +54,7 @@ func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) {
}
}
-func (s *ScaleExtender) scaleCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (s *ScaleExtender) scaleCmd(*tcell.EventKey) *tcell.EventKey {
paths := s.GetTable().GetSelectedItems()
if len(paths) == 0 {
return nil
@@ -137,7 +137,7 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) {
// read the replicas directly from the CRD.
if meta, _ := dao.MetaAccess.MetaFor(s.GVR()); dao.IsScalable(meta) {
replicas, err := s.replicasFromScaleSubresource(fqns[0])
- if err == nil && len(replicas) != 0 {
+ if err == nil && replicas != "" {
factor = replicas
}
}
@@ -163,7 +163,7 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) {
SetLabelColor(styles.LabelFgColor.Color()).
SetFieldTextColor(styles.FieldFgColor.Color())
- f.AddInputField("Replicas:", factor, 4, func(textToCheck string, lastChar rune) bool {
+ f.AddInputField("Replicas:", factor, 4, func(textToCheck string, _ rune) bool {
_, err := strconv.Atoi(textToCheck)
return err == nil
}, func(changed string) {
@@ -180,7 +180,7 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) {
ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
defer cancel()
for _, fqn := range fqns {
- if err := s.scale(ctx, fqn, count); err != nil {
+ if err := s.scale(ctx, fqn, int32(count)); err != nil {
slog.Error("Unable to scale resource", slogs.FQN, fqn)
s.App().Flash().Err(err)
return
@@ -195,14 +195,14 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) {
f.AddButton("Cancel", func() {
s.dismissDialog()
})
- for i := 0; i < 2; i++ {
+ for i := range 2 {
if b := f.GetButton(i); b != nil {
b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color())
b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
}
}
- for i := 0; i < f.GetButtonCount(); i++ {
+ for i := range f.GetButtonCount() {
f.GetButton(i).
SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
@@ -215,7 +215,7 @@ func (s *ScaleExtender) dismissDialog() {
s.App().Content.RemovePage(scaleDialogKey)
}
-func (s *ScaleExtender) scale(ctx context.Context, path string, replicas int) error {
+func (s *ScaleExtender) scale(ctx context.Context, path string, replicas int32) error {
res, err := dao.AccessorFor(s.App().factory, s.GVR())
if err != nil {
return err
@@ -225,5 +225,5 @@ func (s *ScaleExtender) scale(ctx context.Context, path string, replicas int) er
return fmt.Errorf("expecting a scalable resource for %q", s.GVR())
}
- return scaler.Scale(ctx, path, int32(replicas))
+ return scaler.Scale(ctx, path, replicas)
}
diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go
index c36b54a7..2d028ad2 100644
--- a/internal/view/screen_dump.go
+++ b/internal/view/screen_dump.go
@@ -21,7 +21,7 @@ type ScreenDump struct {
}
// NewScreenDump returns a new viewer.
-func NewScreenDump(gvr client.GVR) ResourceViewer {
+func NewScreenDump(gvr *client.GVR) ResourceViewer {
s := ScreenDump{
ResourceViewer: NewBrowser(gvr),
}
@@ -45,12 +45,12 @@ func (s *ScreenDump) dirContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyDir, dir)
}
-func (s *ScreenDump) edit(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (s *ScreenDump) edit(app *App, _ ui.Tabular, _ *client.GVR, path string) {
slog.Debug("ScreenDump selection", slogs.FQN, path)
s.Stop()
defer s.Start()
- if !edit(app, shellOpts{clear: true, args: []string{path}}) {
+ if !edit(app, &shellOpts{clear: true, args: []string{path}}) {
app.Flash().Errf("Failed to launch editor")
}
}
diff --git a/internal/view/screen_dump_test.go b/internal/view/screen_dump_test.go
index b812435b..96af8bb2 100644
--- a/internal/view/screen_dump_test.go
+++ b/internal/view/screen_dump_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestScreenDumpNew(t *testing.T) {
- po := view.NewScreenDump(client.NewGVR("screendumps"))
+ po := view.NewScreenDump(client.SdGVR)
- assert.Nil(t, po.Init(makeCtx()))
+ require.NoError(t, po.Init(makeCtx()))
assert.Equal(t, "ScreenDumps", po.Name())
- assert.Equal(t, 5, len(po.Hints()))
+ assert.Len(t, po.Hints(), 5)
}
diff --git a/internal/view/secret.go b/internal/view/secret.go
index 7f43edd5..ce3fa708 100644
--- a/internal/view/secret.go
+++ b/internal/view/secret.go
@@ -18,7 +18,7 @@ type Secret struct {
}
// NewSecret returns a new viewer.
-func NewSecret(gvr client.GVR) ResourceViewer {
+func NewSecret(gvr *client.GVR) ResourceViewer {
s := Secret{
ResourceViewer: NewOwnerExtender(NewBrowser(gvr)),
}
@@ -35,7 +35,7 @@ func (s *Secret) bindKeys(aa *ui.KeyActions) {
}
func (s *Secret) refCmd(evt *tcell.EventKey) *tcell.EventKey {
- return scanRefs(evt, s.App(), s.GetTable(), dao.SecGVR)
+ return scanRefs(evt, s.App(), s.GetTable(), client.SecGVR)
}
func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
@@ -44,7 +44,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
- o, err := s.App().factory.Get(s.GVR().String(), path, true, labels.Everything())
+ o, err := s.App().factory.Get(s.GVR(), path, true, labels.Everything())
if err != nil {
s.App().Flash().Err(err)
return nil
diff --git a/internal/view/secret_test.go b/internal/view/secret_test.go
index a02972af..7e28ab62 100644
--- a/internal/view/secret_test.go
+++ b/internal/view/secret_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestSecretNew(t *testing.T) {
- s := view.NewSecret(client.NewGVR("v1/secrets"))
+ s := view.NewSecret(client.SecGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "Secrets", s.Name())
- assert.Equal(t, 8, len(s.Hints()))
+ assert.Len(t, s.Hints(), 8)
}
diff --git a/internal/view/sts.go b/internal/view/sts.go
index fa25e4d4..16dafaa2 100644
--- a/internal/view/sts.go
+++ b/internal/view/sts.go
@@ -18,7 +18,7 @@ type StatefulSet struct {
}
// NewStatefulSet returns a new viewer.
-func NewStatefulSet(gvr client.GVR) ResourceViewer {
+func NewStatefulSet(gvr *client.GVR) ResourceViewer {
var s StatefulSet
s.ResourceViewer = NewPortForwardExtender(
NewVulnerabilityExtender(
@@ -49,14 +49,14 @@ func (s *StatefulSet) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err
}
- return podLogOptions(s.App(), path, prev, sts.ObjectMeta, sts.Spec.Template.Spec), nil
+ return podLogOptions(s.App(), path, prev, &sts.ObjectMeta, &sts.Spec.Template.Spec), nil
}
func (s *StatefulSet) bindKeys(aa *ui.KeyActions) {
aa.Add(ui.KeyShiftR, ui.NewKeyAction("Sort Ready", s.GetTable().SortColCmd(readyCol, true), false))
}
-func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ *client.GVR, path string) {
i, err := s.getInstance(path)
if err != nil {
app.Flash().Err(err)
diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go
index 44551945..119e8d02 100644
--- a/internal/view/sts_test.go
+++ b/internal/view/sts_test.go
@@ -9,12 +9,13 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestStatefulSetNew(t *testing.T) {
- s := view.NewStatefulSet(client.NewGVR("apps/v1/statefulsets"))
+ s := view.NewStatefulSet(client.StsGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "StatefulSets", s.Name())
- assert.Equal(t, 14, len(s.Hints()))
+ assert.Len(t, s.Hints(), 14)
}
diff --git a/internal/view/svc.go b/internal/view/svc.go
index c2f2459e..bb233e1f 100644
--- a/internal/view/svc.go
+++ b/internal/view/svc.go
@@ -33,7 +33,7 @@ type Service struct {
}
// NewService returns a new viewer.
-func NewService(gvr client.GVR) ResourceViewer {
+func NewService(gvr *client.GVR) ResourceViewer {
s := Service{
ResourceViewer: NewPortForwardExtender(
NewOwnerExtender(
@@ -56,7 +56,7 @@ func (s *Service) bindKeys(aa *ui.KeyActions) {
})
}
-func (s *Service) showPods(a *App, _ ui.Tabular, _ client.GVR, path string) {
+func (s *Service) showPods(a *App, _ ui.Tabular, _ *client.GVR, path string) {
var res dao.Service
res.Init(a.factory, s.GVR())
@@ -77,14 +77,14 @@ func (s *Service) showPods(a *App, _ ui.Tabular, _ client.GVR, path string) {
showPods(a, path, toLabelsStr(svc.Spec.Selector), "")
}
-func (s *Service) checkSvc(svc *v1.Service) error {
+func (*Service) checkSvc(svc *v1.Service) error {
if svc.Spec.Type != "NodePort" && svc.Spec.Type != "LoadBalancer" {
return errors.New("you must select a reachable service")
}
return nil
}
-func (s *Service) getExternalPort(svc *v1.Service) (string, error) {
+func (*Service) getExternalPort(svc *v1.Service) (string, error) {
if svc.Spec.Type == "LoadBalancer" {
return "", nil
}
@@ -140,7 +140,7 @@ func (s *Service) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
s.App().Flash().Err(err)
return nil
}
- if err := s.runBenchmark(port, cfg); err != nil {
+ if err := s.runBenchmark(port, &cfg); err != nil {
s.App().Flash().Errf("Benchmark failed %v", err)
s.App().ClearStatus(false)
s.bench = nil
@@ -150,7 +150,7 @@ func (s *Service) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
}
// BOZO!! Refactor used by forwards.
-func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error {
+func (s *Service) runBenchmark(port string, cfg *config.BenchConfig) error {
if cfg.HTTP.Host == "" {
return fmt.Errorf("invalid benchmark host %q", cfg.HTTP.Host)
}
@@ -209,7 +209,7 @@ func clearStatus(app *App) {
}
func fetchService(f dao.Factory, path string) (*v1.Service, error) {
- o, err := f.Get("v1/services", path, true, labels.Everything())
+ o, err := f.Get(client.SvcGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go
index 19978d8f..5e0f3ffc 100644
--- a/internal/view/svc_test.go
+++ b/internal/view/svc_test.go
@@ -10,17 +10,18 @@ import (
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func init() {
- dao.MetaAccess.RegisterMeta("dir", metav1.APIResource{
- Name: "dir",
+ dao.MetaAccess.RegisterMeta(client.DirGVR.String(), &metav1.APIResource{
+ Name: "dirs",
SingularName: "dir",
Kind: "Directory",
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/pods", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.PodGVR.String(), &metav1.APIResource{
Name: "pods",
SingularName: "pod",
Namespaced: true,
@@ -28,7 +29,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/namespaces", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.NsGVR.String(), &metav1.APIResource{
Name: "namespaces",
SingularName: "namespace",
Namespaced: true,
@@ -36,7 +37,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/services", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.SvcGVR.String(), &metav1.APIResource{
Name: "services",
SingularName: "service",
Namespaced: true,
@@ -44,7 +45,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/secrets", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.SecGVR.String(), &metav1.APIResource{
Name: "secrets",
SingularName: "secret",
Namespaced: true,
@@ -52,7 +53,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("scheduling.k8s.io/v1/priorityclasses", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.PcGVR.String(), &metav1.APIResource{
Name: "priorityclasses",
SingularName: "priorityclass",
Namespaced: false,
@@ -60,7 +61,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/configmaps", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.CmGVR.String(), &metav1.APIResource{
Name: "configmaps",
SingularName: "configmap",
Namespaced: true,
@@ -69,7 +70,7 @@ func init() {
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("references", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.RefGVR.String(), &metav1.APIResource{
Name: "references",
SingularName: "reference",
Namespaced: true,
@@ -77,7 +78,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("aliases", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.AliGVR.String(), &metav1.APIResource{
Name: "aliases",
SingularName: "alias",
Namespaced: true,
@@ -85,7 +86,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("containers", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.CoGVR.String(), &metav1.APIResource{
Name: "containers",
SingularName: "container",
Namespaced: true,
@@ -93,7 +94,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("contexts", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.CtGVR.String(), &metav1.APIResource{
Name: "contexts",
SingularName: "context",
Namespaced: true,
@@ -101,7 +102,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("subjects", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta("subjects", &metav1.APIResource{
Name: "subjects",
SingularName: "subject",
Namespaced: true,
@@ -109,7 +110,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("rbac", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.RbacGVR.String(), &metav1.APIResource{
Name: "rbacs",
SingularName: "rbac",
Namespaced: true,
@@ -117,7 +118,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("portforwards", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.PfGVR.String(), &metav1.APIResource{
Name: "portforwards",
SingularName: "portforward",
Namespaced: true,
@@ -126,7 +127,7 @@ func init() {
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("screendumps", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.SdGVR.String(), &metav1.APIResource{
Name: "screendumps",
SingularName: "screendump",
Namespaced: true,
@@ -134,7 +135,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("apps/v1/statefulsets", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.StsGVR.String(), &metav1.APIResource{
Name: "statefulsets",
SingularName: "statefulset",
Namespaced: true,
@@ -142,7 +143,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("apps/v1/daemonsets", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.DsGVR.String(), &metav1.APIResource{
Name: "daemonsets",
SingularName: "daemonset",
Namespaced: true,
@@ -150,7 +151,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("apps/v1/deployments", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.DpGVR.String(), &metav1.APIResource{
Name: "deployments",
SingularName: "deployment",
Namespaced: true,
@@ -158,7 +159,7 @@ func init() {
Verbs: []string{"get", "list", "watch", "delete"},
Categories: []string{"k9s"},
})
- dao.MetaAccess.RegisterMeta("v1/persistentvolumeclaims", metav1.APIResource{
+ dao.MetaAccess.RegisterMeta(client.PvcGVR.String(), &metav1.APIResource{
Name: "persistentvolumeclaims",
SingularName: "persistentvolumeclaim",
Namespaced: true,
@@ -169,9 +170,9 @@ func init() {
}
func TestServiceNew(t *testing.T) {
- s := view.NewService(client.NewGVR("v1/services"))
+ s := view.NewService(client.SvcGVR)
- assert.Nil(t, s.Init(makeCtx()))
+ require.NoError(t, s.Init(makeCtx()))
assert.Equal(t, "Services", s.Name())
- assert.Equal(t, 12, len(s.Hints()))
+ assert.Len(t, s.Hints(), 12)
}
diff --git a/internal/view/table.go b/internal/view/table.go
index f3e42679..d00c40bb 100644
--- a/internal/view/table.go
+++ b/internal/view/table.go
@@ -32,7 +32,7 @@ type Table struct {
}
// NewTable returns a new viewer.
-func NewTable(gvr client.GVR) *Table {
+func NewTable(gvr *client.GVR) *Table {
t := Table{
Table: ui.NewTable(gvr),
}
@@ -73,7 +73,7 @@ func (t *Table) SetCommand(cmd *cmd.Interpreter) {
// HeaderIndex returns index of a given column or false if not found.
func (t *Table) HeaderIndex(colName string) (int, bool) {
- for i := 0; i < t.GetColumnCount(); i++ {
+ for i := range t.GetColumnCount() {
h := t.GetCell(0, i)
if h == nil {
continue
@@ -168,7 +168,7 @@ func (t *Table) SetEnterFn(f EnterFunc) {
}
// SetExtraActionsFn specifies custom keyboard behavior.
-func (t *Table) SetExtraActionsFn(BoostActionsFunc) {}
+func (*Table) SetExtraActionsFn(BoostActionsFunc) {}
// BufferCompleted indicates input was accepted.
func (t *Table) BufferCompleted(text, _ string) {
@@ -178,7 +178,7 @@ func (t *Table) BufferCompleted(text, _ string) {
}
// BufferChanged indicates the buffer was changed.
-func (t *Table) BufferChanged(_, _ string) {}
+func (*Table) BufferChanged(_, _ string) {}
// BufferActive indicates the buff activity changed.
func (t *Table) BufferActive(state bool, k model.BufferKind) {
@@ -188,7 +188,7 @@ func (t *Table) BufferActive(state bool, k model.BufferKind) {
}
}
-func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) saveCmd(*tcell.EventKey) *tcell.EventKey {
if path, err := saveTable(t.app.Config.K9s.ContextScreenDumpDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil {
t.app.Flash().Err(err)
} else {
@@ -213,12 +213,12 @@ func (t *Table) bindKeys() {
})
}
-func (t *Table) toggleFaultCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) toggleFaultCmd(*tcell.EventKey) *tcell.EventKey {
t.ToggleToast()
return nil
}
-func (t *Table) toggleWideCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) toggleWideCmd(*tcell.EventKey) *tcell.EventKey {
t.ToggleWide()
return nil
}
@@ -253,21 +253,21 @@ func (t *Table) cpNsCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (t *Table) markCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) markCmd(*tcell.EventKey) *tcell.EventKey {
t.ToggleMark()
t.Refresh()
return nil
}
-func (t *Table) markSpanCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) markSpanCmd(*tcell.EventKey) *tcell.EventKey {
t.SpanMark()
t.Refresh()
return nil
}
-func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (t *Table) clearMarksCmd(*tcell.EventKey) *tcell.EventKey {
t.ClearMarks()
t.Refresh()
diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go
index ca4154a8..31efe246 100644
--- a/internal/view/table_helper.go
+++ b/internal/view/table_helper.go
@@ -22,7 +22,7 @@ import (
func computeFilename(dumpPath, ns, title, path string) (string, error) {
now := time.Now().UnixNano()
- dir := filepath.Join(dumpPath)
+ dir := dumpPath
if err := ensureDir(dir); err != nil {
return "", err
}
diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go
index e4a3b1fa..29966e89 100644
--- a/internal/view/table_int_test.go
+++ b/internal/view/table_int_test.go
@@ -22,27 +22,28 @@ import (
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestTableSave(t *testing.T) {
v := NewTable(client.NewGVR("test"))
- assert.NoError(t, v.Init(makeContext()))
+ require.NoError(t, v.Init(makeContext()))
v.SetTitle("k9s-test")
- assert.NoError(t, ensureDumpDir("/tmp/test-dumps"))
+ require.NoError(t, ensureDumpDir("/tmp/test-dumps"))
dir := v.app.Config.K9s.ContextScreenDumpDir()
c1, _ := os.ReadDir(dir)
v.saveCmd(nil)
c2, _ := os.ReadDir(dir)
- assert.Equal(t, len(c2), len(c1)+1)
+ assert.Len(t, c2, len(c1)+1)
}
func TestTableNew(t *testing.T) {
v := NewTable(client.NewGVR("test"))
- assert.NoError(t, v.Init(makeContext()))
+ require.NoError(t, v.Init(makeContext()))
data := model1.NewTableDataWithRows(
client.NewGVR("test"),
@@ -73,7 +74,7 @@ func TestTableNew(t *testing.T) {
func TestTableViewFilter(t *testing.T) {
v := NewTable(client.NewGVR("test"))
- assert.NoError(t, v.Init(makeContext()))
+ require.NoError(t, v.Init(makeContext()))
v.SetModel(&mockTableModel{})
v.Refresh()
@@ -85,7 +86,7 @@ func TestTableViewFilter(t *testing.T) {
func TestTableViewSort(t *testing.T) {
v := NewTable(client.NewGVR("test"))
- assert.NoError(t, v.Init(makeContext()))
+ require.NoError(t, v.Init(makeContext()))
v.SetModel(new(mockTableModel))
uu := map[string]struct {
@@ -113,12 +114,12 @@ func TestTableViewSort(t *testing.T) {
for k := range uu {
u := uu[k]
v.SortColCmd(u.sortCol, true)(nil)
- assert.Equal(t, len(u.sorted)+1, v.GetRowCount())
+ assert.Len(t, u.sorted, v.GetRowCount()-1)
for i, s := range u.sorted {
assert.Equal(t, s, v.GetCell(i+1, 0).Text)
}
v.SortInvertCmd(nil)
- assert.Equal(t, len(u.reversed)+1, v.GetRowCount())
+ assert.Len(t, u.reversed, v.GetRowCount()-1)
for i, s := range u.reversed {
assert.Equal(t, s, v.GetCell(i+1, 0).Text)
}
@@ -132,40 +133,36 @@ type mockTableModel struct{}
var _ ui.Tabular = (*mockTableModel)(nil)
-func (t *mockTableModel) SetViewSetting(context.Context, *config.ViewSetting) {}
-func (t *mockTableModel) SetInstance(string) {}
-func (t *mockTableModel) SetLabelFilter(string) {}
-func (t *mockTableModel) GetLabelFilter() string { return "" }
-func (t *mockTableModel) Empty() bool { return false }
-func (t *mockTableModel) RowCount() int { return 1 }
-func (t *mockTableModel) HasMetrics() bool { return true }
-func (t *mockTableModel) Peek() *model1.TableData { return makeTableData() }
-func (t *mockTableModel) Refresh(context.Context) error { return nil }
-func (t *mockTableModel) ClusterWide() bool { return false }
-func (t *mockTableModel) GetNamespace() string { return "blee" }
-func (t *mockTableModel) SetNamespace(string) {}
-func (t *mockTableModel) ToggleToast() {}
-func (t *mockTableModel) AddListener(model.TableListener) {}
-func (t *mockTableModel) RemoveListener(model.TableListener) {}
-func (t *mockTableModel) Watch(context.Context) error { return nil }
-func (t *mockTableModel) Get(context.Context, string) (runtime.Object, error) {
+func (*mockTableModel) SetViewSetting(context.Context, *config.ViewSetting) {}
+func (*mockTableModel) SetInstance(string) {}
+func (*mockTableModel) SetLabelFilter(string) {}
+func (*mockTableModel) GetLabelFilter() string { return "" }
+func (*mockTableModel) Empty() bool { return false }
+func (*mockTableModel) RowCount() int { return 1 }
+func (*mockTableModel) HasMetrics() bool { return true }
+func (*mockTableModel) Peek() *model1.TableData { return makeTableData() }
+func (*mockTableModel) Refresh(context.Context) error { return nil }
+func (*mockTableModel) ClusterWide() bool { return false }
+func (*mockTableModel) GetNamespace() string { return "blee" }
+func (*mockTableModel) SetNamespace(string) {}
+func (*mockTableModel) ToggleToast() {}
+func (*mockTableModel) AddListener(model.TableListener) {}
+func (*mockTableModel) RemoveListener(model.TableListener) {}
+func (*mockTableModel) Watch(context.Context) error { return nil }
+func (*mockTableModel) Get(context.Context, string) (runtime.Object, error) {
return nil, nil
}
-
-func (t *mockTableModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
+func (*mockTableModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error {
return nil
}
-
-func (t *mockTableModel) Describe(context.Context, string) (string, error) {
+func (*mockTableModel) Describe(context.Context, string) (string, error) {
return "", nil
}
-
-func (t *mockTableModel) ToYAML(ctx context.Context, path string) (string, error) {
+func (*mockTableModel) ToYAML(context.Context, string) (string, error) {
return "", nil
}
-
-func (t *mockTableModel) InNamespace(string) bool { return true }
-func (t *mockTableModel) SetRefreshRate(time.Duration) {}
+func (*mockTableModel) InNamespace(string) bool { return true }
+func (*mockTableModel) SetRefreshRate(time.Duration) {}
func makeTableData() *model1.TableData {
return model1.NewTableDataWithRows(
diff --git a/internal/view/types.go b/internal/view/types.go
index 46485ba0..1320e46d 100644
--- a/internal/view/types.go
+++ b/internal/view/types.go
@@ -32,7 +32,7 @@ type (
BoostActionsFunc func(ui.KeyActions)
// EnterFunc represents an enter key action.
- EnterFunc func(app *App, model ui.Tabular, gvr client.GVR, path string)
+ EnterFunc func(app *App, model ui.Tabular, gvr *client.GVR, path string)
// LogOptionsFunc returns the active log options.
LogOptionsFunc func(bool) (*dao.LogOptions, error)
@@ -86,7 +86,7 @@ type ResourceViewer interface {
SetEnvFn(EnvFunc)
// GVR returns a resource descriptor.
- GVR() client.GVR
+ GVR() *client.GVR
// SetContextFn provision a custom context.
SetContextFn(ContextFunc)
@@ -127,7 +127,7 @@ type SubjectViewer interface {
}
// ViewerFunc returns a viewer matching a given gvr.
-type ViewerFunc func(client.GVR) ResourceViewer
+type ViewerFunc func(*client.GVR) ResourceViewer
// MetaViewer represents a registered meta viewer.
type MetaViewer struct {
@@ -136,4 +136,4 @@ type MetaViewer struct {
}
// MetaViewers represents a collection of meta viewers.
-type MetaViewers map[client.GVR]MetaViewer
+type MetaViewers map[*client.GVR]MetaViewer
diff --git a/internal/view/user.go b/internal/view/user.go
index 06850451..65000315 100644
--- a/internal/view/user.go
+++ b/internal/view/user.go
@@ -18,7 +18,7 @@ type User struct {
}
// NewUser returns a new subject viewer.
-func NewUser(gvr client.GVR) ResourceViewer {
+func NewUser(gvr *client.GVR) ResourceViewer {
u := User{ResourceViewer: NewBrowser(gvr)}
u.AddBindKeysFn(u.bindKeys)
u.SetContextFn(u.subjectCtx)
@@ -34,7 +34,7 @@ func (u *User) bindKeys(aa *ui.KeyActions) {
})
}
-func (u *User) subjectCtx(ctx context.Context) context.Context {
+func (*User) subjectCtx(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeySubjectKind, "User")
}
diff --git a/internal/view/value_extender.go b/internal/view/value_extender.go
index d772454b..950486b0 100644
--- a/internal/view/value_extender.go
+++ b/internal/view/value_extender.go
@@ -24,7 +24,7 @@ type ValueExtender struct {
func NewValueExtender(r ResourceViewer) ResourceViewer {
p := ValueExtender{ResourceViewer: r}
p.AddBindKeysFn(p.bindKeys)
- p.GetTable().SetEnterFn(func(app *App, model ui.Tabular, gvr client.GVR, path string) {
+ p.GetTable().SetEnterFn(func(*App, ui.Tabular, *client.GVR, string) {
p.valuesCmd(nil)
})
@@ -49,13 +49,13 @@ func (v *ValueExtender) defaultCtx() context.Context {
return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory)
}
-func showValues(ctx context.Context, app *App, path string, gvr client.GVR) {
+func showValues(ctx context.Context, app *App, path string, gvr *client.GVR) {
vm := model.NewValues(gvr, path)
if err := vm.Init(app.factory); err != nil {
app.Flash().Errf("Initializing the values model failed: %s", err)
}
- toggleValuesCmd := func(evt *tcell.EventKey) *tcell.EventKey {
+ toggleValuesCmd := func(*tcell.EventKey) *tcell.EventKey {
if err := vm.ToggleValues(); err != nil {
app.Flash().Errf("Values toggle failed: %s", err)
return nil
diff --git a/internal/view/vul_extender.go b/internal/view/vul_extender.go
index ebc373b6..fa6f3ca8 100644
--- a/internal/view/vul_extender.go
+++ b/internal/view/vul_extender.go
@@ -34,8 +34,8 @@ func (v *VulnerabilityExtender) bindKeys(aa *ui.KeyActions) {
}
}
-func (v *VulnerabilityExtender) showVulCmd(evt *tcell.EventKey) *tcell.EventKey {
- isv := NewImageScan(client.NewGVR("scans"))
+func (v *VulnerabilityExtender) showVulCmd(*tcell.EventKey) *tcell.EventKey {
+ isv := NewImageScan(client.ScGVR)
isv.SetContextFn(v.selContext)
if err := v.App().inject(isv, false); err != nil {
v.App().Flash().Err(err)
diff --git a/internal/view/workload.go b/internal/view/workload.go
index cf0beedd..72aaf727 100644
--- a/internal/view/workload.go
+++ b/internal/view/workload.go
@@ -26,7 +26,7 @@ type Workload struct {
}
// NewWorkload returns a new viewer.
-func NewWorkload(gvr client.GVR) ResourceViewer {
+func NewWorkload(gvr *client.GVR) ResourceViewer {
w := Workload{
ResourceViewer: NewBrowser(gvr),
}
@@ -67,17 +67,17 @@ func (w *Workload) bindKeys(aa *ui.KeyActions) {
})
}
-func parsePath(path string) (client.GVR, string, bool) {
+func parsePath(path string) (*client.GVR, string, bool) {
tt := strings.Split(path, "|")
if len(tt) != 3 {
slog.Error("Unable to parse workload path", slogs.Path, path)
- return client.NewGVR(""), client.FQN("", ""), false
+ return client.NoGVR, client.FQN("", ""), false
}
return client.NewGVR(tt[0]), client.FQN(tt[1], tt[2]), true
}
-func (w *Workload) showRes(app *App, _ ui.Tabular, _ client.GVR, path string) {
+func (*Workload) showRes(app *App, _ ui.Tabular, _ *client.GVR, path string) {
gvr, fqn, ok := parsePath(path)
if !ok {
app.Flash().Err(fmt.Errorf("unable to parse path: %q", path))
@@ -105,7 +105,7 @@ func (w *Workload) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (w *Workload) defaultContext(gvr client.GVR, fqn string) context.Context {
+func (w *Workload) defaultContext(gvr *client.GVR, fqn string) context.Context {
ctx := context.WithValue(context.Background(), internal.KeyFactory, w.App().factory)
ctx = context.WithValue(ctx, internal.KeyGVR, gvr)
if fqn != "" {
@@ -148,7 +148,8 @@ func (w *Workload) resourceDelete(selections []string, msg string) {
}
w.GetTable().Start()
}
- dialog.ShowDelete(w.App().Styles.Dialog(), w.App().Content.Pages, msg, okFn, func() {})
+ d := w.App().Styles.Dialog()
+ dialog.ShowDelete(&d, w.App().Content.Pages, msg, okFn, func() {})
}
func (w *Workload) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
diff --git a/internal/view/xray.go b/internal/view/xray.go
index f46edc06..c97c5733 100644
--- a/internal/view/xray.go
+++ b/internal/view/xray.go
@@ -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/util/sets"
)
const xrayTitle = "Xray"
@@ -39,15 +40,15 @@ type Xray struct {
*ui.Tree
app *App
- gvr client.GVR
- meta metav1.APIResource
+ gvr *client.GVR
+ meta *metav1.APIResource
model *model.Tree
cancelFn context.CancelFunc
envFn EnvFunc
}
// NewXray returns a new view.
-func NewXray(gvr client.GVR) ResourceViewer {
+func NewXray(gvr *client.GVR) ResourceViewer {
return &Xray{
gvr: gvr,
Tree: ui.NewTree(),
@@ -55,9 +56,9 @@ func NewXray(gvr client.GVR) ResourceViewer {
}
}
-func (x *Xray) SetCommand(*cmd.Interpreter) {}
-func (x *Xray) SetFilter(string) {}
-func (x *Xray) SetLabelFilter(map[string]string) {}
+func (*Xray) SetCommand(*cmd.Interpreter) {}
+func (*Xray) SetFilter(string) {}
+func (*Xray) SetLabelFilter(map[string]string) {}
// Init initializes the view.
func (x *Xray) Init(ctx context.Context) error {
@@ -117,7 +118,7 @@ func (x *Xray) ExtraHints() map[string]string {
}
// SetInstance sets specific resource instance.
-func (x *Xray) SetInstance(string) {}
+func (*Xray) SetInstance(string) {}
func (x *Xray) bindKeys() {
x.Actions().Bulk(ui.KeyMap{
@@ -158,7 +159,7 @@ func (x *Xray) refreshActions() {
gvr := spec.GVR()
var err error
- x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(gvr))
+ x.meta, err = dao.MetaAccess.MetaFor(gvr)
if err != nil {
slog.Warn("No meta found!",
slogs.GVR, gvr,
@@ -181,16 +182,16 @@ func (x *Xray) refreshActions() {
}
switch gvr {
- case "v1/namespaces":
+ case client.NsGVR:
x.Actions().Delete(tcell.KeyEnter)
- case "containers":
+ case client.CoGVR:
x.Actions().Delete(tcell.KeyEnter)
aa.Bulk(ui.KeyMap{
ui.KeyS: ui.NewKeyAction("Shell", x.shellCmd, true),
ui.KeyL: ui.NewKeyAction("Logs", x.logsCmd(false), true),
ui.KeyP: ui.NewKeyAction("Logs Previous", x.logsCmd(true), true),
})
- case "v1/pods":
+ case client.PodGVR:
aa.Bulk(ui.KeyMap{
ui.KeyS: ui.NewKeyAction("Shell", x.shellCmd, true),
ui.KeyA: ui.NewKeyAction("Attach", x.attachCmd, true),
@@ -248,7 +249,7 @@ func (x *Xray) k9sEnv() Env {
}
switch spec.GVR() {
- case "containers":
+ case client.CoGVR:
_, co := client.Namespaced(spec.Path())
env["CONTAINER"] = co
ns, n := client.Namespaced(*spec.ParentPath())
@@ -262,12 +263,12 @@ func (x *Xray) k9sEnv() Env {
}
// Aliases returns all available aliases.
-func (x *Xray) Aliases() map[string]struct{} {
- return aliasesFor(x.meta, x.app.command.AliasesFor(x.meta.Name))
+func (x *Xray) Aliases() sets.Set[string] {
+ return aliases(x.meta, x.app.command.AliasesFor(client.NewGVRFromMeta(x.meta)))
}
func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
- return func(evt *tcell.EventKey) *tcell.EventKey {
+ return func(*tcell.EventKey) *tcell.EventKey {
spec := x.selectedSpec()
if spec == nil {
return nil
@@ -282,13 +283,13 @@ func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
func (x *Xray) showLogs(spec *xray.NodeSpec, prev bool) {
// Need to load and wait for pods
path, co := spec.Path(), ""
- if spec.GVR() == "containers" {
+ if spec.GVR() == client.CoGVR {
_, coName := client.Namespaced(spec.Path())
path, co = *spec.ParentPath(), coName
}
ns, _ := client.Namespaced(path)
- _, err := x.app.factory.CanForResource(ns, "v1/pods", client.ListAccess)
+ _, err := x.app.factory.CanForResource(ns, client.PodGVR, client.ListAccess)
if err != nil {
x.app.Flash().Err(err)
return
@@ -299,12 +300,12 @@ func (x *Xray) showLogs(spec *xray.NodeSpec, prev bool) {
Container: co,
Previous: prev,
}
- if err := x.app.inject(NewLog(client.NewGVR("v1/pods"), &opts), false); err != nil {
+ if err := x.app.inject(NewLog(client.PodGVR, &opts), false); err != nil {
x.app.Flash().Err(err)
}
}
-func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (x *Xray) shellCmd(*tcell.EventKey) *tcell.EventKey {
spec := x.selectedSpec()
if spec == nil {
return nil
@@ -316,7 +317,7 @@ func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
}
path, co := spec.Path(), ""
- if spec.GVR() == "containers" {
+ if spec.GVR() == client.CoGVR {
_, co = client.Namespaced(spec.Path())
path = *spec.ParentPath()
}
@@ -328,7 +329,7 @@ func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (x *Xray) attachCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (x *Xray) attachCmd(*tcell.EventKey) *tcell.EventKey {
spec := x.selectedSpec()
if spec == nil {
return nil
@@ -340,7 +341,7 @@ func (x *Xray) attachCmd(evt *tcell.EventKey) *tcell.EventKey {
}
path, co := spec.Path(), ""
- if spec.GVR() == "containers" {
+ if spec.GVR() == client.CoGVR {
path = *spec.ParentPath()
}
@@ -381,8 +382,7 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
x.Stop()
defer x.Start()
{
- gvr := client.NewGVR(spec.GVR())
- meta, err := dao.MetaAccess.MetaFor(gvr)
+ meta, err := dao.MetaAccess.MetaFor(spec.GVR())
if err != nil {
slog.Warn("No meta found!",
slogs.GVR, spec.GVR(),
@@ -390,7 +390,7 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
)
return nil
}
- x.resourceDelete(gvr, spec, fmt.Sprintf("Delete %s %s?", meta.SingularName, spec.Path()))
+ x.resourceDelete(spec.GVR(), spec, fmt.Sprintf("Delete %s %s?", meta.SingularName, spec.Path()))
}
return nil
@@ -407,7 +407,7 @@ func (x *Xray) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (x *Xray) describe(gvr, path string) {
+func (x *Xray) describe(gvr *client.GVR, path string) {
ctx := context.Background()
ctx = context.WithValue(ctx, internal.KeyFactory, x.app.factory)
@@ -434,14 +434,16 @@ func (x *Xray) editCmd(evt *tcell.EventKey) *tcell.EventKey {
{
ns, n := client.Namespaced(spec.Path())
args := make([]string, 0, 10)
- args = append(args, "edit")
- args = append(args, client.NewGVR(spec.GVR()).R())
- args = append(args, "-n", ns)
- args = append(args, "--context", x.app.Config.K9s.ActiveContextName())
+ args = append(args,
+ "edit",
+ spec.GVR().R(),
+ "-n", ns,
+ "--context", x.app.Config.K9s.ActiveContextName(),
+ )
if cfg := x.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" {
args = append(args, "--kubeconfig", *cfg)
}
- if err := runK(x.app, shellOpts{args: append(args, n)}); err != nil {
+ if err := runK(x.app, &shellOpts{args: append(args, n)}); err != nil {
x.app.Flash().Errf("Edit exec failed: %s", err)
}
}
@@ -470,7 +472,7 @@ func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
-func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
+func (x *Xray) gotoCmd(*tcell.EventKey) *tcell.EventKey {
if x.CmdBuff().IsActive() {
if internal.IsLabelSelector(x.CmdBuff().GetText()) {
x.Start()
@@ -488,7 +490,7 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if len(strings.Split(spec.Path(), "/")) == 1 {
return nil
}
- x.app.gotoResource(client.NewGVR(spec.GVR()).R(), spec.Path(), false, true)
+ x.app.gotoResource(spec.GVR().String(), spec.Path(), false, true)
return nil
}
@@ -571,7 +573,7 @@ func (x *Xray) update(node *xray.TreeNode) {
// TreeChanged notifies the model data changed.
func (x *Xray) TreeChanged(node *xray.TreeNode) {
- x.Count = node.Count(x.gvr.String())
+ x.Count = node.Count(x.gvr)
x.update(x.filter(node))
x.UpdateTitle()
}
@@ -585,10 +587,10 @@ func (x *Xray) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
}
// SetEnvFn sets the custom environment function.
-func (x *Xray) SetEnvFn(EnvFunc) {}
+func (*Xray) SetEnvFn(EnvFunc) {}
// Refresh updates the view.
-func (x *Xray) Refresh() {}
+func (*Xray) Refresh() {}
// BufferCompleted indicates the buffer was changed.
func (x *Xray) BufferCompleted(_, _ string) {
@@ -596,7 +598,7 @@ func (x *Xray) BufferCompleted(_, _ string) {
}
// BufferChanged indicates the buffer was changed.
-func (x *Xray) BufferChanged(_, _ string) {}
+func (*Xray) BufferChanged(_, _ string) {}
// BufferActive indicates the buff activity changed.
func (x *Xray) BufferActive(state bool, k model.BufferKind) {
@@ -637,19 +639,19 @@ func (x *Xray) Stop() {
}
// AddBindKeysFn sets up extra key bindings.
-func (x *Xray) AddBindKeysFn(BindKeysFunc) {}
+func (*Xray) AddBindKeysFn(BindKeysFunc) {}
// SetContextFn sets custom context.
-func (x *Xray) SetContextFn(ContextFunc) {}
+func (*Xray) SetContextFn(ContextFunc) {}
// Name returns the component name.
-func (x *Xray) Name() string { return "XRay" }
+func (*Xray) Name() string { return "XRay" }
// GetTable returns the underlying table.
-func (x *Xray) GetTable() *Table { return nil }
+func (*Xray) GetTable() *Table { return nil }
// GVR returns a resource descriptor.
-func (x *Xray) GVR() client.GVR { return x.gvr }
+func (x *Xray) GVR() *client.GVR { return x.gvr }
// App returns the current app handle.
func (x *Xray) App() *App {
@@ -671,14 +673,14 @@ func (x *Xray) styleTitle() string {
ns = client.NamespaceAll
}
- var title string
+ var (
+ title string
+ styles = x.app.Styles.Frame()
+ )
if ns == client.ClusterScope {
- title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
+ title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(x.Count))), &styles)
} else {
- title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
- }
- if ic := ui.ROIndicator(x.app.Config.IsReadOnly(), x.app.Config.K9s.UI.NoIcons); ic != "" {
- title = " " + ic + title
+ title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(x.Count))), &styles)
}
buff := x.CmdBuff().GetText()
@@ -689,11 +691,12 @@ func (x *Xray) styleTitle() string {
buff = ui.TrimLabelSelector(buff)
}
- return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), x.app.Styles.Frame())
+ return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), &styles)
}
-func (x *Xray) resourceDelete(gvr client.GVR, spec *xray.NodeSpec, msg string) {
- dialog.ShowDelete(x.app.Styles.Dialog(), x.app.Content.Pages, msg, func(propagation *metav1.DeletionPropagation, force bool) {
+func (x *Xray) resourceDelete(gvr *client.GVR, spec *xray.NodeSpec, msg string) {
+ d := x.app.Styles.Dialog()
+ dialog.ShowDelete(&d, x.app.Content.Pages, msg, func(_ *metav1.DeletionPropagation, force bool) {
x.app.Flash().Infof("Delete resource %s %s", spec.GVR(), spec.Path())
accessor, err := dao.AccessorFor(x.app.factory, gvr)
if err != nil {
@@ -758,7 +761,7 @@ func rxInverseFilter(q, path string) bool {
return true
}
-func makeTreeNode(node *xray.TreeNode, expanded bool, showIcons bool, styles *config.Styles) *tview.TreeNode {
+func makeTreeNode(node *xray.TreeNode, expanded, showIcons bool, styles *config.Styles) *tview.TreeNode {
n := tview.NewTreeNode("No data...")
if node != nil {
n.SetText(node.Title(showIcons))
diff --git a/internal/view/yaml.go b/internal/view/yaml.go
index 37a9032a..30e13ed3 100644
--- a/internal/view/yaml.go
+++ b/internal/view/yaml.go
@@ -19,8 +19,8 @@ import (
)
var (
- keyValRX = regexp.MustCompile(`\A(\s*)([\w|\-|\.|\/|\s]+):\s(.+)\z`)
- keyRX = regexp.MustCompile(`\A(\s*)([\w|\-|\.|\/|\s]+):\s*\z`)
+ keyValRX = regexp.MustCompile(`\A(\s*)([\w\-./\s]+):\s(.+)\z`)
+ keyRX = regexp.MustCompile(`\A(\s*)([\w\-./\s]+):\s*\z`)
searchRX = regexp.MustCompile(`<<<("search_\d+")>>>(.+)<<<"">>>`)
)
@@ -93,7 +93,7 @@ func saveYAML(dir, name, raw string) (string, error) {
)
}
}()
- if _, err := file.Write([]byte(raw)); err != nil {
+ if _, err := file.WriteString(raw); err != nil {
return "", err
}
diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go
index baa8f12f..e235d46b 100644
--- a/internal/vul/scanner.go
+++ b/internal/vul/scanner.go
@@ -29,7 +29,6 @@ import (
"github.com/anchore/syft/syft"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var ImgScanner *imageScanner
@@ -60,8 +59,8 @@ func NewImageScanner(cfg config.ImageScans, l *slog.Logger) *imageScanner {
}
}
-func (s *imageScanner) ShouldExcludes(m metav1.ObjectMeta) bool {
- return s.config.ShouldExclude(m.Namespace, m.Labels)
+func (s *imageScanner) ShouldExcludes(ns string, lbls map[string]string) bool {
+ return s.config.ShouldExclude(ns, lbls)
}
// GetScan fetch scan for a given image. Returns ok=false when not found.
@@ -100,8 +99,8 @@ func (s *imageScanner) Init(name, version string) {
return
}
- if err := validateDBLoad(err, s.dbStatus); err != nil {
- s.log.Error("VulDb validate failed", slogs.Error, err)
+ if e := validateDBLoad(err, s.dbStatus); e != nil {
+ s.log.Error("VulDb validate failed", slogs.Error, e)
return
}
@@ -168,7 +167,7 @@ func (s *imageScanner) scanWorker(ctx context.Context, img string) {
func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error {
defer func(t time.Time) {
- s.log.Debug("Time to run vulscan",
+ s.log.Debug("[Vulscan] perf",
slogs.Image, img,
slogs.Elapsed, time.Since(t),
)
diff --git a/internal/vul/table_test.go b/internal/vul/table_test.go
index b8e5b2f8..115f14fa 100644
--- a/internal/vul/table_test.go
+++ b/internal/vul/table_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_sort(t *testing.T) {
@@ -73,14 +74,14 @@ func makeTable(t *testing.T, path string) *table {
defer func() {
_ = f.Close()
}()
- assert.NoError(t, err)
+ require.NoError(t, err)
sc := bufio.NewScanner(f)
var tt table
for sc.Scan() {
ff := strings.Fields(sc.Text())
tt.addRow(newRow(ff...))
}
- assert.NoError(t, sc.Err())
+ require.NoError(t, sc.Err())
return &tt
}
diff --git a/internal/watch/factory.go b/internal/watch/factory.go
index d7a1d279..17c9382a 100644
--- a/internal/watch/factory.go
+++ b/internal/watch/factory.go
@@ -72,7 +72,7 @@ func (f *Factory) Terminate() {
}
// List returns a resource collection.
-func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) {
+func (f *Factory) List(gvr *client.GVR, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) {
if client.IsAllNamespace(ns) {
ns = client.BlankNamespace
}
@@ -99,7 +99,7 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run
}
// HasSynced checks if given informer is up to date.
-func (f *Factory) HasSynced(gvr, ns string) (bool, error) {
+func (f *Factory) HasSynced(gvr *client.GVR, ns string) (bool, error) {
inf, err := f.CanForResource(ns, gvr, client.ListAccess)
if err != nil {
return false, err
@@ -109,7 +109,7 @@ func (f *Factory) HasSynced(gvr, ns string) (bool, error) {
}
// Get retrieves a given resource.
-func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (f *Factory) Get(gvr *client.GVR, fqn string, wait bool, _ labels.Selector) (runtime.Object, error) {
ns, n := namespaced(fqn)
if client.IsAllNamespace(ns) {
ns = client.BlankNamespace
@@ -200,7 +200,7 @@ func (f *Factory) isClusterWide() bool {
}
// CanForResource return an informer is user has access.
-func (f *Factory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (f *Factory) CanForResource(ns string, gvr *client.GVR, verbs []string) (informers.GenericInformer, error) {
auth, err := f.Client().CanI(ns, gvr, "", verbs)
if err != nil {
return nil, err
@@ -213,12 +213,12 @@ func (f *Factory) CanForResource(ns, gvr string, verbs []string) (informers.Gene
}
// ForResource returns an informer for a given resource.
-func (f *Factory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (f *Factory) ForResource(ns string, gvr *client.GVR) (informers.GenericInformer, error) {
fact, err := f.ensureFactory(ns)
if err != nil {
return nil, err
}
- inf := fact.ForResource(toGVR(gvr))
+ inf := fact.ForResource(gvr.GVR())
if inf == nil {
slog.Error("No informer found",
slogs.GVR, gvr,
@@ -306,7 +306,7 @@ func (f *Factory) ValidatePortForwards() {
if len(paths) < 1 {
slog.Error("Invalid port-forward path", slogs.Path, tokens[0])
}
- o, err := f.Get("v1/pods", paths[0], false, labels.Everything())
+ o, err := f.Get(client.PodGVR, paths[0], false, labels.Everything())
if err != nil {
fwd.Stop()
delete(f.forwarders, k)
diff --git a/internal/watch/forwarders_test.go b/internal/watch/forwarders_test.go
index 4ebbff7d..79e466c8 100644
--- a/internal/watch/forwarders_test.go
+++ b/internal/watch/forwarders_test.go
@@ -173,7 +173,7 @@ func newNoOpForwarder() noOpForwarder {
return noOpForwarder{}
}
-func (noOpForwarder) Start(path string, tunnel port.PortTunnel) (*portforward.PortForwarder, error) {
+func (noOpForwarder) Start(string, port.PortTunnel) (*portforward.PortForwarder, error) {
return nil, nil
}
func (noOpForwarder) Stop() {}
diff --git a/internal/watch/helper.go b/internal/watch/helper.go
index 67e79455..fd140377 100644
--- a/internal/watch/helper.go
+++ b/internal/watch/helper.go
@@ -25,10 +25,10 @@ func toGVR(gvr string) schema.GroupVersionResource {
}
}
-func namespaced(n string) (string, string) {
- ns, po := path.Split(n)
+func namespaced(n string) (ns, res string) {
+ ns, res = path.Split(n)
- return strings.Trim(ns, "/"), po
+ return strings.Trim(ns, "/"), res
}
// DumpFactory for debug.
@@ -41,7 +41,7 @@ func DumpFactory(f *Factory) {
}
// DebugFactory for debug.
-func DebugFactory(f *Factory, ns string, gvr string) {
+func DebugFactory(f *Factory, ns, gvr string) {
slog.Debug(fmt.Sprintf("----------- DEBUG FACTORY (%s) -------------", gvr))
fac, ok := f.factories[ns]
if !ok {
diff --git a/internal/xray/container.go b/internal/xray/container.go
index 1a0f2136..09a58cb2 100644
--- a/internal/xray/container.go
+++ b/internal/xray/container.go
@@ -21,7 +21,7 @@ import (
type Container struct{}
// Render renders an xray node.
-func (c *Container) Render(ctx context.Context, ns string, o interface{}) error {
+func (c *Container) Render(ctx context.Context, ns string, o any) error {
co, ok := o.(render.ContainerRes)
if !ok {
return fmt.Errorf("expected ContainerRes, but got %T", o)
@@ -32,7 +32,7 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
return fmt.Errorf("no factory found in context")
}
- root := NewTreeNode("containers", client.FQN(ns, co.Container.Name))
+ root := NewTreeNode(client.CoGVR, client.FQN(ns, co.Container.Name))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
@@ -55,11 +55,11 @@ func (c *Container) envRefs(f dao.Factory, parent *TreeNode, ns string, co *v1.C
for _, e := range co.EnvFrom {
if e.ConfigMapRef != nil {
- gvr, id := "v1/configmaps", client.FQN(ns, e.ConfigMapRef.Name)
+ gvr, id := client.CmGVR, client.FQN(ns, e.ConfigMapRef.Name)
addRef(f, parent, gvr, id, e.ConfigMapRef.Optional)
}
if e.SecretRef != nil {
- gvr, id := "v1/secrets", client.FQN(ns, e.SecretRef.Name)
+ gvr, id := client.SecGVR, client.FQN(ns, e.SecretRef.Name)
addRef(f, parent, gvr, id, e.SecretRef.Optional)
}
}
@@ -69,7 +69,7 @@ func (c *Container) secretRefs(f dao.Factory, parent *TreeNode, ns string, ref *
if ref == nil {
return
}
- gvr, id := "v1/secrets", client.FQN(ns, ref.Name)
+ gvr, id := client.SecGVR, client.FQN(ns, ref.Name)
addRef(f, parent, gvr, id, ref.Optional)
}
@@ -77,14 +77,14 @@ func (c *Container) configMapRefs(f dao.Factory, parent *TreeNode, ns string, re
if ref == nil {
return
}
- gvr, id := "v1/configmaps", client.FQN(ns, ref.Name)
+ gvr, id := client.CmGVR, client.FQN(ns, ref.Name)
addRef(f, parent, gvr, id, ref.Optional)
}
// ----------------------------------------------------------------------------
// Helpers...
-func addRef(f dao.Factory, parent *TreeNode, gvr, id string, optional *bool) {
+func addRef(f dao.Factory, parent *TreeNode, gvr *client.GVR, id string, optional *bool) {
if parent.Find(gvr, id) == nil {
n := NewTreeNode(gvr, id)
validate(f, n, optional)
diff --git a/internal/xray/container_test.go b/internal/xray/container_test.go
index 0c78100b..5cbde50f 100644
--- a/internal/xray/container_test.go
+++ b/internal/xray/container_test.go
@@ -18,6 +18,7 @@ import (
"github.com/derailed/k9s/internal/watch"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
@@ -32,11 +33,11 @@ func init() {
func TestCOConfigMapRefs(t *testing.T) {
var re xray.Container
- root := xray.NewTreeNode("root", "root")
+ root := xray.NewTreeNode(client.NewGVR("root"), "root")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", render.ContainerRes{Container: makeCMContainer("c1", false)}))
+ require.NoError(t, re.Render(ctx, "", render.ContainerRes{Container: makeCMContainer("c1", false)}))
assert.Equal(t, xray.MissingRefStatus, root.Children[0].Children[0].Extras[xray.StatusKey])
}
@@ -88,11 +89,11 @@ func TestCORefs(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
var re xray.Container
- root := xray.NewTreeNode("root", "root")
+ root := xray.NewTreeNode(client.NewGVR("root"), "root")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", u.co))
+ require.NoError(t, re.Render(ctx, "", u.co))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
assert.Equal(t, u.e, root.Children[0].Children[0].Extras[xray.StatusKey])
@@ -108,7 +109,7 @@ func makeFactory() testFactory {
}
type testFactory struct {
- rows map[string][]runtime.Object
+ rows map[*client.GVR][]runtime.Object
}
var _ dao.Factory = testFactory{}
@@ -117,7 +118,7 @@ func (f testFactory) Client() client.Connection {
return nil
}
-func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
+func (f testFactory) Get(gvr *client.GVR, _ string, _ bool, _ labels.Selector) (runtime.Object, error) {
oo, ok := f.rows[gvr]
if ok && len(oo) > 0 {
return oo[0], nil
@@ -125,7 +126,7 @@ func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runt
return nil, nil
}
-func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
+func (f testFactory) List(gvr *client.GVR, _ string, _ bool, _ labels.Selector) ([]runtime.Object, error) {
oo, ok := f.rows[gvr]
if ok {
return oo, nil
@@ -133,11 +134,11 @@ func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]run
return nil, nil
}
-func (f testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) {
+func (f testFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
-func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
+func (f testFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
func (f testFactory) WaitForCacheSync() {}
@@ -242,11 +243,11 @@ func makeDoubleCMKeysContainer(n string, optional bool) *v1.Container {
func load(t *testing.T, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
- assert.Nil(t, err)
+ require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
- assert.Nil(t, err)
+ require.NoError(t, err)
return &o
}
diff --git a/internal/xray/dp.go b/internal/xray/dp.go
index 1acc99a0..37fd3e49 100644
--- a/internal/xray/dp.go
+++ b/internal/xray/dp.go
@@ -22,7 +22,7 @@ import (
type Deployment struct{}
// Render renders an xray node.
-func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error {
+func (d *Deployment) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected Unstructured, but got %T", o)
@@ -38,7 +38,7 @@ func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- root := NewTreeNode("apps/v1/deployments", client.FQN(dp.Namespace, dp.Name))
+ root := NewTreeNode(client.DpGVR, client.FQN(dp.Namespace, dp.Name))
oo, err := locatePods(ctx, dp.Namespace, dp.Spec.Selector)
if err != nil {
return err
@@ -58,7 +58,7 @@ func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error
if root.IsLeaf() {
return nil
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, dp.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, dp.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
@@ -102,5 +102,5 @@ func locatePods(ctx context.Context, ns string, sel *metav1.LabelSelector) ([]ru
return nil, fmt.Errorf("expecting a factory but got %T", ctx.Value(internal.KeyFactory))
}
- return f.List("v1/pods", ns, false, fsel.AsSelector())
+ return f.List(client.PodGVR, ns, false, fsel.AsSelector())
}
diff --git a/internal/xray/dp_test.go b/internal/xray/dp_test.go
index 7286e9dd..a4024347 100644
--- a/internal/xray/dp_test.go
+++ b/internal/xray/dp_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -30,19 +32,19 @@ func TestDeployRender(t *testing.T) {
var re xray.Deployment
for k := range uu {
f := makeFactory()
- f.rows = map[string][]runtime.Object{
- "v1/pods": {load(t, "po")},
- "v1/serviceaccounts": {load(t, "sa")},
+ f.rows = map[*client.GVR][]runtime.Object{
+ client.PodGVR: {load(t, "po")},
+ client.SaGVR: {load(t, "sa")},
}
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("deployments", "deployments")
+ root := xray.NewTreeNode(client.DpGVR, "deployments")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, f)
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/ds.go b/internal/xray/ds.go
index 44eaad06..27d6d29f 100644
--- a/internal/xray/ds.go
+++ b/internal/xray/ds.go
@@ -18,7 +18,7 @@ import (
type DaemonSet struct{}
// Render renders an xray node.
-func (d *DaemonSet) Render(ctx context.Context, ns string, o interface{}) error {
+func (d *DaemonSet) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected Unstructured, but got %T", o)
@@ -34,7 +34,7 @@ func (d *DaemonSet) Render(ctx context.Context, ns string, o interface{}) error
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- root := NewTreeNode("apps/v1/daemonsets", client.FQN(ds.Namespace, ds.Name))
+ root := NewTreeNode(client.DsGVR, client.FQN(ds.Namespace, ds.Name))
oo, err := locatePods(ctx, ds.Namespace, ds.Spec.Selector)
if err != nil {
return err
@@ -54,7 +54,7 @@ func (d *DaemonSet) Render(ctx context.Context, ns string, o interface{}) error
if root.IsLeaf() {
return nil
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, ds.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, ds.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
diff --git a/internal/xray/ds_test.go b/internal/xray/ds_test.go
index 40c4db11..27dae15a 100644
--- a/internal/xray/ds_test.go
+++ b/internal/xray/ds_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -30,15 +32,15 @@ func TestDaemonSetRender(t *testing.T) {
var re xray.DaemonSet
for k := range uu {
f := makeFactory()
- f.rows = map[string][]runtime.Object{"v1/pods": {load(t, "po")}}
+ f.rows = map[*client.GVR][]runtime.Object{client.PodGVR: {load(t, "po")}}
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("daemonsets", "daemonsets")
+ root := xray.NewTreeNode(client.DsGVR, "daemonsets")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, f)
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/generic.go b/internal/xray/generic.go
index 8d30badc..7deb52a5 100644
--- a/internal/xray/generic.go
+++ b/internal/xray/generic.go
@@ -22,7 +22,7 @@ func (g *Generic) SetTable(_ string, t *metav1.Table) {
}
// Render renders a K8s resource to screen.
-func (g *Generic) Render(ctx context.Context, ns string, o interface{}) error {
+func (g *Generic) Render(ctx context.Context, ns string, o any) error {
row, ok := o.(metav1.TableRow)
if !ok {
return fmt.Errorf("expecting a TableRow but got %T", o)
@@ -33,7 +33,7 @@ func (g *Generic) Render(ctx context.Context, ns string, o interface{}) error {
return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0])
}
- root := NewTreeNode("generic", client.FQN(ns, n))
+ root := NewTreeNode(client.NewGVR("generic"), client.FQN(ns, n))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("expecting TreeNode but got %T", ctx.Value(KeyParent))
diff --git a/internal/xray/generic_test.go b/internal/xray/generic_test.go
index 61b1dc89..70befbaa 100644
--- a/internal/xray/generic_test.go
+++ b/internal/xray/generic_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
)
@@ -28,11 +30,11 @@ func TestGenericRender(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- root := xray.NewTreeNode("generics", "generics")
+ root := xray.NewTreeNode(client.NewGVR("generic"), "generics")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", makeTable()))
+ require.NoError(t, re.Render(ctx, "", makeTable()))
assert.Equal(t, u.level1, root.CountChildren())
})
}
@@ -42,6 +44,6 @@ func TestGenericRender(t *testing.T) {
func makeTable() metav1beta1.TableRow {
return metav1beta1.TableRow{
- Cells: []interface{}{"fred", "blee"},
+ Cells: []any{"fred", "blee"},
}
}
diff --git a/internal/xray/ns.go b/internal/xray/ns.go
index bb7dd059..2d2c797d 100644
--- a/internal/xray/ns.go
+++ b/internal/xray/ns.go
@@ -17,7 +17,7 @@ import (
type Namespace struct{}
// Render renders an xray node.
-func (n *Namespace) Render(ctx context.Context, ns string, o interface{}) error {
+func (n *Namespace) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected NamespaceWithMetrics, but got %T", o)
@@ -29,7 +29,7 @@ func (n *Namespace) Render(ctx context.Context, ns string, o interface{}) error
return err
}
- root := NewTreeNode("v1/namespaces", client.FQN(client.ClusterScope, nss.Name))
+ root := NewTreeNode(client.NsGVR, client.FQN(client.ClusterScope, nss.Name))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
diff --git a/internal/xray/ns_test.go b/internal/xray/ns_test.go
index ab214e92..7a3ef1ee 100644
--- a/internal/xray/ns_test.go
+++ b/internal/xray/ns_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNamespaceRender(t *testing.T) {
@@ -30,11 +32,11 @@ func TestNamespaceRender(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("namespaces", "namespaces")
+ root := xray.NewTreeNode(client.NsGVR, "namespaces")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
})
}
diff --git a/internal/xray/pod.go b/internal/xray/pod.go
index 9fb27272..0771b5f4 100644
--- a/internal/xray/pod.go
+++ b/internal/xray/pod.go
@@ -21,7 +21,7 @@ import (
type Pod struct{}
// Render renders an xray node.
-func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
+func (p *Pod) Render(ctx context.Context, ns string, o any) error {
pwm, ok := o.(*render.PodWithMetrics)
if !ok {
return fmt.Errorf("expected PodWithMetrics, but got %T", o)
@@ -38,21 +38,21 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
return fmt.Errorf("no factory found in context")
}
- node := NewTreeNode("v1/pods", client.FQN(po.Namespace, po.Name))
+ node := NewTreeNode(client.PodGVR, client.FQN(po.Namespace, po.Name))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- if err := p.containerRefs(ctx, node, po.Namespace, po.Spec); err != nil {
+ if err := p.containerRefs(ctx, node, po.Namespace, &po.Spec); err != nil {
return err
}
p.podVolumeRefs(f, node, po.Namespace, po.Spec.Volumes)
- if err := p.serviceAccountRef(ctx, f, node, po.Namespace, po.Spec); err != nil {
+ if err := p.serviceAccountRef(ctx, f, node, po.Namespace, &po.Spec); err != nil {
return err
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, po.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, po.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
@@ -65,9 +65,9 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
func (p *Pod) validate(node *TreeNode, po v1.Pod) error {
var re render.Pod
- phase := re.Phase(&po)
+ phase := re.Phase(po.DeletionTimestamp, &po.Spec, &po.Status)
ss := po.Status.ContainerStatuses
- cr, _, _ := re.Statuses(ss)
+ cr, _, _, _ := re.Statuses(ss)
status := OkStatus
if cr != len(ss) {
status = ToastStatus
@@ -82,20 +82,20 @@ func (p *Pod) validate(node *TreeNode, po v1.Pod) error {
return nil
}
-func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec v1.PodSpec) error {
+func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec *v1.PodSpec) error {
ctx = context.WithValue(ctx, KeyParent, parent)
var cre Container
- for i := 0; i < len(spec.InitContainers); i++ {
+ for i := range len(spec.InitContainers) {
if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.InitContainers[i]}); err != nil {
return err
}
}
- for i := 0; i < len(spec.Containers); i++ {
+ for i := range len(spec.Containers) {
if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.Containers[i]}); err != nil {
return err
}
}
- for i := 0; i < len(spec.EphemeralContainers); i++ {
+ for i := range len(spec.EphemeralContainers) {
if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.Containers[i]}); err != nil {
return err
}
@@ -104,18 +104,18 @@ func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec
return nil
}
-func (*Pod) serviceAccountRef(ctx context.Context, f dao.Factory, parent *TreeNode, ns string, spec v1.PodSpec) error {
+func (*Pod) serviceAccountRef(ctx context.Context, f dao.Factory, parent *TreeNode, ns string, spec *v1.PodSpec) error {
if spec.ServiceAccountName == "" {
return nil
}
- id := client.FQN(ns, spec.ServiceAccountName)
- o, err := f.Get("v1/serviceaccounts", id, true, labels.Everything())
+ fqn := client.FQN(ns, spec.ServiceAccountName)
+ o, err := f.Get(client.SaGVR, fqn, true, labels.Everything())
if err != nil {
return err
}
if o == nil {
- addRef(f, parent, "v1/serviceaccounts", id, nil)
+ addRef(f, parent, client.SaGVR, fqn, nil)
return nil
}
@@ -126,22 +126,22 @@ func (*Pod) serviceAccountRef(ctx context.Context, f dao.Factory, parent *TreeNo
}
func (*Pod) podVolumeRefs(f dao.Factory, parent *TreeNode, ns string, vv []v1.Volume) {
- for _, v := range vv {
- sec := v.Secret
+ for i := range vv {
+ sec := vv[i].Secret
if sec != nil {
- addRef(f, parent, "v1/secrets", client.FQN(ns, sec.SecretName), sec.Optional)
+ addRef(f, parent, client.SecGVR, client.FQN(ns, sec.SecretName), sec.Optional)
continue
}
- cm := v.ConfigMap
+ cm := vv[i].ConfigMap
if cm != nil {
- addRef(f, parent, "v1/configmaps", client.FQN(ns, cm.Name), cm.Optional)
+ addRef(f, parent, client.CmGVR, client.FQN(ns, cm.Name), cm.Optional)
continue
}
- pvc := v.PersistentVolumeClaim
+ pvc := vv[i].PersistentVolumeClaim
if pvc != nil {
- addRef(f, parent, "v1/persistentvolumeclaims", client.FQN(ns, pvc.ClaimName), nil)
+ addRef(f, parent, client.PvcGVR, client.FQN(ns, pvc.ClaimName), nil)
}
}
}
diff --git a/internal/xray/pod_test.go b/internal/xray/pod_test.go
index 23dd18bc..92a08051 100644
--- a/internal/xray/pod_test.go
+++ b/internal/xray/pod_test.go
@@ -8,9 +8,11 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPodRender(t *testing.T) {
@@ -44,13 +46,13 @@ func TestPodRender(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("pods", "pods")
+ root := xray.NewTreeNode(client.PodGVR, "pods")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", &render.PodWithMetrics{Raw: o}))
+ require.NoError(t, re.Render(ctx, "", &render.PodWithMetrics{Raw: o}))
assert.Equal(t, u.children, root.CountChildren())
- assert.Equal(t, u.count, root.Count(""))
+ assert.Equal(t, u.count, root.Count(client.NoGVR))
})
}
}
diff --git a/internal/xray/rs.go b/internal/xray/rs.go
index 2078d74b..b547569c 100644
--- a/internal/xray/rs.go
+++ b/internal/xray/rs.go
@@ -18,7 +18,7 @@ import (
type ReplicaSet struct{}
// Render renders an xray node.
-func (r *ReplicaSet) Render(ctx context.Context, ns string, o interface{}) error {
+func (r *ReplicaSet) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected Unstructured, but got %T", o)
@@ -34,7 +34,7 @@ func (r *ReplicaSet) Render(ctx context.Context, ns string, o interface{}) error
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- root := NewTreeNode("apps/v1/replicasets", client.FQN(rs.Namespace, rs.Name))
+ root := NewTreeNode(client.RsGVR, client.FQN(rs.Namespace, rs.Name))
oo, err := locatePods(ctx, rs.Namespace, rs.Spec.Selector)
if err != nil {
return err
@@ -56,7 +56,7 @@ func (r *ReplicaSet) Render(ctx context.Context, ns string, o interface{}) error
return nil
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, rs.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, rs.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
diff --git a/internal/xray/rs_test.go b/internal/xray/rs_test.go
index 52e739f1..4868b291 100644
--- a/internal/xray/rs_test.go
+++ b/internal/xray/rs_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -32,14 +34,16 @@ func TestReplicaSetRender(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
f := makeFactory()
- f.rows = map[string][]runtime.Object{"v1/pods": {load(t, "po")}}
+ f.rows = map[*client.GVR][]runtime.Object{
+ client.PodGVR: {load(t, "po")},
+ }
o := load(t, u.file)
- root := xray.NewTreeNode("replicasets", "replicasets")
+ root := xray.NewTreeNode(client.RsGVR, "replicasets")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, f)
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/sa.go b/internal/xray/sa.go
index 175a996a..647b4f02 100644
--- a/internal/xray/sa.go
+++ b/internal/xray/sa.go
@@ -19,7 +19,7 @@ import (
type ServiceAccount struct{}
// Render renders an xray node.
-func (s *ServiceAccount) Render(ctx context.Context, ns string, o interface{}) error {
+func (s *ServiceAccount) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("ServiceAccount render expecting *Unstructured, but got %T", o)
@@ -35,7 +35,7 @@ func (s *ServiceAccount) Render(ctx context.Context, ns string, o interface{}) e
if !ok {
return fmt.Errorf("no factory found in context")
}
- node := NewTreeNode("v1/serviceaccounts", client.FQN(sa.Namespace, sa.Name))
+ node := NewTreeNode(client.SaGVR, client.FQN(sa.Namespace, sa.Name))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
@@ -44,10 +44,10 @@ func (s *ServiceAccount) Render(ctx context.Context, ns string, o interface{}) e
parent.Add(node)
for _, sec := range sa.Secrets {
- addRef(f, node, "v1/secrets", client.FQN(sa.Namespace, sec.Name), nil)
+ addRef(f, node, client.SecGVR, client.FQN(sa.Namespace, sec.Name), nil)
}
for _, sec := range sa.ImagePullSecrets {
- addRef(f, node, "v1/secrets", client.FQN(sa.Namespace, sec.Name), nil)
+ addRef(f, node, client.SecGVR, client.FQN(sa.Namespace, sec.Name), nil)
}
auto, _ := ctx.Value(KeySAAutomount).(*bool)
diff --git a/internal/xray/sa_test.go b/internal/xray/sa_test.go
index 7afdf0de..65156f28 100644
--- a/internal/xray/sa_test.go
+++ b/internal/xray/sa_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestSARender(t *testing.T) {
@@ -31,11 +33,11 @@ func TestSARender(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("serviceaccounts", "serviceaccounts")
+ root := xray.NewTreeNode(client.SaGVR, "serviceaccounts")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/section.go b/internal/xray/section.go
index cc6d04ae..089c11ce 100644
--- a/internal/xray/section.go
+++ b/internal/xray/section.go
@@ -8,6 +8,7 @@ import (
"fmt"
"strings"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
)
@@ -17,12 +18,12 @@ type Section struct {
}
// Render renders an xray node.
-func (s *Section) Render(ctx context.Context, ns string, o interface{}) error {
+func (s *Section) Render(ctx context.Context, ns string, o any) error {
section, ok := o.(render.Section)
if !ok {
return fmt.Errorf("expected Section, but got %T", o)
}
- root := NewTreeNode(section.GVR, section.Title)
+ root := NewTreeNode(client.NewGVR(section.GVR), section.Title)
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
@@ -35,20 +36,20 @@ func (s *Section) Render(ctx context.Context, ns string, o interface{}) error {
func (*Section) outcomeRefs(parent *TreeNode, section render.Section) {
for k, issues := range section.Outcome {
- p := NewTreeNode(section.GVR, cleanse(k))
+ p := NewTreeNode(client.NewGVR(section.GVR), cleanse(k))
parent.Add(p)
for _, issue := range issues {
msg := colorize(cleanse(issue.Message), issue.Level)
- c := NewTreeNode(fmt.Sprintf("issue_%d", issue.Level), msg)
+ c := NewTreeNode(client.NewGVR(fmt.Sprintf("issue_%d", issue.Level)), msg)
if issue.Group == "__root__" {
p.Add(c)
continue
}
- if pa := p.Find(issue.GVR, issue.Group); pa != nil {
+ if pa := p.Find(client.NewGVR(issue.GVR), issue.Group); pa != nil {
pa.Add(c)
continue
}
- pa := NewTreeNode(issue.GVR, issue.Group)
+ pa := NewTreeNode(client.NewGVR(issue.GVR), issue.Group)
pa.Add(c)
p.Add(pa)
}
diff --git a/internal/xray/sts.go b/internal/xray/sts.go
index 78ab796c..3ff66959 100644
--- a/internal/xray/sts.go
+++ b/internal/xray/sts.go
@@ -18,7 +18,7 @@ import (
type StatefulSet struct{}
// Render renders an xray node.
-func (s *StatefulSet) Render(ctx context.Context, ns string, o interface{}) error {
+func (s *StatefulSet) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected Unstructured, but got %T", o)
@@ -34,7 +34,7 @@ func (s *StatefulSet) Render(ctx context.Context, ns string, o interface{}) erro
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- root := NewTreeNode("apps/v1/statefulsets", client.FQN(sts.Namespace, sts.Name))
+ root := NewTreeNode(client.StsGVR, client.FQN(sts.Namespace, sts.Name))
oo, err := locatePods(ctx, sts.Namespace, sts.Spec.Selector)
if err != nil {
return err
@@ -56,7 +56,7 @@ func (s *StatefulSet) Render(ctx context.Context, ns string, o interface{}) erro
return nil
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, sts.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, sts.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
diff --git a/internal/xray/sts_test.go b/internal/xray/sts_test.go
index 7d044471..868c4e1a 100644
--- a/internal/xray/sts_test.go
+++ b/internal/xray/sts_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -32,14 +34,14 @@ func TestStatefulSetRender(t *testing.T) {
u := uu[k]
t.Run(k, func(t *testing.T) {
f := makeFactory()
- f.rows = map[string][]runtime.Object{"v1/pods": {load(t, "po")}}
+ f.rows = map[*client.GVR][]runtime.Object{client.PodGVR: {load(t, "po")}}
o := load(t, u.file)
- root := xray.NewTreeNode("statefulsets", "statefulsets")
+ root := xray.NewTreeNode(client.StsGVR, "statefulsets")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, f)
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/svc.go b/internal/xray/svc.go
index 59a97682..90577b7f 100644
--- a/internal/xray/svc.go
+++ b/internal/xray/svc.go
@@ -22,7 +22,7 @@ import (
type Service struct{}
// Render renders an xray node.
-func (s *Service) Render(ctx context.Context, ns string, o interface{}) error {
+func (s *Service) Render(ctx context.Context, ns string, o any) error {
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expected Unstructured, but got %T", o)
@@ -39,7 +39,7 @@ func (s *Service) Render(ctx context.Context, ns string, o interface{}) error {
return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
- root := NewTreeNode("v1/services", client.FQN(svc.Namespace, svc.Name))
+ root := NewTreeNode(client.SvcGVR, client.FQN(svc.Namespace, svc.Name))
oo, err := s.locatePods(ctx, svc.Namespace, svc.Spec.Selector)
if err != nil {
return err
@@ -60,7 +60,7 @@ func (s *Service) Render(ctx context.Context, ns string, o interface{}) error {
if root.IsLeaf() {
return nil
}
- gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, svc.Namespace)
+ gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, svc.Namespace)
nsn := parent.Find(gvr, nsID)
if nsn == nil {
nsn = NewTreeNode(gvr, nsID)
@@ -87,5 +87,5 @@ func (s *Service) locatePods(ctx context.Context, ns string, sel map[string]stri
return nil, err
}
- return f.List("v1/pods", ns, false, fsel.AsSelector())
+ return f.List(client.PodGVR, ns, false, fsel.AsSelector())
}
diff --git a/internal/xray/svc_test.go b/internal/xray/svc_test.go
index 14be39e5..a296b6de 100644
--- a/internal/xray/svc_test.go
+++ b/internal/xray/svc_test.go
@@ -8,8 +8,10 @@ import (
"testing"
"github.com/derailed/k9s/internal"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -30,16 +32,16 @@ func TestServiceRender(t *testing.T) {
var re xray.Service
for k := range uu {
f := makeFactory()
- f.rows = map[string][]runtime.Object{"v1/pods": {load(t, "po")}}
+ f.rows = map[*client.GVR][]runtime.Object{client.PodGVR: {load(t, "po")}}
u := uu[k]
t.Run(k, func(t *testing.T) {
o := load(t, u.file)
- root := xray.NewTreeNode("services", "services")
+ root := xray.NewTreeNode(client.SvcGVR, "services")
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
ctx = context.WithValue(ctx, internal.KeyFactory, f)
- assert.Nil(t, re.Render(ctx, "", o))
+ require.NoError(t, re.Render(ctx, "", o))
assert.Equal(t, u.level1, root.CountChildren())
assert.Equal(t, u.level2, root.Children[0].CountChildren())
})
diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go
index 44b86970..eca8a928 100644
--- a/internal/xray/tree_node.go
+++ b/internal/xray/tree_node.go
@@ -54,14 +54,16 @@ type TreeRef string
// NodeSpec represents a node resource specification.
type NodeSpec struct {
- GVRs, Paths, Statuses []string
+ GVRs client.GVRs
+ Paths, Statuses []string
}
// ParentGVR returns the parent GVR.
-func (s NodeSpec) ParentGVR() *string {
+func (s NodeSpec) ParentGVR() *client.GVR {
if len(s.GVRs) > 1 {
- return &s.GVRs[1]
+ return s.GVRs[1]
}
+
return nil
}
@@ -74,7 +76,7 @@ func (s NodeSpec) ParentPath() *string {
}
// GVR returns the current GVR.
-func (s NodeSpec) GVR() string {
+func (s NodeSpec) GVR() *client.GVR {
return s.GVRs[0]
}
@@ -95,7 +97,12 @@ func (s NodeSpec) AsPath() string {
// AsGVR returns a gvr hierarchy as string.
func (s NodeSpec) AsGVR() string {
- return strings.Join(s.GVRs, PathSeparator)
+ ss := make([]string, 0, len(s.GVRs))
+ for _, gvr := range s.GVRs {
+ ss = append(ss, gvr.R())
+ }
+
+ return strings.Join(ss, PathSeparator)
}
// AsStatus returns a status hierarchy as string.
@@ -129,14 +136,15 @@ func (c ChildNodes) Less(i, j int) bool {
// TreeNode represents a resource tree node.
type TreeNode struct {
- GVR, ID string
+ GVR *client.GVR
+ ID string
Children ChildNodes
Parent *TreeNode
Extras map[string]string
}
// NewTreeNode returns a new instance.
-func NewTreeNode(gvr, id string) *TreeNode {
+func NewTreeNode(gvr *client.GVR, id string) *TreeNode {
return &TreeNode{
GVR: gvr,
ID: id,
@@ -150,9 +158,9 @@ func (t *TreeNode) CountChildren() int {
}
// Count all the nodes from this node.
-func (t *TreeNode) Count(gvr string) int {
+func (t *TreeNode) Count(gvr *client.GVR) int {
counter := 0
- if t.GVR == gvr || gvr == "" {
+ if t.GVR == gvr || gvr == client.NoGVR {
counter++
}
for _, c := range t.Children {
@@ -192,17 +200,18 @@ func (t *TreeNode) Sort() {
// Spec returns this node specification.
func (t *TreeNode) Spec() NodeSpec {
- var GVRs, Paths, Statuses []string
+ var gvrs client.GVRs
+ var paths, statuses []string
for parent := t; parent != nil; parent = parent.Parent {
- GVRs = append(GVRs, parent.GVR)
- Paths = append(Paths, parent.ID)
- Statuses = append(Statuses, parent.Extras[StatusKey])
+ gvrs = append(gvrs, parent.GVR)
+ paths = append(paths, parent.ID)
+ statuses = append(statuses, parent.Extras[StatusKey])
}
return NodeSpec{
- GVRs: GVRs,
- Paths: Paths,
- Statuses: Statuses,
+ GVRs: gvrs,
+ Paths: paths,
+ Statuses: statuses,
}
}
@@ -221,12 +230,12 @@ func (t *TreeNode) Flatten() []NodeSpec {
// Blank returns true if this node is unset.
func (t *TreeNode) Blank() bool {
- return t.GVR == "" && t.ID == ""
+ return t.GVR == client.NoGVR && t.ID == ""
}
// Hydrate hydrates a full tree bases on a collection of specifications.
func Hydrate(specs []NodeSpec) *TreeNode {
- root := NewTreeNode("", "")
+ root := NewTreeNode(client.NoGVR, "")
nav := root
for _, spec := range specs {
for i := len(spec.Paths) - 1; i >= 0; i-- {
@@ -325,7 +334,7 @@ func (t *TreeNode) Clear() {
}
// Find locates a node given a gvr/id spec.
-func (t *TreeNode) Find(gvr, id string) *TreeNode {
+func (t *TreeNode) Find(gvr *client.GVR, id string) *TreeNode {
if t.GVR == gvr && t.ID == id {
return t
}
@@ -377,8 +386,8 @@ func dumpStdOut(n *TreeNode, level int) {
}
}
-func category(gvr string) string {
- meta, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
+func category(gvr *client.GVR) string {
+ meta, err := dao.MetaAccess.MetaFor(gvr)
if err != nil {
return ""
}
@@ -470,32 +479,32 @@ func (t TreeNode) toEmojiTitle() (title string) {
return
}
-func toEmoji(gvr string) string {
+func toEmoji(gvr *client.GVR) string {
if e := v1Emoji(gvr); e != "" {
return e
}
if e := appsEmoji(gvr); e != "" {
return e
}
- if e := issueEmoji(gvr); e != "" {
+ if e := issueEmoji(gvr.String()); e != "" {
return e
}
switch gvr {
- case "autoscaling/v1/horizontalpodautoscalers":
+ case client.HpaGVR:
return "โ๏ธ"
- case "rbac.authorization.k8s.io/v1/clusterrolebindings", "rbac.authorization.k8s.io/v1/clusterroles":
+ case client.CrGVR, client.CrbGVR:
return "๐ฉโ"
- case "rbac.authorization.k8s.io/v1/rolebindings", "rbac.authorization.k8s.io/v1/roles":
+ case client.RoGVR, client.RobGVR:
return "๐จ๐ปโ"
- case "networking.k8s.io/v1/networkpolicies":
+ case client.NpGVR:
return "๐"
- case "policy/v1/poddisruptionbudgets":
+ case client.PdbGVR:
return "๐ท "
- case "policy/v1beta1/podsecuritypolicies":
+ case client.PspGVR:
return "๐ฎโโ๏ธ"
- case "containers":
+ case client.CoGVR:
return "๐ณ"
- case "report":
+ case client.NewGVR("report"):
return "๐งผ"
default:
return "๐"
@@ -517,40 +526,40 @@ func issueEmoji(gvr string) string {
}
}
-func v1Emoji(gvr string) string {
+func v1Emoji(gvr *client.GVR) string {
switch gvr {
- case "v1/namespaces":
+ case client.NsGVR:
return "๐ "
- case "v1/nodes":
+ case client.NodeGVR:
return "๐ฅ "
- case "v1/pods":
+ case client.PodGVR:
return "๐"
- case "v1/services":
+ case client.SvcGVR:
return "๐โโ๏ธ"
- case "v1/serviceaccounts":
+ case client.SaGVR:
return "๐ณ"
- case "v1/persistentvolumes":
+ case client.PvGVR:
return "๐"
- case "v1/persistentvolumeclaims":
+ case client.PvcGVR:
return "๐ "
- case "v1/secrets":
+ case client.SecGVR:
return "๐"
- case "v1/configmaps":
+ case client.CmGVR:
return "๐บ "
default:
return ""
}
}
-func appsEmoji(gvr string) string {
+func appsEmoji(gvr *client.GVR) string {
switch gvr {
- case "apps/v1/deployments":
+ case client.DpGVR:
return "๐ช"
- case "apps/v1/statefulsets":
+ case client.StsGVR:
return "๐"
- case "apps/v1/daemonsets":
+ case client.DsGVR:
return "๐"
- case "apps/v1/replicasets":
+ case client.RsGVR:
return "๐ฏโโ๏ธ"
default:
return ""
@@ -559,24 +568,24 @@ func appsEmoji(gvr string) string {
// EmojiInfo returns emoji help.
func EmojiInfo() map[string]string {
- GVRs := []string{
- "containers",
- "v1/namespaces",
- "v1/pods",
- "v1/services",
- "v1/serviceaccounts",
- "v1/persistentvolumes",
- "v1/persistentvolumeclaims",
- "v1/secrets",
- "v1/configmaps",
- "apps/v1/deployments",
- "apps/v1/statefulsets",
- "apps/v1/daemonsets",
+ gvrs := []*client.GVR{
+ client.CoGVR,
+ client.NsGVR,
+ client.PodGVR,
+ client.SvcGVR,
+ client.SaGVR,
+ client.PvGVR,
+ client.PvcGVR,
+ client.SecGVR,
+ client.CmGVR,
+ client.DpGVR,
+ client.StsGVR,
+ client.DsGVR,
}
- m := make(map[string]string, len(GVRs))
- for _, g := range GVRs {
- m[client.NewGVR(g).R()] = toEmoji(g)
+ m := make(map[string]string, len(gvrs))
+ for _, gvr := range gvrs {
+ m[gvr.R()] = toEmoji(gvr)
}
return m
diff --git a/internal/xray/tree_node_test.go b/internal/xray/tree_node_test.go
index cd3442c7..67722542 100644
--- a/internal/xray/tree_node_test.go
+++ b/internal/xray/tree_node_test.go
@@ -8,6 +8,7 @@ import (
"strings"
"testing"
+ "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/xray"
"github.com/stretchr/testify/assert"
)
@@ -30,7 +31,7 @@ func TestTreeNodeCount(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
- assert.Equal(t, u.e, u.root.Count(""))
+ assert.Equal(t, u.e, u.root.Count(client.NoGVR))
})
}
}
@@ -99,12 +100,12 @@ func TestTreeNodeHydrate(t *testing.T) {
"flat_simple": {
spec: []xray.NodeSpec{
{
- GVRs: []string{"containers", "v1/pods"},
+ GVRs: []*client.GVR{client.CoGVR, client.PodGVR},
Paths: []string{"c1", "default/p1"},
Statuses: threeOK,
},
{
- GVRs: []string{"containers", "v1/pods"},
+ GVRs: []*client.GVR{client.CoGVR, client.PodGVR},
Paths: []string{"c2", "default/p1"},
Statuses: threeOK,
},
@@ -114,12 +115,12 @@ func TestTreeNodeHydrate(t *testing.T) {
"flat_complex": {
spec: []xray.NodeSpec{
{
- GVRs: []string{"v1/secrets", "containers", "v1/pods"},
+ GVRs: []*client.GVR{client.SecGVR, client.CoGVR, client.PodGVR},
Paths: []string{"s1", "c1", "default/p1"},
Statuses: threeOK,
},
{
- GVRs: []string{"v1/secrets", "containers", "v1/pods"},
+ GVRs: []*client.GVR{client.SecGVR, client.CoGVR, client.PodGVR},
Paths: []string{"s2", "c2", "default/p1"},
Statuses: threeOK,
},
@@ -129,47 +130,47 @@ func TestTreeNodeHydrate(t *testing.T) {
"complex1": {
spec: []xray.NodeSpec{
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"default/default-token-rr22g", "default/nginx-6b866d578b-c6tcn", "default/nginx", "-/default", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/configmaps", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.CmGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/coredns", "kube-system/coredns-6955765f44-89q2p", "kube-system/coredns", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/coredns-token-5cq9j", "kube-system/coredns-6955765f44-89q2p", "kube-system/coredns", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/configmaps", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.CmGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/coredns", "kube-system/coredns-6955765f44-r9j9t", "kube-system/coredns", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/coredns-token-5cq9j", "kube-system/coredns-6955765f44-r9j9t", "kube-system/coredns", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/default-token-thzt8", "kube-system/metrics-server-6754dbc9df-88bk4", "kube-system/metrics-server", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kube-system/nginx-ingress-token-kff5q", "kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55", "kube-system/nginx-ingress-controller", "-/kube-system", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kubernetes-dashboard/kubernetes-dashboard-token-d6rt4", "kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56", "kubernetes-dashboard/dashboard-metrics-scraper", "-/kubernetes-dashboard", "deployments"},
Statuses: fiveOK,
},
{
- GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
+ GVRs: []*client.GVR{client.SecGVR, client.PodGVR, client.DpGVR, client.NsGVR, client.DpGVR},
Paths: []string{"kubernetes-dashboard/kubernetes-dashboard-token-d6rt4", "kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d", "kubernetes-dashboard/kubernetes-dashboard", "-/kubernetes-dashboard", "deployments"},
Statuses: fiveOK,
},
@@ -196,12 +197,12 @@ func TestTreeNodeFlatten(t *testing.T) {
root: root1(),
e: []xray.NodeSpec{
{
- GVRs: []string{"containers", "v1/pods"},
+ GVRs: []*client.GVR{client.CoGVR, client.PodGVR},
Paths: []string{"c1", "default/p1"},
Statuses: []string{"ok", "ok"},
},
{
- GVRs: []string{"containers", "v1/pods"},
+ GVRs: []*client.GVR{client.CoGVR, client.PodGVR},
Paths: []string{"c2", "default/p1"},
Statuses: []string{"ok", "ok"},
},
@@ -211,12 +212,12 @@ func TestTreeNodeFlatten(t *testing.T) {
root: root2(),
e: []xray.NodeSpec{
{
- GVRs: []string{"v1/secrets", "containers", "v1/pods"},
+ GVRs: []*client.GVR{client.SecGVR, client.CoGVR, client.PodGVR},
Paths: []string{"s1", "c1", "default/p1"},
Statuses: []string{"ok", "ok", "ok"},
},
{
- GVRs: []string{"v1/secrets", "containers", "v1/pods"},
+ GVRs: []*client.GVR{client.SecGVR, client.CoGVR, client.PodGVR},
Paths: []string{"s2", "c2", "default/p1"},
Statuses: []string{"ok", "ok", "ok"},
},
@@ -243,8 +244,8 @@ func TestTreeNodeDiff(t *testing.T) {
n2: &xray.TreeNode{},
},
"same": {
- n1: xray.NewTreeNode("v1/pods", "default/p1"),
- n2: xray.NewTreeNode("v1/pods", "default/p1"),
+ n1: xray.NewTreeNode(client.PodGVR, "default/p1"),
+ n2: xray.NewTreeNode(client.PodGVR, "default/p1"),
},
}
@@ -257,8 +258,8 @@ func TestTreeNodeDiff(t *testing.T) {
}
func TestTreeNodeClone(t *testing.T) {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
n.Add(c1)
c := n.ShallowClone()
@@ -266,9 +267,9 @@ func TestTreeNodeClone(t *testing.T) {
}
func TestTreeNodeRoot(t *testing.T) {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
- c2 := xray.NewTreeNode("containers", "c2")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
+ c2 := xray.NewTreeNode(client.CoGVR, "c2")
n.Add(c1)
n.Add(c2)
@@ -283,9 +284,9 @@ func TestTreeNodeRoot(t *testing.T) {
}
func TestTreeNodeLevel(t *testing.T) {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
- c2 := xray.NewTreeNode("containers", "c2")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
+ c2 := xray.NewTreeNode(client.CoGVR, "c2")
n.Add(c1)
n.Add(c2)
@@ -295,9 +296,9 @@ func TestTreeNodeLevel(t *testing.T) {
}
func TestTreeNodeMaxDepth(t *testing.T) {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
- c2 := xray.NewTreeNode("containers", "c2")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
+ c2 := xray.NewTreeNode(client.CoGVR, "c2")
n.Add(c1)
n.Add(c2)
@@ -308,9 +309,9 @@ func TestTreeNodeMaxDepth(t *testing.T) {
// Helpers...
func root1() *xray.TreeNode {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
- c2 := xray.NewTreeNode("containers", "c2")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
+ c2 := xray.NewTreeNode(client.CoGVR, "c2")
n.Add(c1)
n.Add(c2)
@@ -318,23 +319,23 @@ func root1() *xray.TreeNode {
}
func diff1() *xray.TreeNode {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c1")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
n.Add(c1)
return n
}
func root2() *xray.TreeNode {
- c1 := xray.NewTreeNode("containers", "c1")
- s1 := xray.NewTreeNode("v1/secrets", "s1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c1")
+ s1 := xray.NewTreeNode(client.SecGVR, "s1")
c1.Add(s1)
- c2 := xray.NewTreeNode("containers", "c2")
- s2 := xray.NewTreeNode("v1/secrets", "s2")
+ c2 := xray.NewTreeNode(client.CoGVR, "c2")
+ s2 := xray.NewTreeNode(client.SecGVR, "s2")
c2.Add(s2)
- n := xray.NewTreeNode("v1/pods", "default/p1")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
n.Add(c1)
n.Add(c2)
@@ -342,99 +343,99 @@ func root2() *xray.TreeNode {
}
func diff2() *xray.TreeNode {
- n := xray.NewTreeNode("v1/pods", "default/p1")
- c1 := xray.NewTreeNode("containers", "c2")
+ n := xray.NewTreeNode(client.PodGVR, "default/p1")
+ c1 := xray.NewTreeNode(client.CoGVR, "c2")
n.Add(c1)
- s1 := xray.NewTreeNode("v1/secrets", "s2")
+ s1 := xray.NewTreeNode(client.SecGVR, "s2")
c1.Add(s1)
return n
}
func root3() *xray.TreeNode {
- n := xray.NewTreeNode("apps/v1/deployments", "deployments")
+ n := xray.NewTreeNode(client.DpGVR, "deployments")
- ns1 := xray.NewTreeNode("v1/namespaces", "-/default")
+ ns1 := xray.NewTreeNode(client.NsGVR, "-/default")
n.Add(ns1)
{
- d1 := xray.NewTreeNode("apps/v1/deployments", "default/nginx")
+ d1 := xray.NewTreeNode(client.DpGVR, "default/nginx")
ns1.Add(d1)
{
- p1 := xray.NewTreeNode("v1/pods", "default/nginx-6b866d578b-c6tcn")
+ p1 := xray.NewTreeNode(client.PodGVR, "default/nginx-6b866d578b-c6tcn")
d1.Add(p1)
{
- s1 := xray.NewTreeNode("v1/secrets", "default/default-token-rr22g")
+ s1 := xray.NewTreeNode(client.SecGVR, "default/default-token-rr22g")
p1.Add(s1)
}
}
}
- ns2 := xray.NewTreeNode("v1/namespaces", "-/kube-system")
+ ns2 := xray.NewTreeNode(client.NsGVR, "-/kube-system")
n.Add(ns2)
{
- d2 := xray.NewTreeNode("apps/v1/deployments", "kube-system/coredns")
+ d2 := xray.NewTreeNode(client.DpGVR, "kube-system/coredns")
ns2.Add(d2)
{
- p2 := xray.NewTreeNode("v1/pods", "kube-system/coredns-6955765f44-89q2p")
+ p2 := xray.NewTreeNode(client.PodGVR, "kube-system/coredns-6955765f44-89q2p")
d2.Add(p2)
{
- c1 := xray.NewTreeNode("v1/configmaps", "kube-system/coredns")
+ c1 := xray.NewTreeNode(client.CmGVR, "kube-system/coredns")
p2.Add(c1)
- s2 := xray.NewTreeNode("v1/secrets", "kube-system/coredns-token-5cq9j")
+ s2 := xray.NewTreeNode(client.SecGVR, "kube-system/coredns-token-5cq9j")
p2.Add(s2)
}
- p3 := xray.NewTreeNode("v1/pods", "kube-system/coredns-6955765f44-r9j9t")
+ p3 := xray.NewTreeNode(client.PodGVR, "kube-system/coredns-6955765f44-r9j9t")
d2.Add(p3)
{
- c2 := xray.NewTreeNode("v1/configmaps", "kube-system/coredns")
+ c2 := xray.NewTreeNode(client.CmGVR, "kube-system/coredns")
p3.Add(c2)
- s3 := xray.NewTreeNode("v1/secrets", "kube-system/coredns-token-5cq9j")
+ s3 := xray.NewTreeNode(client.SecGVR, "kube-system/coredns-token-5cq9j")
p3.Add(s3)
}
}
- d3 := xray.NewTreeNode("apps/v1/deployments", "kube-system/metrics-server")
+ d3 := xray.NewTreeNode(client.DpGVR, "kube-system/metrics-server")
ns2.Add(d3)
{
- p3 := xray.NewTreeNode("v1/pods", "kube-system/metrics-server-6754dbc9df-88bk4")
+ p3 := xray.NewTreeNode(client.PodGVR, "kube-system/metrics-server-6754dbc9df-88bk4")
d3.Add(p3)
{
- s4 := xray.NewTreeNode("v1/secrets", "kube-system/default-token-thzt8")
+ s4 := xray.NewTreeNode(client.SecGVR, "kube-system/default-token-thzt8")
p3.Add(s4)
}
}
- d4 := xray.NewTreeNode("apps/v1/deployments", "kube-system/nginx-ingress-controller")
+ d4 := xray.NewTreeNode(client.DpGVR, "kube-system/nginx-ingress-controller")
ns2.Add(d4)
{
- p4 := xray.NewTreeNode("v1/pods", "kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55")
+ p4 := xray.NewTreeNode(client.PodGVR, "kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55")
d4.Add(p4)
{
- s5 := xray.NewTreeNode("v1/secrets", "kube-system/nginx-ingress-token-kff5q")
+ s5 := xray.NewTreeNode(client.SecGVR, "kube-system/nginx-ingress-token-kff5q")
p4.Add(s5)
}
}
}
- ns3 := xray.NewTreeNode("v1/namespaces", "-/kubernetes-dashboard")
+ ns3 := xray.NewTreeNode(client.NsGVR, "-/kubernetes-dashboard")
n.Add(ns3)
{
- d5 := xray.NewTreeNode("apps/v1/deployments", "kubernetes-dashboard/dashboard-metrics-scraper")
+ d5 := xray.NewTreeNode(client.DpGVR, "kubernetes-dashboard/dashboard-metrics-scraper")
ns3.Add(d5)
{
- p5 := xray.NewTreeNode("v1/pods", "kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56")
+ p5 := xray.NewTreeNode(client.PodGVR, "kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56")
d5.Add(p5)
{
- s6 := xray.NewTreeNode("v1/secrets", "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4")
+ s6 := xray.NewTreeNode(client.SecGVR, "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4")
p5.Add(s6)
}
}
- d6 := xray.NewTreeNode("apps/v1/deployments", "kubernetes-dashboard/kubernetes-dashboard")
+ d6 := xray.NewTreeNode(client.DpGVR, "kubernetes-dashboard/kubernetes-dashboard")
ns3.Add(d6)
{
- p6 := xray.NewTreeNode("v1/pods", "kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d")
+ p6 := xray.NewTreeNode(client.PodGVR, "kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d")
d6.Add(p6)
{
- s6 := xray.NewTreeNode("v1/secrets", "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4")
+ s6 := xray.NewTreeNode(client.SecGVR, "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4")
p6.Add(s6)
}
}
@@ -444,27 +445,27 @@ func root3() *xray.TreeNode {
}
func diff3() *xray.TreeNode {
- n := xray.NewTreeNode("apps/v1/deployments", "deployments")
- ns2 := xray.NewTreeNode("v1/namespaces", "-/kube-system")
+ n := xray.NewTreeNode(client.DpGVR, "deployments")
+ ns2 := xray.NewTreeNode(client.NsGVR, "-/kube-system")
n.Add(ns2)
{
- d2 := xray.NewTreeNode("apps/v1/deployments", "kube-system/coredns")
+ d2 := xray.NewTreeNode(client.DpGVR, "kube-system/coredns")
ns2.Add(d2)
{
- p2 := xray.NewTreeNode("v1/pods", "kube-system/coredns-6955765f44-89q2p")
+ p2 := xray.NewTreeNode(client.PodGVR, "kube-system/coredns-6955765f44-89q2p")
d2.Add(p2)
{
- c1 := xray.NewTreeNode("v1/configmaps", "kube-system/coredns")
+ c1 := xray.NewTreeNode(client.CmGVR, "kube-system/coredns")
p2.Add(c1)
- s2 := xray.NewTreeNode("v1/secrets", "kube-system/coredns-token-5cq9j")
+ s2 := xray.NewTreeNode(client.SecGVR, "kube-system/coredns-token-5cq9j")
p2.Add(s2)
}
- p3 := xray.NewTreeNode("v1/pods", "kube-system/coredns-6955765f44-r9j9t")
+ p3 := xray.NewTreeNode(client.PodGVR, "kube-system/coredns-6955765f44-r9j9t")
d2.Add(p3)
{
- c2 := xray.NewTreeNode("v1/configmaps", "kube-system/coredns")
+ c2 := xray.NewTreeNode(client.CmGVR, "kube-system/coredns")
p3.Add(c2)
- s3 := xray.NewTreeNode("v1/secrets", "kube-system/coredns-token-5cq9j")
+ s3 := xray.NewTreeNode(client.SecGVR, "kube-system/coredns-token-5cq9j")
p3.Add(s3)
}
}
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 244c986c..00174b88 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,6 +1,6 @@
name: k9s
base: core22
-version: 'v0.40.10'
+version: 'v0.50.0'
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.