Rel v0.50.0 (#3254)
* update deps + add stale issues * springclean client and dao * gvr clean up * perf updates * springclean render * update gvr * perf * add jq like cust views support * springclean config and models * gvr update * perf * springclean perf * springclean ui bits * update ro icon * updage gvr * perf * springclean watch * update linter to v2 * update gha workflows * small clean up * spring clean * move pool to internal * rel notesmine
parent
694c015dc3
commit
e55083ba27
|
|
@ -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 }}
|
||||
|
|
@ -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 }}
|
||||
396
.golangci.yml
396
.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
|
||||
# # - linters: [staticcheck]
|
||||
# # text: "SA2001: empty critical section"
|
||||
|
||||
# # - linters: [err113]
|
||||
# # text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text")
|
||||
# # # Skip goimports check on generated files
|
||||
# # - path: \\.(generated\\.deepcopy|pb)\\.go$
|
||||
# # linters:
|
||||
# # - goimports
|
||||
# # # Skip goheader check on files imported and modified from upstream k8s
|
||||
# # - path: "pkg/ipam/(cidrset|service)/.+\\.go"
|
||||
# # linters:
|
||||
# # - goheader
|
||||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
|||
else
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
endif
|
||||
VERSION ?= v0.40.10
|
||||
VERSION ?= v0.50.0
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
|
||||
|
||||
# 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
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
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
|
||||
|
|
|
|||
4
go.sum
4
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=
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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": {""},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
)
|
||||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "_")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("[-::] ")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue