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-label: "stale"
|
||||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
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."
|
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 }}
|
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"
|
version: "2"
|
||||||
|
|
||||||
run:
|
run:
|
||||||
concurrency: 8
|
allow-parallel-runners: true
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
@ -10,137 +10,305 @@ run:
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: true
|
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:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- gci
|
- gci
|
||||||
- gofmt
|
- gofmt
|
||||||
# - gofumpt
|
|
||||||
- goimports
|
- goimports
|
||||||
# - golines
|
settings:
|
||||||
|
gofmt:
|
||||||
linters:
|
rewrite-rules:
|
||||||
# disable-all: true
|
- pattern: 'interface{}'
|
||||||
enable:
|
replacement: 'any'
|
||||||
- sloglint
|
goimports:
|
||||||
|
local-prefixes:
|
||||||
|
- github.com/golangci/golangci-lint/v2
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
|
||||||
paths:
|
paths:
|
||||||
- third_party$
|
- test/testdata_etc # test files
|
||||||
- builtin$
|
- internal/go # extracted from Go code
|
||||||
- examples$
|
- internal/x # extracted from x/tools code
|
||||||
- \\.(generated\\.deepcopy|pb)\\.go$
|
- pkg/goformatters/gci/internal # extracted from gci code
|
||||||
|
- pkg/goanalysis/runner_checker.go # extracted from x/tools code
|
||||||
|
|
||||||
settings:
|
# linters:
|
||||||
gocyclo:
|
# default: none
|
||||||
min-complexity: 35
|
# enable:
|
||||||
|
# - sloglint
|
||||||
|
|
||||||
govet:
|
# exclusions:
|
||||||
enable:
|
# generated: lax
|
||||||
- nilness
|
# paths:
|
||||||
|
# - third_party$
|
||||||
|
# - builtin$
|
||||||
|
# - examples$
|
||||||
|
# - \\.(generated\\.deepcopy|pb)\\.go$
|
||||||
|
|
||||||
goimports:
|
# settings:
|
||||||
local-prefixes: github.com/derailed/k9s
|
# gocyclo:
|
||||||
|
# min-complexity: 35
|
||||||
|
|
||||||
unused:
|
# govet:
|
||||||
parameters-are-used: true
|
# enable:
|
||||||
local-variables-are-used: true
|
# - nilness
|
||||||
field-writes-are-uses: true
|
|
||||||
post-statements-are-reads: true
|
|
||||||
exported-fields-are-used: true
|
|
||||||
generated-is-used: true
|
|
||||||
|
|
||||||
goheader:
|
# goimports:
|
||||||
values:
|
# local-prefixes: github.com/derailed/k9s
|
||||||
regexp:
|
|
||||||
PROJECT: 'K9s'
|
|
||||||
template: |-
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
Copyright Authors of {{ PROJECT }}
|
|
||||||
|
|
||||||
gosec:
|
# unused:
|
||||||
includes:
|
# parameters-are-used: true
|
||||||
- G402
|
# 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:
|
# goheader:
|
||||||
# Enforce not mixing key-value pairs and attributes.
|
# values:
|
||||||
# https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-mixed-arguments
|
# regexp:
|
||||||
# Default: true
|
# PROJECT: 'K9s'
|
||||||
no-mixed-args: true
|
# template: |-
|
||||||
# Enforce using key-value pairs only (overrides no-mixed-args, incompatible with attr-only).
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-value-pairs-only
|
# Copyright Authors of {{ PROJECT }}
|
||||||
# Default: false
|
|
||||||
kv-only: true
|
# gosec:
|
||||||
# Enforce using attributes only (overrides no-mixed-args, incompatible with kv-only).
|
# includes:
|
||||||
# https://github.com/go-simpler/sloglint?tab=readme-ov-file#attributes-only
|
# - G402
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
issues:
|
|
||||||
|
|
||||||
# default is true. Enables skipping of directories:
|
# issues:
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
|
||||||
# exclude-dirs-use-default: true
|
|
||||||
|
|
||||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
# # default is true. Enables skipping of directories:
|
||||||
# exclude-rules:
|
# # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
# - linters: [staticcheck]
|
# # exclude-dirs-use-default: true
|
||||||
# text: "SA1019" # this is rule for deprecated method
|
|
||||||
|
|
||||||
# - linters: [staticcheck]
|
# # Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
# text: "SA9003: empty branch"
|
# # exclude-rules:
|
||||||
|
# # - linters: [staticcheck]
|
||||||
|
# # text: "SA1019" # this is rule for deprecated method
|
||||||
|
|
||||||
# - linters: [staticcheck]
|
# # - linters: [staticcheck]
|
||||||
# text: "SA2001: empty critical section"
|
# # text: "SA9003: empty branch"
|
||||||
|
|
||||||
# - linters: [err113]
|
# # - linters: [staticcheck]
|
||||||
# text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text")
|
# # text: "SA2001: empty critical section"
|
||||||
# # Skip goimports check on generated files
|
|
||||||
# - path: \\.(generated\\.deepcopy|pb)\\.go$
|
# # - linters: [err113]
|
||||||
# linters:
|
# # text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text")
|
||||||
# - goimports
|
# # # Skip goimports check on generated files
|
||||||
# # Skip goheader check on files imported and modified from upstream k8s
|
# # - path: \\.(generated\\.deepcopy|pb)\\.go$
|
||||||
# - path: "pkg/ipam/(cidrset|service)/.+\\.go"
|
# # linters:
|
||||||
# linters:
|
# # - goimports
|
||||||
# - goheader
|
# # # 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
|
else
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
endif
|
endif
|
||||||
VERSION ?= v0.40.10
|
VERSION ?= v0.50.0
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
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 {
|
if err := config.InitLocs(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func init() {
|
||||||
fmt.Printf("Fail to init k9s logs location %s\n", err)
|
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}
|
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 {
|
if err := config.InitLocs(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -378,7 +378,7 @@ func initK8sFlagCompletion() {
|
||||||
return cfg.AuthInfos
|
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)
|
conn := client.NewConfig(k8sFlags)
|
||||||
if c, err := client.InitConnection(conn, slog.Default()); err == nil {
|
if c, err := client.InitConnection(conn, slog.Default()); err == nil {
|
||||||
if nss, err := c.ValidNamespaceNames(); err == nil {
|
if nss, err := c.ValidNamespaceNames(); err == nil {
|
||||||
|
|
@ -391,7 +391,7 @@ func initK8sFlagCompletion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn {
|
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)
|
conn := client.NewConfig(k8sFlags)
|
||||||
cfg, err := conn.RawConfig()
|
cfg, err := conn.RawConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func versionCmd() *cobra.Command {
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print version/build info",
|
Short: "Print version/build info",
|
||||||
Long: "Print version/build information",
|
Long: "Print version/build information",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(*cobra.Command, []string) {
|
||||||
printVersion(short)
|
printVersion(short)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
github.com/fsnotify/fsnotify v1.8.0
|
||||||
github.com/fvbommel/sortorder v1.1.0
|
github.com/fvbommel/sortorder v1.1.0
|
||||||
github.com/go-errors/errors v1.5.1
|
github.com/go-errors/errors v1.5.1
|
||||||
|
github.com/itchyny/gojq v0.12.17
|
||||||
github.com/lmittmann/tint v1.0.7
|
github.com/lmittmann/tint v1.0.7
|
||||||
github.com/mattn/go-colorable v0.1.14
|
github.com/mattn/go-colorable v0.1.14
|
||||||
github.com/mattn/go-runewidth v0.0.16
|
github.com/mattn/go-runewidth v0.0.16
|
||||||
|
|
@ -192,6 +193,7 @@ require (
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jinzhu/copier v0.4.0 // indirect
|
github.com/jinzhu/copier v0.4.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.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.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
|
|
|
||||||
|
|
@ -88,12 +88,11 @@ func (a *APIClient) ConnectionOK() bool {
|
||||||
return a.connOK
|
return a.connOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSAR(ns, gvr, name string) *authorizationv1.SelfSubjectAccessReview {
|
func makeSAR(ns string, gvr *GVR, name string) *authorizationv1.SelfSubjectAccessReview {
|
||||||
if ns == ClusterScope {
|
if ns == ClusterScope {
|
||||||
ns = BlankNamespace
|
ns = BlankNamespace
|
||||||
}
|
}
|
||||||
spec := NewGVR(gvr)
|
res := gvr.GVR()
|
||||||
res := spec.GVR()
|
|
||||||
return &authorizationv1.SelfSubjectAccessReview{
|
return &authorizationv1.SelfSubjectAccessReview{
|
||||||
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
||||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||||
|
|
@ -101,15 +100,15 @@ func makeSAR(ns, gvr, name string) *authorizationv1.SelfSubjectAccessReview {
|
||||||
Group: res.Group,
|
Group: res.Group,
|
||||||
Version: res.Version,
|
Version: res.Version,
|
||||||
Resource: res.Resource,
|
Resource: res.Resource,
|
||||||
Subresource: spec.SubResource(),
|
Subresource: gvr.SubResource(),
|
||||||
Name: name,
|
Name: name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCacheKey(ns, gvr, n string, vv []string) string {
|
func makeCacheKey(ns string, gvr *GVR, n string, vv []string) string {
|
||||||
return ns + ":" + gvr + ":" + n + "::" + strings.Join(vv, ",")
|
return ns + ":" + gvr.String() + ":" + n + "::" + strings.Join(vv, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveContext returns the current context name.
|
// ActiveContext returns the current context name.
|
||||||
|
|
@ -147,7 +146,7 @@ func (a *APIClient) clearCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanI checks if user has access to a certain resource.
|
// 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() {
|
if !a.getConnOK() {
|
||||||
return false, errors.New("ACCESS -- No API server connection")
|
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 {
|
if !ok || err != nil {
|
||||||
return nil, fmt.Errorf("user not authorized to list all namespaces")
|
return nil, fmt.Errorf("user not authorized to list all namespaces")
|
||||||
}
|
}
|
||||||
|
|
@ -281,8 +280,8 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nns := make(NamespaceNames, len(nn.Items))
|
nns := make(NamespaceNames, len(nn.Items))
|
||||||
for _, n := range nn.Items {
|
for i := range nn.Items {
|
||||||
nns[n.Name] = struct{}{}
|
nns[nn.Items[i].Name] = struct{}{}
|
||||||
}
|
}
|
||||||
a.cache.Add(cacheNSKey, nns, cacheExpiry)
|
a.cache.Add(cacheNSKey, nns, cacheExpiry)
|
||||||
|
|
||||||
|
|
@ -457,11 +456,11 @@ func (a *APIClient) Dial() (kubernetes.Interface, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c, err := kubernetes.NewForConfig(cfg); err != nil {
|
c, err := kubernetes.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
a.setClient(c)
|
|
||||||
}
|
}
|
||||||
|
a.setClient(c)
|
||||||
|
|
||||||
return a.getClient(), nil
|
return a.getClient(), nil
|
||||||
}
|
}
|
||||||
|
|
@ -586,7 +585,7 @@ func (a *APIClient) reset() {
|
||||||
a.setConnOK(true)
|
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)
|
v, found := a.cache.Get(key)
|
||||||
if !found {
|
if !found {
|
||||||
return
|
return
|
||||||
|
|
@ -617,11 +616,11 @@ func (a *APIClient) supportsMetricsResources() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, grp := range apiGroups.Groups {
|
for i := range apiGroups.Groups {
|
||||||
if grp.Name != metricsapi.GroupName {
|
if apiGroups.Groups[i].Name != metricsapi.GroupName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if checkMetricsVersion(grp) {
|
if checkMetricsVersion(&(apiGroups.Groups[i])) {
|
||||||
supported = true
|
supported = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +629,7 @@ func (a *APIClient) supportsMetricsResources() error {
|
||||||
return metricsUnsupportedErr
|
return metricsUnsupportedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMetricsVersion(grp metav1.APIGroup) bool {
|
func checkMetricsVersion(grp *metav1.APIGroup) bool {
|
||||||
for _, v := range grp.Versions {
|
for _, v := range grp.Versions {
|
||||||
for _, supportedVersion := range supportedMetricsAPIVersions {
|
for _, supportedVersion := range supportedMetricsAPIVersions {
|
||||||
if v.Version == supportedVersion {
|
if v.Version == supportedVersion {
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ import (
|
||||||
func TestMakeSAR(t *testing.T) {
|
func TestMakeSAR(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
ns string
|
ns string
|
||||||
gvr GVR
|
gvr *GVR
|
||||||
sar *authorizationv1.SelfSubjectAccessReview
|
sar *authorizationv1.SelfSubjectAccessReview
|
||||||
}{
|
}{
|
||||||
"all-pods": {
|
"all-pods": {
|
||||||
ns: NamespaceAll,
|
ns: NamespaceAll,
|
||||||
gvr: NewGVR("v1/pods"),
|
gvr: PodGVR,
|
||||||
sar: &authorizationv1.SelfSubjectAccessReview{
|
sar: &authorizationv1.SelfSubjectAccessReview{
|
||||||
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
||||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||||
|
|
@ -30,9 +30,10 @@ func TestMakeSAR(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"ns-pods": {
|
"ns-pods": {
|
||||||
ns: "fred",
|
ns: "fred",
|
||||||
gvr: NewGVR("v1/pods"),
|
gvr: PodGVR,
|
||||||
sar: &authorizationv1.SelfSubjectAccessReview{
|
sar: &authorizationv1.SelfSubjectAccessReview{
|
||||||
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
||||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||||
|
|
@ -43,9 +44,10 @@ func TestMakeSAR(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"clusterscope-ns": {
|
"clusterscope-ns": {
|
||||||
ns: ClusterScope,
|
ns: ClusterScope,
|
||||||
gvr: NewGVR("v1/namespaces"),
|
gvr: NsGVR,
|
||||||
sar: &authorizationv1.SelfSubjectAccessReview{
|
sar: &authorizationv1.SelfSubjectAccessReview{
|
||||||
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
||||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||||
|
|
@ -55,6 +57,7 @@ func TestMakeSAR(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"subres-pods": {
|
"subres-pods": {
|
||||||
ns: "fred",
|
ns: "fred",
|
||||||
gvr: NewGVR("v1/pods:logs"),
|
gvr: NewGVR("v1/pods:logs"),
|
||||||
|
|
@ -74,7 +77,7 @@ func TestMakeSAR(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.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"
|
const key = "fred"
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
key string
|
key string
|
||||||
val interface{}
|
val any
|
||||||
found, actual, sleep bool
|
found, actual, sleep bool
|
||||||
}{
|
}{
|
||||||
"setTrue": {
|
"setTrue": {
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ func (c *Config) clientConfig() clientcmd.ClientConfig {
|
||||||
return c.flags.ToRawKubeConfigLoader()
|
return c.flags.ToRawKubeConfigLoader()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) reset() {}
|
func (*Config) reset() {}
|
||||||
|
|
||||||
// SwitchContext changes the kubeconfig context to a new cluster.
|
// SwitchContext changes the kubeconfig context to a new cluster.
|
||||||
func (c *Config) SwitchContext(name string) error {
|
func (c *Config) SwitchContext(name string) error {
|
||||||
|
|
@ -221,17 +221,17 @@ func (c *Config) DelContext(n string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameContext renames a context.
|
// 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()
|
cfg, err := c.RawConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cfg.Contexts[new]; ok {
|
if _, ok := cfg.Contexts[newCtx]; ok {
|
||||||
return fmt.Errorf("context with name %s already exists", new)
|
return fmt.Errorf("context with name %s already exists", newCtx)
|
||||||
}
|
}
|
||||||
cfg.Contexts[new] = cfg.Contexts[old]
|
cfg.Contexts[newCtx] = cfg.Contexts[oldCtx]
|
||||||
delete(cfg.Contexts, old)
|
delete(cfg.Contexts, oldCtx)
|
||||||
acc, err := c.ConfigAccess()
|
acc, err := c.ConfigAccess()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -243,8 +243,8 @@ func (c *Config) RenameContext(old string, new string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if current == old {
|
if current == oldCtx {
|
||||||
return c.SwitchContext(new)
|
return c.SwitchContext(newCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -344,9 +344,9 @@ func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func isSet(s *string) bool {
|
func isSet(s *string) bool {
|
||||||
return s != nil && len(*s) != 0
|
return s != nil && *s != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func areSet(s *[]string) bool {
|
func areSet(ss *[]string) bool {
|
||||||
return s != nil && len(*s) != 0
|
return ss != nil && len(*ss) != 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,12 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var kubeConfig = "./testdata/config"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
slog.SetDefault(slog.New(slog.DiscardHandler))
|
slog.SetDefault(slog.New(slog.DiscardHandler))
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +48,6 @@ func TestCallTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigCurrentContext(t *testing.T) {
|
func TestConfigCurrentContext(t *testing.T) {
|
||||||
kubeConfig := "./testdata/config"
|
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
context string
|
context string
|
||||||
e string
|
e string
|
||||||
|
|
@ -70,14 +71,14 @@ func TestConfigCurrentContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
cfg := client.NewConfig(flags)
|
cfg := client.NewConfig(flags)
|
||||||
ctx, err := cfg.CurrentContextName()
|
ctx, err := cfg.CurrentContextName()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.e, ctx)
|
assert.Equal(t, u.e, ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigCurrentCluster(t *testing.T) {
|
func TestConfigCurrentCluster(t *testing.T) {
|
||||||
name, kubeConfig := "blee", "./testdata/config"
|
name := "blee"
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
flags *genericclioptions.ConfigFlags
|
flags *genericclioptions.ConfigFlags
|
||||||
cluster string
|
cluster string
|
||||||
|
|
@ -102,14 +103,14 @@ func TestConfigCurrentCluster(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
cfg := client.NewConfig(u.flags)
|
cfg := client.NewConfig(u.flags)
|
||||||
ct, err := cfg.CurrentClusterName()
|
ct, err := cfg.CurrentClusterName()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.cluster, ct)
|
assert.Equal(t, u.cluster, ct)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigCurrentUser(t *testing.T) {
|
func TestConfigCurrentUser(t *testing.T) {
|
||||||
name, kubeConfig := "blee", "./testdata/config"
|
name := "blee"
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
flags *genericclioptions.ConfigFlags
|
flags *genericclioptions.ConfigFlags
|
||||||
user string
|
user string
|
||||||
|
|
@ -129,14 +130,13 @@ func TestConfigCurrentUser(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
cfg := client.NewConfig(u.flags)
|
cfg := client.NewConfig(u.flags)
|
||||||
ctx, err := cfg.CurrentUserName()
|
ctx, err := cfg.CurrentUserName()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.user, ctx)
|
assert.Equal(t, u.user, ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigCurrentNamespace(t *testing.T) {
|
func TestConfigCurrentNamespace(t *testing.T) {
|
||||||
kubeConfig := "./testdata/config"
|
|
||||||
bleeNS, bleeCTX := "blee", "blee"
|
bleeNS, bleeCTX := "blee", "blee"
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
flags *genericclioptions.ConfigFlags
|
flags *genericclioptions.ConfigFlags
|
||||||
|
|
@ -162,7 +162,7 @@ func TestConfigCurrentNamespace(t *testing.T) {
|
||||||
cfg := client.NewConfig(u.flags)
|
cfg := client.NewConfig(u.flags)
|
||||||
ns, err := cfg.CurrentNamespaceName()
|
ns, err := cfg.CurrentNamespaceName()
|
||||||
if ns != "" {
|
if ns != "" {
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, u.namespace, ns)
|
assert.Equal(t, u.namespace, ns)
|
||||||
})
|
})
|
||||||
|
|
@ -170,7 +170,6 @@ func TestConfigCurrentNamespace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigGetContext(t *testing.T) {
|
func TestConfigGetContext(t *testing.T) {
|
||||||
kubeConfig := "./testdata/config"
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
cluster string
|
cluster string
|
||||||
err error
|
err error
|
||||||
|
|
@ -201,7 +200,7 @@ func TestConfigGetContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigSwitchContext(t *testing.T) {
|
func TestConfigSwitchContext(t *testing.T) {
|
||||||
cluster, kubeConfig := "duh", "./testdata/config"
|
cluster := "duh"
|
||||||
flags := genericclioptions.ConfigFlags{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeConfig,
|
||||||
Context: &cluster,
|
Context: &cluster,
|
||||||
|
|
@ -209,14 +208,14 @@ func TestConfigSwitchContext(t *testing.T) {
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
err := cfg.SwitchContext("blee")
|
err := cfg.SwitchContext("blee")
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
ctx, err := cfg.CurrentContextName()
|
ctx, err := cfg.CurrentContextName()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "blee", ctx)
|
assert.Equal(t, "blee", ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigAccess(t *testing.T) {
|
func TestConfigAccess(t *testing.T) {
|
||||||
context, kubeConfig := "duh", "./testdata/config"
|
context := "duh"
|
||||||
flags := genericclioptions.ConfigFlags{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeConfig,
|
||||||
Context: &context,
|
Context: &context,
|
||||||
|
|
@ -224,12 +223,12 @@ func TestConfigAccess(t *testing.T) {
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
acc, err := cfg.ConfigAccess()
|
acc, err := cfg.ConfigAccess()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, len(acc.GetDefaultFilename()) > 0)
|
assert.NotEmpty(t, acc.GetDefaultFilename())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigContextNames(t *testing.T) {
|
func TestConfigContextNames(t *testing.T) {
|
||||||
cluster, kubeConfig := "duh", "./testdata/config"
|
cluster := "duh"
|
||||||
flags := genericclioptions.ConfigFlags{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeConfig,
|
||||||
Context: &cluster,
|
Context: &cluster,
|
||||||
|
|
@ -237,12 +236,12 @@ func TestConfigContextNames(t *testing.T) {
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
cc, err := cfg.ContextNames()
|
cc, err := cfg.ContextNames()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 3, len(cc))
|
assert.Len(t, cc, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigContexts(t *testing.T) {
|
func TestConfigContexts(t *testing.T) {
|
||||||
context, kubeConfig := "duh", "./testdata/config"
|
context := "duh"
|
||||||
flags := genericclioptions.ConfigFlags{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeConfig,
|
||||||
Context: &context,
|
Context: &context,
|
||||||
|
|
@ -250,39 +249,38 @@ func TestConfigContexts(t *testing.T) {
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
cc, err := cfg.Contexts()
|
cc, err := cfg.Contexts()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 3, len(cc))
|
assert.Len(t, cc, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigDelContext(t *testing.T) {
|
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{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeCfg,
|
||||||
Context: &context,
|
Context: &context,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
err := cfg.DelContext("fred")
|
err := cfg.DelContext("fred")
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cc, err := cfg.ContextNames()
|
cc, err := cfg.ContextNames()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(cc))
|
assert.Len(t, cc, 1)
|
||||||
_, ok := cc["blee"]
|
_, ok := cc["blee"]
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigRestConfig(t *testing.T) {
|
func TestConfigRestConfig(t *testing.T) {
|
||||||
kubeConfig := "./testdata/config"
|
|
||||||
flags := genericclioptions.ConfigFlags{
|
flags := genericclioptions.ConfigFlags{
|
||||||
KubeConfig: &kubeConfig,
|
KubeConfig: &kubeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
rc, err := cfg.RESTConfig()
|
rc, err := cfg.RESTConfig()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "https://localhost:3002", rc.Host)
|
assert.Equal(t, "https://localhost:3002", rc.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,12 +292,12 @@ func TestConfigBadConfig(t *testing.T) {
|
||||||
|
|
||||||
cfg := client.NewConfig(&flags)
|
cfg := client.NewConfig(&flags)
|
||||||
_, err := cfg.RESTConfig()
|
_, err := cfg.RESTConfig()
|
||||||
assert.NotNil(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func cp(src string, dst string) error {
|
func cp(src, dst string) error {
|
||||||
data, err := os.ReadFile(src)
|
data, err := os.ReadFile(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/slogs"
|
"github.com/derailed/k9s/internal/slogs"
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
|
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var NoGVR = GVR{}
|
var NoGVR = &GVR{}
|
||||||
|
|
||||||
// GVR represents a kubernetes resource schema as a string.
|
// GVR represents a kubernetes resource schema as a string.
|
||||||
// Format is group/version/resources:subresource.
|
// Format is group/version/resources:subresource.
|
||||||
|
|
@ -23,12 +24,29 @@ type GVR struct {
|
||||||
raw, g, v, r, sr string
|
raw, g, v, r, sr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGVR builds a new gvr from a group, version, resource.
|
type gvrCache map[string]*GVR
|
||||||
func NewGVR(gvr string) GVR {
|
|
||||||
var g, v, r, sr string
|
|
||||||
|
|
||||||
tokens := strings.Split(gvr, ":")
|
func (c gvrCache) add(gvr *GVR) {
|
||||||
raw := 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 {
|
if len(tokens) == 2 {
|
||||||
raw, sr = tokens[0], tokens[1]
|
raw, sr = tokens[0], tokens[1]
|
||||||
}
|
}
|
||||||
|
|
@ -41,34 +59,62 @@ func NewGVR(gvr string) GVR {
|
||||||
case 1:
|
case 1:
|
||||||
r = tokens[0]
|
r = tokens[0]
|
||||||
default:
|
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.
|
// NewGVRFromMeta builds a gvr from resource metadata.
|
||||||
func NewGVRFromMeta(a metav1.APIResource) GVR {
|
func NewGVRFromMeta(a *metav1.APIResource) *GVR {
|
||||||
return GVR{
|
return NewGVR(path.Join(a.Group, a.Version, a.Name))
|
||||||
raw: path.Join(a.Group, a.Version, a.Name),
|
}
|
||||||
g: a.Group,
|
|
||||||
v: a.Version,
|
// NewGVRFromCRD builds a gvr from a custom resource definition.
|
||||||
r: a.Name,
|
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.
|
// 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))
|
return NewGVR(path.Join(gv, r))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FQN returns a fully qualified resource name.
|
// 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)
|
return path.Join(g.AsResourceName(), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
|
// 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 == "" {
|
if g.g == "" {
|
||||||
return g.r
|
return g.r
|
||||||
}
|
}
|
||||||
|
|
@ -77,17 +123,17 @@ func (g GVR) AsResourceName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubResource returns a sub resource if available.
|
// SubResource returns a sub resource if available.
|
||||||
func (g GVR) SubResource() string {
|
func (g *GVR) SubResource() string {
|
||||||
return g.sr
|
return g.sr
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns gvr as string.
|
// String returns gvr as string.
|
||||||
func (g GVR) String() string {
|
func (g *GVR) String() string {
|
||||||
return g.raw
|
return g.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
// GV returns the group version scheme representation.
|
// GV returns the group version scheme representation.
|
||||||
func (g GVR) GV() schema.GroupVersion {
|
func (g *GVR) GV() schema.GroupVersion {
|
||||||
return schema.GroupVersion{
|
return schema.GroupVersion{
|
||||||
Group: g.g,
|
Group: g.g,
|
||||||
Version: g.v,
|
Version: g.v,
|
||||||
|
|
@ -95,7 +141,7 @@ func (g GVR) GV() schema.GroupVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVK returns a full schema representation.
|
// GVK returns a full schema representation.
|
||||||
func (g GVR) GVK() schema.GroupVersionKind {
|
func (g *GVR) GVK() schema.GroupVersionKind {
|
||||||
return schema.GroupVersionKind{
|
return schema.GroupVersionKind{
|
||||||
Group: g.G(),
|
Group: g.G(),
|
||||||
Version: g.V(),
|
Version: g.V(),
|
||||||
|
|
@ -104,7 +150,7 @@ func (g GVR) GVK() schema.GroupVersionKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVR returns a full schema representation.
|
// GVR returns a full schema representation.
|
||||||
func (g GVR) GVR() schema.GroupVersionResource {
|
func (g *GVR) GVR() schema.GroupVersionResource {
|
||||||
return schema.GroupVersionResource{
|
return schema.GroupVersionResource{
|
||||||
Group: g.G(),
|
Group: g.G(),
|
||||||
Version: g.V(),
|
Version: g.V(),
|
||||||
|
|
@ -113,7 +159,7 @@ func (g GVR) GVR() schema.GroupVersionResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVSub returns group vervion sub path.
|
// GVSub returns group vervion sub path.
|
||||||
func (g GVR) GVSub() string {
|
func (g *GVR) GVSub() string {
|
||||||
if g.G() == "" {
|
if g.G() == "" {
|
||||||
return g.V()
|
return g.V()
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +168,7 @@ func (g GVR) GVSub() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GR returns a full schema representation.
|
// GR returns a full schema representation.
|
||||||
func (g GVR) GR() *schema.GroupResource {
|
func (g *GVR) GR() *schema.GroupResource {
|
||||||
return &schema.GroupResource{
|
return &schema.GroupResource{
|
||||||
Group: g.G(),
|
Group: g.G(),
|
||||||
Resource: g.R(),
|
Resource: g.R(),
|
||||||
|
|
@ -130,32 +176,32 @@ func (g GVR) GR() *schema.GroupResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// V returns the resource version.
|
// V returns the resource version.
|
||||||
func (g GVR) V() string {
|
func (g *GVR) V() string {
|
||||||
return g.v
|
return g.v
|
||||||
}
|
}
|
||||||
|
|
||||||
// RG returns the resource and group.
|
// RG returns the resource and group.
|
||||||
func (g GVR) RG() (string, string) {
|
func (g *GVR) RG() (resource, group string) {
|
||||||
return g.r, g.g
|
return g.r, g.g
|
||||||
}
|
}
|
||||||
|
|
||||||
// R returns the resource name.
|
// R returns the resource name.
|
||||||
func (g GVR) R() string {
|
func (g *GVR) R() string {
|
||||||
return g.r
|
return g.r
|
||||||
}
|
}
|
||||||
|
|
||||||
// G returns the resource group name.
|
// G returns the resource group name.
|
||||||
func (g GVR) G() string {
|
func (g *GVR) G() string {
|
||||||
return g.g
|
return g.g
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDecodable checks if the k8s resource has a decodable view
|
// 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"
|
return g.GVK().Kind == "secrets"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVRs represents a collection of gvr.
|
// GVRs represents a collection of gvr.
|
||||||
type GVRs []GVR
|
type GVRs []*GVR
|
||||||
|
|
||||||
// Len returns the list size.
|
// Len returns the list size.
|
||||||
func (g GVRs) Len() int {
|
func (g GVRs) Len() int {
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@ import (
|
||||||
|
|
||||||
func TestGVRSort(t *testing.T) {
|
func TestGVRSort(t *testing.T) {
|
||||||
gg := client.GVRs{
|
gg := client.GVRs{
|
||||||
client.NewGVR("v1/pods"),
|
client.PodGVR,
|
||||||
client.NewGVR("v1/services"),
|
client.SvcGVR,
|
||||||
client.NewGVR("apps/v1/deployments"),
|
client.DpGVR,
|
||||||
}
|
}
|
||||||
sort.Sort(gg)
|
sort.Sort(gg)
|
||||||
assert.Equal(t, client.GVRs{
|
assert.Equal(t, client.GVRs{
|
||||||
client.NewGVR("v1/pods"),
|
client.PodGVR,
|
||||||
client.NewGVR("v1/services"),
|
client.SvcGVR,
|
||||||
client.NewGVR("apps/v1/deployments"),
|
client.DpGVR,
|
||||||
}, gg)
|
}, gg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,9 +54,9 @@ func TestGVR(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e schema.GroupVersionResource
|
e schema.GroupVersionResource
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
|
"full": {client.DpGVR.String(), schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
|
||||||
"core": {"v1/pods", schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
|
"core": {client.PodGVR.String(), schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
|
||||||
"bork": {"users", schema.GroupVersionResource{Resource: "users"}},
|
"bork": {client.UsrGVR.String(), schema.GroupVersionResource{Resource: "users"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
|
@ -72,9 +72,9 @@ func TestAsGV(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e schema.GroupVersion
|
e schema.GroupVersion
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", schema.GroupVersion{Group: "apps", Version: "v1"}},
|
"full": {client.DpGVR.String(), schema.GroupVersion{Group: "apps", Version: "v1"}},
|
||||||
"core": {"v1/pods", schema.GroupVersion{Version: "v1"}},
|
"core": {client.PodGVR.String(), schema.GroupVersion{Version: "v1"}},
|
||||||
"bork": {"users", schema.GroupVersion{}},
|
"bork": {client.UsrGVR.String(), schema.GroupVersion{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
|
@ -90,8 +90,8 @@ func TestNewGVR(t *testing.T) {
|
||||||
g, v, r string
|
g, v, r string
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps", "v1", "deployments", "apps/v1/deployments"},
|
"full": {"apps", "v1", "deployments", client.DpGVR.String()},
|
||||||
"core": {"", "v1", "pods", "v1/pods"},
|
"core": {"", "v1", "pods", client.PodGVR.String()},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
|
@ -107,9 +107,9 @@ func TestGVRAsResourceName(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", "deployments.v1.apps"},
|
"full": {client.DpGVR.String(), "deployments.v1.apps"},
|
||||||
"core": {"v1/pods", "pods"},
|
"core": {client.PodGVR.String(), "pods"},
|
||||||
"k9s": {"users", "users"},
|
"k9s": {client.UsrGVR.String(), "users"},
|
||||||
"empty": {"", ""},
|
"empty": {"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,9 +126,9 @@ func TestToR(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", "deployments"},
|
"full": {client.DpGVR.String(), "deployments"},
|
||||||
"core": {"v1/pods", "pods"},
|
"core": {client.PodGVR.String(), "pods"},
|
||||||
"k9s": {"users", "users"},
|
"k9s": {client.UsrGVR.String(), "users"},
|
||||||
"empty": {"", ""},
|
"empty": {"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,9 +145,9 @@ func TestToG(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", "apps"},
|
"full": {client.DpGVR.String(), "apps"},
|
||||||
"core": {"v1/pods", ""},
|
"core": {client.PodGVR.String(), ""},
|
||||||
"k9s": {"users", ""},
|
"k9s": {client.UsrGVR.String(), ""},
|
||||||
"empty": {"", ""},
|
"empty": {"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,9 +164,9 @@ func TestToV(t *testing.T) {
|
||||||
gvr string
|
gvr string
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", "v1"},
|
"full": {client.DpGVR.String(), "v1"},
|
||||||
"core": {"v1beta1/pods", "v1beta1"},
|
"core": {"v1beta1/pods", "v1beta1"},
|
||||||
"k9s": {"users", ""},
|
"k9s": {client.UsrGVR.String(), ""},
|
||||||
"empty": {"", ""},
|
"empty": {"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,9 +182,9 @@ func TestToString(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
gvr string
|
gvr string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments"},
|
"full": {client.DpGVR.String()},
|
||||||
"core": {"v1beta1/pods"},
|
"core": {"v1beta1/pods"},
|
||||||
"k9s": {"users"},
|
"k9s": {client.UsrGVR.String()},
|
||||||
"empty": {""},
|
"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 {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, 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 {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, 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.
|
// Namespaced converts a resource path to namespace and resource name.
|
||||||
func Namespaced(p string) (string, string) {
|
func Namespaced(p string) (ns, name string) {
|
||||||
ns, n := path.Split(p)
|
ns, name = path.Split(p)
|
||||||
|
|
||||||
return strings.Trim(ns, "/"), n
|
return strings.Trim(ns, "/"), name
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoFQN returns a fully qualified container 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
|
return MetaFQN(m) + ":" + co
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ func FQN(ns, n string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetaFQN returns a fully qualified resource name.
|
// MetaFQN returns a fully qualified resource name.
|
||||||
func MetaFQN(m metav1.ObjectMeta) string {
|
func MetaFQN(m *metav1.ObjectMeta) string {
|
||||||
if m.Namespace == "" {
|
if m.Namespace == "" {
|
||||||
return FQN(ClusterScope, m.Name)
|
return FQN(ClusterScope, m.Name)
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +90,9 @@ func mustHomeDir() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toHostDir(host string) 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, "_")
|
return toFileName.ReplaceAllString(h, "_")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ import (
|
||||||
const (
|
const (
|
||||||
mxCacheSize = 100
|
mxCacheSize = 100
|
||||||
mxCacheExpiry = 1 * time.Minute
|
mxCacheExpiry = 1 * time.Minute
|
||||||
podMXGVR = "metrics.k8s.io/v1beta1/pods"
|
|
||||||
nodeMXGVR = "metrics.k8s.io/v1beta1/nodes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MetricsDial tracks global metric server handle.
|
// MetricsDial tracks global metric server handle.
|
||||||
|
|
@ -57,22 +55,22 @@ func NewMetricsServer(c Connection) *MetricsServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterLoad retrieves all cluster nodes metrics.
|
// 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 {
|
if nos == nil || nmx == nil {
|
||||||
return fmt.Errorf("invalid node or node metrics lists")
|
return fmt.Errorf("invalid node or node metrics lists")
|
||||||
}
|
}
|
||||||
nodeMetrics := make(NodesMetrics, len(nos.Items))
|
nodeMetrics := make(NodesMetrics, len(nos.Items))
|
||||||
for _, no := range nos.Items {
|
for i := range nos.Items {
|
||||||
nodeMetrics[no.Name] = NodeMetrics{
|
nodeMetrics[nos.Items[i].Name] = NodeMetrics{
|
||||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
AllocatableCPU: nos.Items[i].Status.Allocatable.Cpu().MilliValue(),
|
||||||
AllocatableMEM: no.Status.Allocatable.Memory().Value(),
|
AllocatableMEM: nos.Items[i].Status.Allocatable.Memory().Value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, mx := range nmx.Items {
|
for i := range nmx.Items {
|
||||||
if node, ok := nodeMetrics[mx.Name]; ok {
|
if node, ok := nodeMetrics[nmx.Items[i].Name]; ok {
|
||||||
node.CurrentCPU = mx.Usage.Cpu().MilliValue()
|
node.CurrentCPU = nmx.Items[i].Usage.Cpu().MilliValue()
|
||||||
node.CurrentMEM = mx.Usage.Memory().Value()
|
node.CurrentMEM = nmx.Items[i].Usage.Memory().Value()
|
||||||
nodeMetrics[mx.Name] = node
|
nodeMetrics[nmx.Items[i].Name] = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +86,7 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
|
||||||
return nil
|
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() {
|
if !m.HasMetrics() {
|
||||||
return errors.New("no metrics-server detected on cluster")
|
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.
|
// 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 {
|
if nodes == nil || metrics == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, no := range nodes.Items {
|
for i := range nodes.Items {
|
||||||
mmx[no.Name] = NodeMetrics{
|
mmx[nodes.Items[i].Name] = NodeMetrics{
|
||||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
AllocatableCPU: nodes.Items[i].Status.Allocatable.Cpu().MilliValue(),
|
||||||
AllocatableMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
AllocatableMEM: ToMB(nodes.Items[i].Status.Allocatable.Memory().Value()),
|
||||||
AllocatableEphemeral: ToMB(no.Status.Allocatable.StorageEphemeral().Value()),
|
AllocatableEphemeral: ToMB(nodes.Items[i].Status.Allocatable.StorageEphemeral().Value()),
|
||||||
TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
|
TotalCPU: nodes.Items[i].Status.Capacity.Cpu().MilliValue(),
|
||||||
TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
|
TotalMEM: ToMB(nodes.Items[i].Status.Capacity.Memory().Value()),
|
||||||
TotalEphemeral: ToMB(no.Status.Capacity.StorageEphemeral().Value()),
|
TotalEphemeral: ToMB(nodes.Items[i].Status.Capacity.StorageEphemeral().Value()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range metrics.Items {
|
for i := range metrics.Items {
|
||||||
if mx, ok := mmx[c.Name]; ok {
|
mx, ok := mmx[metrics.Items[i].Name]
|
||||||
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
|
if !ok {
|
||||||
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
|
continue
|
||||||
mx.AvailableCPU = mx.AllocatableCPU - mx.CurrentCPU
|
|
||||||
mx.AvailableMEM = mx.AllocatableMEM - mx.CurrentMEM
|
|
||||||
mmx[c.Name] = mx
|
|
||||||
}
|
}
|
||||||
|
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"
|
const msg = "user is not authorized to list node metrics"
|
||||||
|
|
||||||
mx := new(mv1beta1.NodeMetricsList)
|
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
|
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"
|
const msg = "user is not authorized to list node metrics"
|
||||||
|
|
||||||
mx := new(mv1beta1.NodeMetrics)
|
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
|
return mx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
|
||||||
if ns == NamespaceAll {
|
if ns == NamespaceAll {
|
||||||
ns = BlankNamespace
|
ns = BlankNamespace
|
||||||
}
|
}
|
||||||
if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
|
if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
|
||||||
return mx, err
|
return mx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,7 +273,7 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be
|
||||||
if ns == NamespaceAll {
|
if ns == NamespaceAll {
|
||||||
ns = BlankNamespace
|
ns = BlankNamespace
|
||||||
}
|
}
|
||||||
if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
|
if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
|
||||||
return mx, err
|
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.
|
// 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 {
|
if pods == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute all pod's containers metrics.
|
// Compute all pod's containers metrics.
|
||||||
for _, p := range pods.Items {
|
for i := range pods.Items {
|
||||||
var mx PodMetrics
|
var mx PodMetrics
|
||||||
for _, c := range p.Containers {
|
for _, c := range pods.Items[i].Containers {
|
||||||
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
|
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
|
||||||
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
|
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)
|
mmx := make(client.PodsMetrics)
|
||||||
m.PodsMetrics(u.metrics, mmx)
|
m.PodsMetrics(u.metrics, mmx)
|
||||||
|
|
||||||
assert.Equal(t, u.eSize, len(mmx))
|
assert.Len(t, mmx, u.eSize)
|
||||||
if u.eSize == 0 {
|
if u.eSize == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ func BenchmarkPodsMetrics(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for n := 0; n < b.N; n++ {
|
for range b.N {
|
||||||
m.PodsMetrics(&metrics, mmx)
|
m.PodsMetrics(&metrics, mmx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +175,7 @@ func TestNodesMetrics(t *testing.T) {
|
||||||
mmx := make(client.NodesMetrics)
|
mmx := make(client.NodesMetrics)
|
||||||
m.NodesMetrics(u.nodes, u.metrics, mmx)
|
m.NodesMetrics(u.nodes, u.metrics, mmx)
|
||||||
|
|
||||||
assert.Equal(t, u.eSize, len(mmx))
|
assert.Len(t, mmx, u.eSize)
|
||||||
if u.eSize == 0 {
|
if u.eSize == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +206,7 @@ func BenchmarkNodesMetrics(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for n := 0; n < b.N; n++ {
|
for range b.N {
|
||||||
m.NodesMetrics(&nodes, &metrics, mmx)
|
m.NodesMetrics(&nodes, &metrics, mmx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +290,7 @@ func BenchmarkClusterLoad(b *testing.B) {
|
||||||
var mx client.ClusterMetrics
|
var mx client.ClusterMetrics
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for n := 0; n < b.N; n++ {
|
for range b.N {
|
||||||
_ = m.ClusterLoad(&nodes, &metrics, &mx)
|
_ = 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.
|
// Authorizer checks what a user can or cannot do to a resource.
|
||||||
type Authorizer interface {
|
type Authorizer interface {
|
||||||
// CanI returns true if the user can use these actions for a given resource.
|
// 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.
|
// Connection represents a Kubernetes apiserver connection.
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestHighlight(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, 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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/derailed/k9s/internal/config/json"
|
"github.com/derailed/k9s/internal/config/json"
|
||||||
"github.com/derailed/k9s/internal/slogs"
|
"github.com/derailed/k9s/internal/slogs"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alias tracks shortname to GVR mappings.
|
// 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.
|
// 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.
|
// Aliases represents a collection of aliases.
|
||||||
type Aliases struct {
|
type Aliases struct {
|
||||||
|
|
@ -35,29 +38,17 @@ func NewAliases() *Aliases {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Aliases) AliasesFor(s string) []string {
|
func (a *Aliases) AliasesFor(gvr *client.GVR) sets.Set[string] {
|
||||||
aa := make([]string, 0, 10)
|
|
||||||
|
|
||||||
a.mx.RLock()
|
a.mx.RLock()
|
||||||
defer a.mx.RUnlock()
|
defer a.mx.RUnlock()
|
||||||
for k, v := range a.Alias {
|
|
||||||
if v == s {
|
ss := sets.New[string]()
|
||||||
aa = append(aa, k)
|
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
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,24 +80,27 @@ func (a *Aliases) Clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves an alias.
|
// Get retrieves an alias.
|
||||||
func (a *Aliases) Get(k string) (string, bool) {
|
func (a *Aliases) Get(alias string) (*client.GVR, bool) {
|
||||||
a.mx.RLock()
|
a.mx.RLock()
|
||||||
defer a.mx.RUnlock()
|
defer a.mx.RUnlock()
|
||||||
|
|
||||||
v, ok := a.Alias[k]
|
gvr, ok := a.Alias[alias]
|
||||||
return v, ok
|
if ok && !gvr.IsK8sRes() {
|
||||||
|
if rgvr, found := a.Alias[gvr.String()]; found {
|
||||||
|
return rgvr, found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gvr, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define declares a new alias.
|
// 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()
|
a.mx.Lock()
|
||||||
defer a.mx.Unlock()
|
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 {
|
for _, alias := range aliases {
|
||||||
if _, ok := a.Alias[alias]; !ok && alias != "" {
|
if _, ok := a.Alias[alias]; !ok && alias != "" {
|
||||||
a.Alias[alias] = gvr
|
a.Alias[alias] = gvr
|
||||||
|
|
@ -117,12 +111,10 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
|
||||||
// Load K9s aliases.
|
// Load K9s aliases.
|
||||||
func (a *Aliases) Load(path string) error {
|
func (a *Aliases) Load(path string) error {
|
||||||
a.loadDefaultAliases()
|
a.loadDefaultAliases()
|
||||||
|
|
||||||
f, err := EnsureAliasesCfgFile()
|
f, err := EnsureAliasesCfgFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Unable to gen config aliases", slogs.Error, err)
|
slog.Error("Unable to gen config aliases", slogs.Error, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load global alias file
|
// load global alias file
|
||||||
if err := a.LoadFile(f); err != nil {
|
if err := a.LoadFile(f); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -132,11 +124,18 @@ func (a *Aliases) Load(path string) error {
|
||||||
return a.LoadFile(path)
|
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.
|
// LoadFile loads alias from a given file.
|
||||||
func (a *Aliases) LoadFile(path string) error {
|
func (a *Aliases) LoadFile(path string) error {
|
||||||
if path == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -149,23 +148,23 @@ func (a *Aliases) LoadFile(path string) error {
|
||||||
slog.Warn("Aliases validation failed", slogs.Error, err)
|
slog.Warn("Aliases validation failed", slogs.Error, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var aa Aliases
|
var aa aliases
|
||||||
if err := yaml.Unmarshal(bb, &aa); err != nil {
|
if err := yaml.Unmarshal(bb, &aa); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.mx.Lock()
|
a.mx.Lock()
|
||||||
defer a.mx.Unlock()
|
defer a.mx.Unlock()
|
||||||
for k, v := range aa.Alias {
|
for alias, cmd := range aa.Alias {
|
||||||
a.Alias[k] = v
|
a.Alias[alias] = client.NewGVR(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Aliases) declare(key string, aliases ...string) {
|
func (a *Aliases) declare(gvr *client.GVR, aliases ...string) {
|
||||||
a.Alias[key] = key
|
a.Alias[gvr.String()] = gvr
|
||||||
for _, alias := range aliases {
|
for _, alias := range aliases {
|
||||||
a.Alias[alias] = key
|
a.Alias[alias] = gvr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,20 +172,20 @@ func (a *Aliases) loadDefaultAliases() {
|
||||||
a.mx.Lock()
|
a.mx.Lock()
|
||||||
defer a.mx.Unlock()
|
defer a.mx.Unlock()
|
||||||
|
|
||||||
a.declare("help", "h", "?")
|
a.declare(client.HlpGVR, "h", "?")
|
||||||
a.declare("quit", "q", "q!", "qa", "Q")
|
a.declare(client.QGVR, "q", "q!", "qa", "Q")
|
||||||
a.declare("aliases", "alias", "a")
|
a.declare(client.AliGVR, "alias", "a")
|
||||||
a.declare("helm", "charts", "chart", "hm")
|
a.declare(client.HmGVR, "charts", "chart", "hm")
|
||||||
a.declare("dir", "d")
|
a.declare(client.DirGVR, "dir", "d")
|
||||||
a.declare("contexts", "context", "ctx")
|
a.declare(client.CtGVR, "context", "ctx")
|
||||||
a.declare("users", "user", "usr")
|
a.declare(client.UsrGVR, "user", "usr")
|
||||||
a.declare("groups", "group", "grp")
|
a.declare(client.GrpGVR, "group", "grp")
|
||||||
a.declare("portforwards", "portforward", "pf")
|
a.declare(client.PfGVR, "portforward", "pf")
|
||||||
a.declare("benchmarks", "benchmark", "bench")
|
a.declare(client.BeGVR, "benchmark", "bench")
|
||||||
a.declare("screendumps", "screendump", "sd")
|
a.declare(client.SdGVR, "screendump", "sd")
|
||||||
a.declare("pulses", "pulse", "pu", "hz")
|
a.declare(client.PuGVR, "pulse", "pu", "hz")
|
||||||
a.declare("xrays", "xray", "x")
|
a.declare(client.XGVR, "xray", "x")
|
||||||
a.declare("workloads", "workload", "wk")
|
a.declare(client.WkGVR, "workload", "wk")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save alias to disk.
|
// Save alias to disk.
|
||||||
|
|
@ -200,6 +199,10 @@ func (a *Aliases) SaveAliases(path string) error {
|
||||||
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
|
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
|
||||||
return err
|
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
|
package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAliasClear(t *testing.T) {
|
func TestAliasClear(t *testing.T) {
|
||||||
a := testAliases()
|
a := testAliases()
|
||||||
a.Clear()
|
a.Clear()
|
||||||
|
|
||||||
assert.Equal(t, 0, len(a.Keys()))
|
assert.Empty(t, slices.Collect(maps.Keys(a.Alias)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasKeys(t *testing.T) {
|
func TestAliasKeys(t *testing.T) {
|
||||||
a := testAliases()
|
a := testAliases()
|
||||||
kk := a.Keys()
|
kk := maps.Keys(a.Alias)
|
||||||
slices.Sort(kk)
|
|
||||||
|
|
||||||
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) {
|
func TestAliasShortNames(t *testing.T) {
|
||||||
a := testAliases()
|
a := testAliases()
|
||||||
ess := config.ShortNames{
|
ess := config.ShortNames{
|
||||||
"gvr1": []string{"a1", "a11"},
|
gvr1: []string{"a1", "a11"},
|
||||||
"gvr2": []string{"a2"},
|
gvr2: []string{"a2"},
|
||||||
"gvr3": []string{"a3"},
|
gvr3: []string{"a3"},
|
||||||
}
|
}
|
||||||
ss := a.ShortNames()
|
ss := a.ShortNames()
|
||||||
assert.Equal(t, len(ess), len(ss))
|
assert.Len(t, ss, len(ess))
|
||||||
for k, v := range ss {
|
for k, v := range ss {
|
||||||
v1, ok := ess[k]
|
v1, ok := ess[k]
|
||||||
assert.True(t, ok, fmt.Sprintf("missing: %q", k))
|
assert.True(t, ok, "missing: %q", k)
|
||||||
slices.Sort(v)
|
slices.Sort(v)
|
||||||
assert.Equal(t, v1, v)
|
assert.Equal(t, v1, v)
|
||||||
}
|
}
|
||||||
|
|
@ -49,41 +50,41 @@ func TestAliasShortNames(t *testing.T) {
|
||||||
|
|
||||||
func TestAliasDefine(t *testing.T) {
|
func TestAliasDefine(t *testing.T) {
|
||||||
type aliasDef struct {
|
type aliasDef struct {
|
||||||
cmd string
|
gvr *client.GVR
|
||||||
aliases []string
|
aliases []string
|
||||||
}
|
}
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
aliases []aliasDef
|
aliases []aliasDef
|
||||||
registeredCommands map[string]string
|
registeredCommands map[string]*client.GVR
|
||||||
}{
|
}{
|
||||||
"simple": {
|
"simple": {
|
||||||
aliases: []aliasDef{
|
aliases: []aliasDef{
|
||||||
{
|
{
|
||||||
cmd: "one",
|
gvr: client.NewGVR("one"),
|
||||||
aliases: []string{"blee", "duh"},
|
aliases: []string{"blee", "duh"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
registeredCommands: map[string]string{
|
registeredCommands: map[string]*client.GVR{
|
||||||
"blee": "one",
|
"blee": client.NewGVR("one"),
|
||||||
"duh": "one",
|
"duh": client.NewGVR("one"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"duplicates": {
|
"duplicates": {
|
||||||
aliases: []aliasDef{
|
aliases: []aliasDef{
|
||||||
{
|
{
|
||||||
cmd: "one",
|
gvr: client.NewGVR("one"),
|
||||||
aliases: []string{"blee", "duh"},
|
aliases: []string{"blee", "duh"},
|
||||||
}, {
|
}, {
|
||||||
cmd: "two",
|
gvr: client.NewGVR("two"),
|
||||||
aliases: []string{"blee", "duh", "fred", "zorg"},
|
aliases: []string{"blee", "duh", "fred", "zorg"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
registeredCommands: map[string]string{
|
registeredCommands: map[string]*client.GVR{
|
||||||
"blee": "one",
|
"blee": client.NewGVR("one"),
|
||||||
"duh": "one",
|
"duh": client.NewGVR("one"),
|
||||||
"fred": "two",
|
"fred": client.NewGVR("two"),
|
||||||
"zorg": "two",
|
"zorg": client.NewGVR("two"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +95,7 @@ func TestAliasDefine(t *testing.T) {
|
||||||
configAlias := config.NewAliases()
|
configAlias := config.NewAliases()
|
||||||
for _, aliases := range u.aliases {
|
for _, aliases := range u.aliases {
|
||||||
for _, a := range aliases.aliases {
|
for _, a := range aliases.aliases {
|
||||||
configAlias.Define(aliases.cmd, a)
|
configAlias.Define(aliases.gvr, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for alias, cmd := range u.registeredCommands {
|
for alias, cmd := range u.registeredCommands {
|
||||||
|
|
@ -109,33 +110,39 @@ func TestAliasDefine(t *testing.T) {
|
||||||
func TestAliasesLoad(t *testing.T) {
|
func TestAliasesLoad(t *testing.T) {
|
||||||
config.AppConfigDir = "testdata/aliases"
|
config.AppConfigDir = "testdata/aliases"
|
||||||
a := config.NewAliases()
|
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.Len(t, a.Alias, 55)
|
||||||
assert.Equal(t, 54, len(a.Alias))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasesSave(t *testing.T) {
|
func TestAliasesSave(t *testing.T) {
|
||||||
assert.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod))
|
require.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod))
|
||||||
defer assert.NoError(t, os.RemoveAll("/tmp/test-aliases"))
|
defer require.NoError(t, os.RemoveAll("/tmp/test-aliases"))
|
||||||
|
|
||||||
config.AppAliasesFile = "/tmp/test-aliases/aliases.yaml"
|
config.AppAliasesFile = "/tmp/test-aliases/aliases.yaml"
|
||||||
a := testAliases()
|
a := testAliases()
|
||||||
c := len(a.Alias)
|
c := len(a.Alias)
|
||||||
|
|
||||||
assert.Equal(t, c, len(a.Alias))
|
assert.Len(t, a.Alias, c)
|
||||||
assert.Nil(t, a.Save())
|
require.NoError(t, a.Save())
|
||||||
assert.Nil(t, a.LoadFile(config.AppAliasesFile))
|
require.NoError(t, a.LoadFile(config.AppAliasesFile))
|
||||||
assert.Equal(t, c, len(a.Alias))
|
assert.Len(t, a.Alias, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
|
var (
|
||||||
|
gvr1 = client.NewGVR("gvr1")
|
||||||
|
gvr2 = client.NewGVR("gvr2")
|
||||||
|
gvr3 = client.NewGVR("gvr3")
|
||||||
|
)
|
||||||
|
|
||||||
func testAliases() *config.Aliases {
|
func testAliases() *config.Aliases {
|
||||||
a := config.NewAliases()
|
a := config.NewAliases()
|
||||||
a.Alias["a1"] = "gvr1"
|
a.Alias["a1"] = gvr1
|
||||||
a.Alias["a11"] = "gvr1"
|
a.Alias["a11"] = gvr1
|
||||||
a.Alias["a2"] = "gvr2"
|
a.Alias["a2"] = gvr2
|
||||||
a.Alias["a3"] = "gvr3"
|
a.Alias["a3"] = gvr3
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBenchEmpty(t *testing.T) {
|
func TestBenchEmpty(t *testing.T) {
|
||||||
|
|
@ -55,11 +56,11 @@ func TestBenchLoad(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
b, err := NewBench(u.file)
|
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.c, b.Benchmarks.Defaults.C)
|
||||||
assert.Equal(t, u.n, b.Benchmarks.Defaults.N)
|
assert.Equal(t, u.n, b.Benchmarks.Defaults.N)
|
||||||
assert.Equal(t, u.svcCount, len(b.Benchmarks.Services))
|
assert.Len(t, b.Benchmarks.Services, u.svcCount)
|
||||||
assert.Equal(t, u.coCount, len(b.Benchmarks.Containers))
|
assert.Len(t, b.Benchmarks.Containers, u.coCount)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,8 +106,8 @@ func TestBenchServiceLoad(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
b, err := NewBench("testdata/benchmarks/b_good.yaml")
|
b, err := NewBench("testdata/benchmarks/b_good.yaml")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
assert.Len(t, b.Benchmarks.Services, 2)
|
||||||
svc := b.Benchmarks.Services[u.key]
|
svc := b.Benchmarks.Services[u.key]
|
||||||
assert.Equal(t, u.c, svc.C)
|
assert.Equal(t, u.c, svc.C)
|
||||||
assert.Equal(t, u.n, svc.N)
|
assert.Equal(t, u.n, svc.N)
|
||||||
|
|
@ -123,16 +124,16 @@ func TestBenchServiceLoad(t *testing.T) {
|
||||||
|
|
||||||
func TestBenchReLoad(t *testing.T) {
|
func TestBenchReLoad(t *testing.T) {
|
||||||
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
|
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.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)
|
assert.Equal(t, 20, b.Benchmarks.Defaults.C)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBenchLoadToast(t *testing.T) {
|
func TestBenchLoadToast(t *testing.T) {
|
||||||
_, err := NewBench("testdata/toast.yaml")
|
_, err := NewBench("testdata/toast.yaml")
|
||||||
assert.NotNil(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBenchContainerLoad(t *testing.T) {
|
func TestBenchContainerLoad(t *testing.T) {
|
||||||
|
|
@ -176,8 +177,8 @@ func TestBenchContainerLoad(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
|
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
assert.Len(t, b.Benchmarks.Services, 2)
|
||||||
co := b.Benchmarks.Containers[u.key]
|
co := b.Benchmarks.Containers[u.key]
|
||||||
assert.Equal(t, u.c, co.C)
|
assert.Equal(t, u.c, co.C)
|
||||||
assert.Equal(t, u.n, co.N)
|
assert.Equal(t, u.n, co.N)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal/config/mock"
|
"github.com/derailed/k9s/internal/config/mock"
|
||||||
m "github.com/petergtz/pegomock"
|
m "github.com/petergtz/pegomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -54,16 +55,16 @@ func TestConfigSave(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
c := mock.NewMockConfig()
|
c := mock.NewMockConfig()
|
||||||
_, err := c.K9s.ActivateContext(u.ct)
|
_, err := c.K9s.ActivateContext(u.ct)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if u.flags != nil {
|
if u.flags != nil {
|
||||||
c.K9s.Override(u.k9sFlags)
|
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)
|
bb, err := os.ReadFile(config.AppConfigFile)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ee, err := os.ReadFile("testdata/configs/default.yaml")
|
ee, err := os.ReadFile("testdata/configs/default.yaml")
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, string(ee), string(bb))
|
assert.Equal(t, string(ee), string(bb))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +116,7 @@ func TestSetActiveView(t *testing.T) {
|
||||||
c := mock.NewMockConfig()
|
c := mock.NewMockConfig()
|
||||||
_, _ = c.K9s.ActivateContext(u.ct)
|
_, _ = c.K9s.ActivateContext(u.ct)
|
||||||
if u.flags != nil {
|
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.K9s.Override(u.k9sFlags)
|
||||||
}
|
}
|
||||||
c.SetActiveView(u.view)
|
c.SetActiveView(u.view)
|
||||||
|
|
@ -158,7 +159,7 @@ func TestActiveContextName(t *testing.T) {
|
||||||
c := mock.NewMockConfig()
|
c := mock.NewMockConfig()
|
||||||
_, _ = c.K9s.ActivateContext(u.ct)
|
_, _ = c.K9s.ActivateContext(u.ct)
|
||||||
if u.flags != nil {
|
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.K9s.Override(u.k9sFlags)
|
||||||
}
|
}
|
||||||
assert.Equal(t, u.e, c.ActiveContextName())
|
assert.Equal(t, u.e, c.ActiveContextName())
|
||||||
|
|
@ -206,7 +207,7 @@ func TestActiveView(t *testing.T) {
|
||||||
c := mock.NewMockConfig()
|
c := mock.NewMockConfig()
|
||||||
_, _ = c.K9s.ActivateContext(u.ct)
|
_, _ = c.K9s.ActivateContext(u.ct)
|
||||||
if u.flags != nil {
|
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.K9s.Override(u.k9sFlags)
|
||||||
}
|
}
|
||||||
assert.Equal(t, u.e, c.ActiveView())
|
assert.Equal(t, u.e, c.ActiveView())
|
||||||
|
|
@ -349,7 +350,7 @@ func TestConfigActivateContext(t *testing.T) {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.cl, ct.ClusterName)
|
assert.Equal(t, u.cl, ct.ClusterName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -393,9 +394,9 @@ func TestConfigCurrentContext(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
cfg := mock.NewMockConfig()
|
||||||
|
|
||||||
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
|
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ct, err := cfg.CurrentContext()
|
ct, err := cfg.CurrentContext()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.cluster, ct.ClusterName)
|
assert.Equal(t, u.cluster, ct.ClusterName)
|
||||||
assert.Equal(t, u.namespace, ct.Namespace.Active)
|
assert.Equal(t, u.namespace, ct.Namespace.Active)
|
||||||
})
|
})
|
||||||
|
|
@ -408,7 +409,7 @@ func TestConfigRefine(t *testing.T) {
|
||||||
cl1 = "cl-1"
|
cl1 = "cl-1"
|
||||||
ct2 = "ct-1-2"
|
ct2 = "ct-1-2"
|
||||||
ns1, ns2, nsx = "ns-1", "ns-2", "ns-x"
|
ns1, ns2, nsx = "ns-1", "ns-2", "ns-x"
|
||||||
true = true
|
trueVal = true
|
||||||
)
|
)
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
|
|
@ -465,7 +466,7 @@ func TestConfigRefine(t *testing.T) {
|
||||||
Namespace: &ns2,
|
Namespace: &ns2,
|
||||||
},
|
},
|
||||||
k9sFlags: &config.Flags{
|
k9sFlags: &config.Flags{
|
||||||
AllNamespaces: &true,
|
AllNamespaces: &trueVal,
|
||||||
},
|
},
|
||||||
cluster: "cl-1",
|
cluster: "cl-1",
|
||||||
context: "ct-1-1",
|
context: "ct-1-1",
|
||||||
|
|
@ -516,7 +517,7 @@ func TestConfigRefine(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
} else {
|
} else {
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.context, cfg.K9s.ActiveContextName())
|
assert.Equal(t, u.context, cfg.K9s.ActiveContextName())
|
||||||
assert.Equal(t, u.namespace, cfg.ActiveNamespace())
|
assert.Equal(t, u.namespace, cfg.ActiveNamespace())
|
||||||
}
|
}
|
||||||
|
|
@ -528,14 +529,14 @@ func TestConfigValidate(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
cfg := mock.NewMockConfig()
|
||||||
cfg.SetConnection(mock.NewMockConnection())
|
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")
|
cfg.Validate("ct-1-1", "cl-1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigLoad(t *testing.T) {
|
func TestConfigLoad(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
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, 2, cfg.K9s.RefreshRate)
|
||||||
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
|
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
|
||||||
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
|
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
|
||||||
|
|
@ -544,13 +545,13 @@ func TestConfigLoad(t *testing.T) {
|
||||||
func TestConfigLoadCrap(t *testing.T) {
|
func TestConfigLoadCrap(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
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) {
|
func TestConfigSaveFile(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
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.RefreshRate = 100
|
||||||
cfg.K9s.ReadOnly = true
|
cfg.K9s.ReadOnly = true
|
||||||
|
|
@ -559,28 +560,28 @@ func TestConfigSaveFile(t *testing.T) {
|
||||||
cfg.K9s.UI.UseFullGVRTitle = true
|
cfg.K9s.UI.UseFullGVRTitle = true
|
||||||
cfg.Validate("ct-1-1", "cl-1")
|
cfg.Validate("ct-1-1", "cl-1")
|
||||||
|
|
||||||
path := filepath.Join("/tmp", "k9s.yaml")
|
path := filepath.Join(os.TempDir(), "k9s.yaml")
|
||||||
assert.NoError(t, cfg.SaveFile(path))
|
require.NoError(t, cfg.SaveFile(path))
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
ee, err := os.ReadFile("testdata/configs/expected.yaml")
|
ee, err := os.ReadFile("testdata/configs/expected.yaml")
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, string(ee), string(raw))
|
assert.Equal(t, string(ee), string(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigReset(t *testing.T) {
|
func TestConfigReset(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
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.Reset()
|
||||||
cfg.Validate("ct-1-1", "cl-1")
|
cfg.Validate("ct-1-1", "cl-1")
|
||||||
|
|
||||||
path := filepath.Join("/tmp", "k9s.yaml")
|
path := filepath.Join(os.TempDir(), "k9s.yaml")
|
||||||
assert.NoError(t, cfg.SaveFile(path))
|
require.NoError(t, cfg.SaveFile(path))
|
||||||
|
|
||||||
bb, err := os.ReadFile(path)
|
bb, err := os.ReadFile(path)
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
ee, err := os.ReadFile("testdata/configs/k9s.yaml")
|
ee, err := os.ReadFile("testdata/configs/k9s.yaml")
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, string(ee), string(bb))
|
assert.Equal(t, string(ee), string(bb))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (c *Context) GetClusterName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ensures a context config is tip top.
|
// 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()
|
c.mx.Lock()
|
||||||
defer c.mx.Unlock()
|
defer c.mx.Unlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func TestClusterValidate(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "po", c.View.Active)
|
assert.Equal(t, "po", c.View.Active)
|
||||||
assert.Equal(t, "default", c.Namespace.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)
|
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, "po", c.View.Active)
|
||||||
assert.Equal(t, "default", c.Namespace.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)
|
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/data"
|
||||||
"github.com/derailed/k9s/internal/config/mock"
|
"github.com/derailed/k9s/internal/config/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
@ -68,12 +69,12 @@ func TestDirLoad(t *testing.T) {
|
||||||
|
|
||||||
ks := mock.NewMockKubeSettings(u.flags)
|
ks := mock.NewMockKubeSettings(u.flags)
|
||||||
if strings.Index(u.dir, "/tmp") == 0 {
|
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)
|
d := data.NewDir(u.dir)
|
||||||
ct, err := ks.CurrentContext()
|
ct, err := ks.CurrentContext()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ func EnsureDirPath(path string, mod os.FileMode) error {
|
||||||
// EnsureFullPath ensures a directory exist from the given path.
|
// EnsureFullPath ensures a directory exist from the given path.
|
||||||
func EnsureFullPath(path string, mod os.FileMode) error {
|
func EnsureFullPath(path string, mod os.FileMode) error {
|
||||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
||||||
if err = os.MkdirAll(path, mod); err != nil {
|
if e := os.MkdirAll(path, mod); e != nil {
|
||||||
return err
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSanitizeFileName(t *testing.T) {
|
func TestSanitizeFileName(t *testing.T) {
|
||||||
|
|
@ -65,27 +66,27 @@ func TestHelperInList(t *testing.T) {
|
||||||
func TestEnsureDirPathNone(t *testing.T) {
|
func TestEnsureDirPathNone(t *testing.T) {
|
||||||
const mod = 0744
|
const mod = 0744
|
||||||
|
|
||||||
dir := filepath.Join("/tmp", "k9s-test")
|
dir := filepath.Join(os.TempDir(), "k9s-test")
|
||||||
_ = os.Remove(dir)
|
_ = os.Remove(dir)
|
||||||
|
|
||||||
path := filepath.Join(dir, "duh.yaml")
|
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)
|
p, err := os.Stat(dir)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureDirPathNoOpt(t *testing.T) {
|
func TestEnsureDirPathNoOpt(t *testing.T) {
|
||||||
var mod os.FileMode = 0744
|
var mod os.FileMode = 0744
|
||||||
dir := filepath.Join("/tmp", "k9s-test")
|
dir := filepath.Join(os.TempDir(), "k9s-test")
|
||||||
assert.NoError(t, os.RemoveAll(dir))
|
require.NoError(t, os.RemoveAll(dir))
|
||||||
assert.NoError(t, os.Mkdir(dir, mod))
|
require.NoError(t, os.Mkdir(dir, mod))
|
||||||
|
|
||||||
path := filepath.Join(dir, "duh.yaml")
|
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)
|
p, err := os.Stat(dir)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func (n *Namespace) Validate(conn client.Connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetActive set the active namespace.
|
// 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 {
|
if n == nil {
|
||||||
n = NewActiveNamespace(ns)
|
n = NewActiveNamespace(ns)
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ func (n *Namespace) addFavNS(ns string) {
|
||||||
|
|
||||||
nfv := make([]string, 0, MaxFavoritesNS)
|
nfv := make([]string, 0, MaxFavoritesNS)
|
||||||
nfv = append(nfv, ns)
|
nfv = append(nfv, ns)
|
||||||
for i := 0; i < len(n.Favorites); i++ {
|
for i := range n.Favorites {
|
||||||
if i+1 < MaxFavoritesNS {
|
if i+1 < MaxFavoritesNS {
|
||||||
nfv = append(nfv, n.Favorites[i])
|
nfv = append(nfv, n.Favorites[i])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/derailed/k9s/internal/config/mock"
|
"github.com/derailed/k9s/internal/config/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNSValidate(t *testing.T) {
|
func TestNSValidate(t *testing.T) {
|
||||||
|
|
@ -41,7 +42,7 @@ func TestNsValidateMaxNS(t *testing.T) {
|
||||||
ns.Favorites = allNS
|
ns.Favorites = allNS
|
||||||
|
|
||||||
ns.Validate(mock.NewMockConnection())
|
ns.Validate(mock.NewMockConnection())
|
||||||
assert.Equal(t, data.MaxFavoritesNS, len(ns.Favorites))
|
assert.Len(t, ns.Favorites, data.MaxFavoritesNS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNSSetActive(t *testing.T) {
|
func TestNSSetActive(t *testing.T) {
|
||||||
|
|
@ -61,7 +62,7 @@ func TestNSSetActive(t *testing.T) {
|
||||||
ns := data.NewNamespace()
|
ns := data.NewNamespace()
|
||||||
for _, u := range uu {
|
for _, u := range uu {
|
||||||
err := ns.SetActive(u.ns, mk)
|
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.ns, ns.Active)
|
||||||
assert.Equal(t, u.fav, ns.Favorites)
|
assert.Equal(t, u.fav, ns.Favorites)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func NewView() *View {
|
||||||
|
|
||||||
// Validate a view configuration.
|
// Validate a view configuration.
|
||||||
func (v *View) Validate() {
|
func (v *View) Validate() {
|
||||||
if len(v.Active) == 0 {
|
if v.Active == "" {
|
||||||
v.Active = DefaultView
|
v.Active = DefaultView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,8 @@ func initXDGLocs() error {
|
||||||
AppViewsFile = filepath.Join(AppConfigDir, "views.yaml")
|
AppViewsFile = filepath.Join(AppConfigDir, "views.yaml")
|
||||||
|
|
||||||
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
|
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
|
||||||
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil {
|
if e := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); e != nil {
|
||||||
slog.Warn("No skins dir detected", slogs.Error, err)
|
slog.Warn("No skins dir detected", slogs.Error, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
|
AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,22 @@ import (
|
||||||
"github.com/adrg/xdg"
|
"github.com/adrg/xdg"
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_initXDGLocs(t *testing.T) {
|
func Test_initXDGLocs(t *testing.T) {
|
||||||
tmp, err := UserTmpDir()
|
tmp, err := UserTmpDir()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
|
require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
|
||||||
assert.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
|
require.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
|
||||||
assert.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
|
require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
|
||||||
assert.NoError(t, os.Unsetenv("XDG_DATA_HOME"))
|
require.NoError(t, os.Unsetenv("XDG_DATA_HOME"))
|
||||||
|
|
||||||
assert.NoError(t, os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "k9s-xdg", "config")))
|
require.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")))
|
require.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")))
|
require.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_DATA_HOME", filepath.Join(tmp, "k9s-xdg", "data")))
|
||||||
xdg.Reload()
|
xdg.Reload()
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
|
|
@ -55,7 +56,7 @@ func Test_initXDGLocs(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.NoError(t, initXDGLocs())
|
require.NoError(t, initXDGLocs())
|
||||||
assert.Equal(t, u.configDir, AppConfigDir)
|
assert.Equal(t, u.configDir, AppConfigDir)
|
||||||
assert.Equal(t, u.configFile, AppConfigFile)
|
assert.Equal(t, u.configFile, AppConfigFile)
|
||||||
assert.Equal(t, u.benchmarksDir, AppBenchmarksDir)
|
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.contextHotkeysFile, AppContextHotkeysFile("cl-1", "ct-1-1"))
|
||||||
assert.Equal(t, u.contextConfig, AppContextConfig("cl-1", "ct-1-1"))
|
assert.Equal(t, u.contextConfig, AppContextConfig("cl-1", "ct-1-1"))
|
||||||
dir, err := DumpsDir("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)
|
assert.Equal(t, u.dumpsDir, dir)
|
||||||
bdir, err := EnsureBenchmarksDir("cl-1", "ct-1-1")
|
bdir, err := EnsureBenchmarksDir("cl-1", "ct-1-1")
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.benchDir, bdir)
|
assert.Equal(t, u.benchDir, bdir)
|
||||||
hk, err := EnsureHotkeysCfgFile()
|
hk, err := EnsureHotkeysCfgFile()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.hkFile, hk)
|
assert.Equal(t, u.hkFile, hk)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ import (
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInitLogLoc(t *testing.T) {
|
func TestInitLogLoc(t *testing.T) {
|
||||||
tmp, err := config.UserTmpDir()
|
tmp, err := config.UserTmpDir()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
dir string
|
dir string
|
||||||
|
|
@ -39,33 +40,33 @@ func TestInitLogLoc(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.NoError(t, os.Unsetenv(config.K9sEnvLogsDir))
|
require.NoError(t, os.Unsetenv(config.K9sEnvLogsDir))
|
||||||
assert.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
|
require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
|
||||||
assert.NoError(t, os.Unsetenv(config.K9sEnvConfigDir))
|
require.NoError(t, os.Unsetenv(config.K9sEnvConfigDir))
|
||||||
switch k {
|
switch k {
|
||||||
case "log-env":
|
case "log-env":
|
||||||
assert.NoError(t, os.Setenv(config.K9sEnvLogsDir, u.dir))
|
require.NoError(t, os.Setenv(config.K9sEnvLogsDir, u.dir))
|
||||||
case "xdg-env":
|
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()
|
xdg.Reload()
|
||||||
case "cfg-env":
|
case "cfg-env":
|
||||||
assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, u.dir))
|
require.NoError(t, os.Setenv(config.K9sEnvConfigDir, u.dir))
|
||||||
}
|
}
|
||||||
err := config.InitLogLoc()
|
err := config.InitLogLoc()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.e, config.AppLogFile)
|
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) {
|
func TestEnsureBenchmarkCfg(t *testing.T) {
|
||||||
assert.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
|
require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
|
||||||
assert.NoError(t, config.InitLocs())
|
require.NoError(t, config.InitLocs())
|
||||||
defer assert.NoError(t, os.RemoveAll("/tmp/test-config"))
|
defer require.NoError(t, os.RemoveAll("/tmp/test-config"))
|
||||||
|
|
||||||
assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod))
|
require.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, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
cluster, context string
|
cluster, context string
|
||||||
|
|
@ -88,10 +89,10 @@ func TestEnsureBenchmarkCfg(t *testing.T) {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
f, err := config.EnsureBenchmarksCfgFile(u.cluster, u.context)
|
f, err := config.EnsureBenchmarksCfgFile(u.cluster, u.context)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.f, f)
|
assert.Equal(t, u.f, f)
|
||||||
bb, err := os.ReadFile(f)
|
bb, err := os.ReadFile(f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, u.e, string(bb))
|
assert.Equal(t, u.e, string(bb))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +100,7 @@ func TestEnsureBenchmarkCfg(t *testing.T) {
|
||||||
|
|
||||||
func TestSkinFileFromName(t *testing.T) {
|
func TestSkinFileFromName(t *testing.T) {
|
||||||
config.AppSkinsDir = "/tmp/k9s-test/skins"
|
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 {
|
uu := map[string]struct {
|
||||||
n string
|
n string
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ func IsBoolSet(b *bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isStringSet(s *string) bool {
|
func isStringSet(s *string) bool {
|
||||||
return s != nil && len(*s) > 0
|
return s != nil && *s != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func isYamlFile(file string) bool {
|
func isYamlFile(file string) bool {
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,18 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHotKeyLoad(t *testing.T) {
|
func TestHotKeyLoad(t *testing.T) {
|
||||||
h := config.NewHotKeys()
|
h := config.NewHotKeys()
|
||||||
assert.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml"))
|
require.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml"))
|
||||||
|
assert.Len(t, h.HotKey, 1)
|
||||||
assert.Equal(t, 1, len(h.HotKey))
|
|
||||||
|
|
||||||
k, ok := h.HotKey["pods"]
|
k, ok := h.HotKey["pods"]
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, "shift-0", k.ShortCut)
|
assert.Equal(t, "shift-0", k.ShortCut)
|
||||||
assert.Equal(t, "Launch pod view", k.Description)
|
assert.Equal(t, "Launch pod view", k.Description)
|
||||||
assert.Equal(t, "pods", k.Command)
|
assert.Equal(t, "pods", k.Command)
|
||||||
assert.Equal(t, true, k.KeepHistory)
|
assert.True(t, k.KeepHistory)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,22 @@
|
||||||
package json_test
|
package json_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config/json"
|
"github.com/derailed/k9s/internal/config/json"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidatePluginSnippet(t *testing.T) {
|
func TestValidatePluginSnippet(t *testing.T) {
|
||||||
plugPath := "testdata/plugins/snippet.yaml"
|
plugPath := "testdata/plugins/snippet.yaml"
|
||||||
bb, err := os.ReadFile(plugPath)
|
bb, err := os.ReadFile(plugPath)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
p := json.NewValidator()
|
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) {
|
func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
@ -51,7 +50,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.path)
|
bb, err := os.ReadFile(u.path)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
v := json.NewValidator()
|
v := json.NewValidator()
|
||||||
if err := v.Validate(u.schema, bb); err != nil {
|
if err := v.Validate(u.schema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
|
|
@ -63,7 +62,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
func TestValidatePluginDir(t *testing.T) {
|
func TestValidatePluginDir(t *testing.T) {
|
||||||
plugDir := "../../../plugins"
|
plugDir := "../../../plugins"
|
||||||
ee, err := os.ReadDir(plugDir)
|
ee, err := os.ReadDir(plugDir)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, e := range ee {
|
for _, e := range ee {
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
continue
|
continue
|
||||||
|
|
@ -72,31 +71,31 @@ func TestValidatePluginDir(t *testing.T) {
|
||||||
if ext == ".md" {
|
if ext == ".md" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name()))
|
assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
|
||||||
assert.False(t, strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name()))
|
assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
|
||||||
bb, err := os.ReadFile(filepath.Join(plugDir, e.Name()))
|
bb, err := os.ReadFile(filepath.Join(plugDir, e.Name()))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
p := json.NewValidator()
|
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) {
|
func TestValidateSkinDir(t *testing.T) {
|
||||||
skinDir := "../../../skins"
|
skinDir := "../../../skins"
|
||||||
ee, err := os.ReadDir(skinDir)
|
ee, err := os.ReadDir(skinDir)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
p := json.NewValidator()
|
p := json.NewValidator()
|
||||||
for _, e := range ee {
|
for _, e := range ee {
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ext := filepath.Ext(e.Name())
|
ext := filepath.Ext(e.Name())
|
||||||
assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name()))
|
assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
|
||||||
assert.True(t, !strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name()))
|
assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
|
||||||
bb, err := os.ReadFile(filepath.Join(skinDir, e.Name()))
|
bb, err := os.ReadFile(filepath.Join(skinDir, e.Name()))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NoError(t, p.Validate(json.SkinSchema, bb), e.Name())
|
require.NoError(t, p.Validate(json.SkinSchema, bb), e.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +118,7 @@ func TestValidateSkin(t *testing.T) {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.f)
|
bb, err := os.ReadFile(u.f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err := v.Validate(json.SkinSchema, bb); err != nil {
|
if err := v.Validate(json.SkinSchema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +145,7 @@ func TestValidateK9s(t *testing.T) {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.f)
|
bb, err := os.ReadFile(u.f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err := v.Validate(json.K9sSchema, bb); err != nil {
|
if err := v.Validate(json.K9sSchema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +173,7 @@ Additional property namespaces is not allowed`,
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.f)
|
bb, err := os.ReadFile(u.f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err := v.Validate(json.ContextSchema, bb); err != nil {
|
if err := v.Validate(json.ContextSchema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +201,7 @@ aliases is required`,
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.f)
|
bb, err := os.ReadFile(u.f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err := v.Validate(json.AliasesSchema, bb); err != nil {
|
if err := v.Validate(json.AliasesSchema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +231,7 @@ columns is required`,
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
bb, err := os.ReadFile(u.f)
|
bb, err := os.ReadFile(u.f)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if err := v.Validate(json.ViewsSchema, bb); err != nil {
|
if err := v.Validate(json.ViewsSchema, bb); err != nil {
|
||||||
assert.Equal(t, u.err, err.Error())
|
assert.Equal(t, u.err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,14 @@ type K9s struct {
|
||||||
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
|
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
|
||||||
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
|
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
|
||||||
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
|
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
|
||||||
MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"`
|
MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
|
||||||
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
|
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
|
||||||
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
|
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
|
||||||
PortForwardAddress string `yaml:"portForwardAddress"`
|
PortForwardAddress string `yaml:"portForwardAddress"`
|
||||||
UI UI `json:"ui" yaml:"ui"`
|
UI UI `json:"ui" yaml:"ui"`
|
||||||
SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"`
|
SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"`
|
||||||
DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"`
|
DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"`
|
||||||
ShellPod ShellPod `json:"shellPod" yaml:"shellPod"`
|
ShellPod *ShellPod `json:"shellPod" yaml:"shellPod"`
|
||||||
ImageScans ImageScans `json:"imageScans" yaml:"imageScans"`
|
ImageScans ImageScans `json:"imageScans" yaml:"imageScans"`
|
||||||
Logger Logger `json:"logger" yaml:"logger"`
|
Logger Logger `json:"logger" yaml:"logger"`
|
||||||
Thresholds Threshold `json:"thresholds" yaml:"thresholds"`
|
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 {
|
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())
|
return k.dir.Save(path, k.getActiveConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,8 +302,8 @@ func (k *K9s) Override(k9sFlags *Flags) {
|
||||||
k.manualReadOnly = k9sFlags.ReadOnly
|
k.manualReadOnly = k9sFlags.ReadOnly
|
||||||
}
|
}
|
||||||
if k9sFlags.Write != nil && *k9sFlags.Write {
|
if k9sFlags.Write != nil && *k9sFlags.Write {
|
||||||
var false bool
|
var falseVal bool
|
||||||
k.manualReadOnly = &false
|
k.manualReadOnly = &falseVal
|
||||||
}
|
}
|
||||||
k.manualCommand = k9sFlags.Command
|
k.manualCommand = k9sFlags.Command
|
||||||
k.manualScreenDumpDir = k9sFlags.ScreenDumpDir
|
k.manualScreenDumpDir = k9sFlags.ScreenDumpDir
|
||||||
|
|
@ -382,7 +386,7 @@ func (k *K9s) Validate(c client.Connection, contextName, clusterName string) {
|
||||||
if k.getActiveConfig() == nil {
|
if k.getActiveConfig() == nil {
|
||||||
_, _ = k.ActivateContext(contextName)
|
_, _ = k.ActivateContext(contextName)
|
||||||
}
|
}
|
||||||
k.ShellPod = k.ShellPod.Validate()
|
k.ShellPod.Validate()
|
||||||
k.Logger = k.Logger.Validate()
|
k.Logger = k.Logger.Validate()
|
||||||
k.Thresholds = k.Thresholds.Validate()
|
k.Thresholds = k.Thresholds.Validate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_k9sOverrides(t *testing.T) {
|
func Test_k9sOverrides(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
true = true
|
trueVal = true
|
||||||
cmd = "po"
|
cmd = "po"
|
||||||
dir = "/tmp/blee"
|
dir = "/tmp/blee"
|
||||||
)
|
)
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
|
|
@ -71,15 +72,15 @@ func Test_k9sOverrides(t *testing.T) {
|
||||||
Headless: false,
|
Headless: false,
|
||||||
Logoless: false,
|
Logoless: false,
|
||||||
Crumbsless: false,
|
Crumbsless: false,
|
||||||
manualHeadless: &true,
|
manualHeadless: &trueVal,
|
||||||
manualLogoless: &true,
|
manualLogoless: &trueVal,
|
||||||
manualCrumbsless: &true,
|
manualCrumbsless: &trueVal,
|
||||||
manualSplashless: &true,
|
manualSplashless: &trueVal,
|
||||||
},
|
},
|
||||||
SkipLatestRevCheck: false,
|
SkipLatestRevCheck: false,
|
||||||
DisablePodCounting: false,
|
DisablePodCounting: false,
|
||||||
manualRefreshRate: 100,
|
manualRefreshRate: 100,
|
||||||
manualReadOnly: &true,
|
manualReadOnly: &trueVal,
|
||||||
manualCommand: &cmd,
|
manualCommand: &cmd,
|
||||||
manualScreenDumpDir: &dir,
|
manualScreenDumpDir: &dir,
|
||||||
},
|
},
|
||||||
|
|
@ -123,7 +124,7 @@ func Test_screenDumpDirOverride(t *testing.T) {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
cfg := NewConfig(nil)
|
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
|
cfg.K9s.manualScreenDumpDir = &u.dir
|
||||||
assert.Equal(t, u.e, cfg.K9s.AppScreenDumpDir())
|
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"
|
||||||
"github.com/derailed/k9s/internal/config/mock"
|
"github.com/derailed/k9s/internal/config/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@ func TestK9sMerge(t *testing.T) {
|
||||||
UI: config.UI{},
|
UI: config.UI{},
|
||||||
SkipLatestRevCheck: false,
|
SkipLatestRevCheck: false,
|
||||||
DisablePodCounting: false,
|
DisablePodCounting: false,
|
||||||
ShellPod: config.ShellPod{},
|
ShellPod: new(config.ShellPod),
|
||||||
ImageScans: config.ImageScans{},
|
ImageScans: config.ImageScans{},
|
||||||
Logger: config.Logger{},
|
Logger: config.Logger{},
|
||||||
Thresholds: nil,
|
Thresholds: nil,
|
||||||
|
|
@ -135,14 +136,14 @@ func TestContextScreenDumpDir(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
cfg := mock.NewMockConfig()
|
||||||
_, err := cfg.K9s.ActivateContext("ct-1-1")
|
_, err := cfg.K9s.ActivateContext("ct-1-1")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
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/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir())
|
assert.Equal(t, "/tmp/k9s-test/screen-dumps/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppScreenDumpDir(t *testing.T) {
|
func TestAppScreenDumpDir(t *testing.T) {
|
||||||
cfg := mock.NewMockConfig()
|
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())
|
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) {
|
func (m mockKubeSettings) CurrentClusterName() (string, error) {
|
||||||
return *m.flags.ClusterName, nil
|
return *m.flags.ClusterName, nil
|
||||||
}
|
}
|
||||||
func (m mockKubeSettings) CurrentNamespaceName() (string, error) {
|
func (mockKubeSettings) CurrentNamespaceName() (string, error) {
|
||||||
return "default", nil
|
return "default", nil
|
||||||
}
|
}
|
||||||
func (m mockKubeSettings) GetContext(s string) (*api.Context, error) {
|
func (m mockKubeSettings) GetContext(s string) (*api.Context, error) {
|
||||||
|
|
@ -111,7 +111,7 @@ func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) {
|
||||||
return mm, nil
|
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 {
|
type mockConnection struct {
|
||||||
ct string
|
ct string
|
||||||
|
|
@ -124,57 +124,57 @@ func NewMockConnectionWithContext(ct string) mockConnection {
|
||||||
return mockConnection{ct: ct}
|
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
|
return true, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) Config() *client.Config {
|
func (mockConnection) Config() *client.Config {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) ConnectionOK() bool {
|
func (mockConnection) ConnectionOK() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (m mockConnection) Dial() (kubernetes.Interface, error) {
|
func (mockConnection) Dial() (kubernetes.Interface, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) DialLogs() (kubernetes.Interface, error) {
|
func (mockConnection) DialLogs() (kubernetes.Interface, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) SwitchContext(ctx string) error {
|
func (mockConnection) SwitchContext(string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
|
func (mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) RestConfig() (*restclient.Config, error) {
|
func (mockConnection) RestConfig() (*restclient.Config, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) MXDial() (*versioned.Clientset, error) {
|
func (mockConnection) MXDial() (*versioned.Clientset, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) DynDial() (dynamic.Interface, error) {
|
func (mockConnection) DynDial() (dynamic.Interface, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) HasMetrics() bool {
|
func (mockConnection) HasMetrics() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (m mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
|
func (mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) IsValidNamespace(string) bool {
|
func (mockConnection) IsValidNamespace(string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func (m mockConnection) ServerVersion() (*version.Info, error) {
|
func (mockConnection) ServerVersion() (*version.Info, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m mockConnection) CheckConnectivity() bool {
|
func (mockConnection) CheckConnectivity() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (m mockConnection) ActiveContext() string {
|
func (m mockConnection) ActiveContext() string {
|
||||||
return m.ct
|
return m.ct
|
||||||
}
|
}
|
||||||
func (m mockConnection) ActiveNamespace() string {
|
func (mockConnection) ActiveNamespace() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
func (m mockConnection) IsActiveNamespace(string) bool {
|
func (mockConnection) IsActiveNamespace(string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,16 +114,16 @@ func (p *Plugins) load(path string) error {
|
||||||
if err := yaml.Unmarshal(bb, &oo); err != nil {
|
if err := yaml.Unmarshal(bb, &oo); err != nil {
|
||||||
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
|
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
|
||||||
}
|
}
|
||||||
for k, v := range oo.Plugins {
|
for k := range oo.Plugins {
|
||||||
p.Plugins[k] = v
|
p.Plugins[k] = oo.Plugins[k]
|
||||||
}
|
}
|
||||||
case json.PluginMultiSchema:
|
case json.PluginMultiSchema:
|
||||||
var oo plugins
|
var oo plugins
|
||||||
if err := yaml.Unmarshal(bb, &oo); err != nil {
|
if err := yaml.Unmarshal(bb, &oo); err != nil {
|
||||||
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
|
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
|
||||||
}
|
}
|
||||||
for k, v := range oo {
|
for k := range oo {
|
||||||
p.Plugins[k] = v
|
p.Plugins[k] = oo[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPluginLoad(t *testing.T) {
|
func TestPluginLoad(t *testing.T) {
|
||||||
|
|
@ -103,10 +104,10 @@ func TestSinglePluginFileLoad(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := NewPlugins()
|
p := NewPlugins()
|
||||||
assert.NoError(t, p.load("testdata/plugins/plugins.yaml"))
|
require.NoError(t, p.load("testdata/plugins/plugins.yaml"))
|
||||||
assert.NoError(t, p.loadDir("/random/dir/not/exist"))
|
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"]
|
v, ok := p.Plugins["blah"]
|
||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
@ -169,8 +170,8 @@ func TestMultiplePluginFilesLoad(t *testing.T) {
|
||||||
for k, u := range uu {
|
for k, u := range uu {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
p := NewPlugins()
|
p := NewPlugins()
|
||||||
assert.NoError(t, p.load(u.path))
|
require.NoError(t, p.load(u.path))
|
||||||
assert.NoError(t, p.loadDir(u.dir))
|
require.NoError(t, p.loadDir(u.dir))
|
||||||
assert.Equal(t, u.ee, p)
|
assert.Equal(t, u.ee, p)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ type ShellPod struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShellPod returns a new instance.
|
// NewShellPod returns a new instance.
|
||||||
func NewShellPod() ShellPod {
|
func NewShellPod() *ShellPod {
|
||||||
return ShellPod{
|
return &ShellPod{
|
||||||
Image: defaultDockerShellImage,
|
Image: defaultDockerShellImage,
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Limits: defaultLimits(),
|
Limits: defaultLimits(),
|
||||||
|
|
@ -35,15 +35,13 @@ func NewShellPod() ShellPod {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the configuration.
|
// Validate validates the configuration.
|
||||||
func (s ShellPod) Validate() ShellPod {
|
func (s *ShellPod) Validate() {
|
||||||
if s.Image == "" {
|
if s.Image == "" {
|
||||||
s.Image = defaultDockerShellImage
|
s.Image = defaultDockerShellImage
|
||||||
}
|
}
|
||||||
if len(s.Limits) == 0 {
|
if len(s.Limits) == 0 {
|
||||||
s.Limits = defaultLimits()
|
s.Limits = defaultLimits()
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultLimits() Limits {
|
func defaultLimits() Limits {
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,21 @@ type StyleListener interface {
|
||||||
StylesChanged(*Styles)
|
StylesChanged(*Styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextStyle tracks text styles.
|
||||||
type TextStyle string
|
type TextStyle string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// TextStyleNormal is the default text style.
|
||||||
TextStyleNormal TextStyle = "normal"
|
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 {
|
func (ts TextStyle) ToShortString() string {
|
||||||
switch ts {
|
switch ts {
|
||||||
case TextStyleNormal:
|
case TextStyleNormal:
|
||||||
|
|
@ -283,8 +290,8 @@ func newCharts() Charts {
|
||||||
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
|
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
|
||||||
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
|
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
|
||||||
ResourceColors: map[string]Colors{
|
ResourceColors: map[string]Colors{
|
||||||
"cpu": {Color("dodgerblue"), Color("darkslateblue")},
|
CPU: {Color("dodgerblue"), Color("darkslateblue")},
|
||||||
"mem": {Color("yellow"), Color("goldenrod")},
|
MEM: {Color("yellow"), Color("goldenrod")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/derailed/tcell/v2"
|
"github.com/derailed/tcell/v2"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewStyle(t *testing.T) {
|
func TestNewStyle(t *testing.T) {
|
||||||
|
|
@ -38,7 +39,7 @@ func TestColor(t *testing.T) {
|
||||||
|
|
||||||
func TestSkinHappy(t *testing.T) {
|
func TestSkinHappy(t *testing.T) {
|
||||||
s := config.NewStyles()
|
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()
|
s.Update()
|
||||||
|
|
||||||
assert.Equal(t, "#ffffff", s.Body().FgColor.String())
|
assert.Equal(t, "#ffffff", s.Body().FgColor.String())
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,14 @@ type Threshold map[string]*Severity
|
||||||
// NewThreshold returns a new threshold.
|
// NewThreshold returns a new threshold.
|
||||||
func NewThreshold() Threshold {
|
func NewThreshold() Threshold {
|
||||||
return Threshold{
|
return Threshold{
|
||||||
"cpu": NewSeverity(),
|
CPU: NewSeverity(),
|
||||||
"memory": NewSeverity(),
|
MEM: NewSeverity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate a namespace is setup correctly.
|
// Validate a namespace is setup correctly.
|
||||||
func (t Threshold) Validate() Threshold {
|
func (t Threshold) Validate() Threshold {
|
||||||
for _, k := range []string{"cpu", "memory"} {
|
for _, k := range []string{CPU, MEM} {
|
||||||
v, ok := t[k]
|
v, ok := t[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
t[k] = NewSeverity()
|
t[k] = NewSeverity()
|
||||||
|
|
@ -92,7 +92,7 @@ func (t Threshold) LevelFor(k string, v int) SeverityLevel {
|
||||||
|
|
||||||
// SeverityColor returns a defcon level associated level.
|
// SeverityColor returns a defcon level associated level.
|
||||||
func (t *Threshold) SeverityColor(k string, v int) string {
|
func (t *Threshold) SeverityColor(k string, v int) string {
|
||||||
// nolint:exhaustive
|
//nolint:exhaustive
|
||||||
switch t.LevelFor(k, v) {
|
switch t.LevelFor(k, v) {
|
||||||
case SeverityHigh:
|
case SeverityHigh:
|
||||||
return "red"
|
return "red"
|
||||||
|
|
|
||||||
|
|
@ -48,32 +48,37 @@ func TestLevelFor(t *testing.T) {
|
||||||
e config.SeverityLevel
|
e config.SeverityLevel
|
||||||
}{
|
}{
|
||||||
"normal": {
|
"normal": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
v: 0,
|
v: 0,
|
||||||
e: config.SeverityLow,
|
e: config.SeverityLow,
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
v: 71,
|
v: 71,
|
||||||
e: config.SeverityMedium,
|
e: config.SeverityMedium,
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
v: 75,
|
v: 75,
|
||||||
e: config.SeverityMedium,
|
e: config.SeverityMedium,
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
v: 80,
|
v: 80,
|
||||||
e: config.SeverityMedium,
|
e: config.SeverityMedium,
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
v: 100,
|
v: 100,
|
||||||
e: config.SeverityHigh,
|
e: config.SeverityHigh,
|
||||||
},
|
},
|
||||||
"over": {
|
"over": {
|
||||||
k: "cpu",
|
k: config.CPU,
|
||||||
|
v: 150,
|
||||||
|
e: config.SeverityLow,
|
||||||
|
},
|
||||||
|
"over-mem": {
|
||||||
|
k: config.MEM,
|
||||||
v: 150,
|
v: 150,
|
||||||
e: config.SeverityLow,
|
e: config.SeverityLow,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ package config
|
||||||
const (
|
const (
|
||||||
defaultRefreshRate = 2
|
defaultRefreshRate = 2
|
||||||
defaultMaxConnRetry = 5
|
defaultMaxConnRetry = 5
|
||||||
|
|
||||||
|
// CPU tracks cpu usage.
|
||||||
|
CPU = "cpu"
|
||||||
|
|
||||||
|
// MEM tracks memory usage.
|
||||||
|
MEM = "memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UI tracks ui specific configs.
|
// UI tracks ui specific configs.
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func (v *ViewSetting) IsBlank() bool {
|
||||||
return v == nil || (len(v.Columns) == 0 && v.SortColumn == "")
|
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 == "" {
|
if v == nil || v.SortColumn == "" {
|
||||||
return "", false, fmt.Errorf("no sort column specified")
|
return "", false, fmt.Errorf("no sort column specified")
|
||||||
}
|
}
|
||||||
|
|
@ -180,9 +180,7 @@ func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
|
||||||
}
|
}
|
||||||
k := gvr
|
k := gvr
|
||||||
kk := slices.Collect(maps.Keys(v.Views))
|
kk := slices.Collect(maps.Keys(v.Views))
|
||||||
slices.SortFunc(kk, func(s1, s2 string) int {
|
slices.SortFunc(kk, strings.Compare)
|
||||||
return strings.Compare(s1, s2)
|
|
||||||
})
|
|
||||||
slices.Reverse(kk)
|
slices.Reverse(kk)
|
||||||
for _, key := range kk {
|
for _, key := range kk {
|
||||||
if !strings.HasPrefix(key, gvr) && !strings.HasPrefix(gvr, key) {
|
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]
|
vs := v.Views[key]
|
||||||
return &vs
|
return &vs
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ package config
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCustomView_getVS(t *testing.T) {
|
func TestCustomView_getVS(t *testing.T) {
|
||||||
|
|
@ -22,14 +24,14 @@ func TestCustomView_getVS(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
"gvr": {
|
"gvr": {
|
||||||
gvr: "v1/pods",
|
gvr: client.PodGVR.String(),
|
||||||
e: &ViewSetting{
|
e: &ViewSetting{
|
||||||
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"gvr+ns": {
|
"gvr+ns": {
|
||||||
gvr: "v1/pods",
|
gvr: client.PodGVR.String(),
|
||||||
ns: "default",
|
ns: "default",
|
||||||
e: &ViewSetting{
|
e: &ViewSetting{
|
||||||
Columns: []string{"NAME", "IP", "AGE"},
|
Columns: []string{"NAME", "IP", "AGE"},
|
||||||
|
|
@ -37,7 +39,7 @@ func TestCustomView_getVS(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
"rx": {
|
"rx": {
|
||||||
gvr: "v1/pods",
|
gvr: client.PodGVR.String(),
|
||||||
ns: "ns-fred",
|
ns: "ns-fred",
|
||||||
e: &ViewSetting{
|
e: &ViewSetting{
|
||||||
Columns: []string{"AGE", "NAME", "IP"},
|
Columns: []string{"AGE", "NAME", "IP"},
|
||||||
|
|
@ -52,7 +54,7 @@ func TestCustomView_getVS(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
"toast-no-ns": {
|
"toast-no-ns": {
|
||||||
gvr: "v1/pods",
|
gvr: client.PodGVR.String(),
|
||||||
ns: "zorg",
|
ns: "zorg",
|
||||||
e: &ViewSetting{
|
e: &ViewSetting{
|
||||||
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
||||||
|
|
@ -60,13 +62,13 @@ func TestCustomView_getVS(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
"toast-no-res": {
|
"toast-no-res": {
|
||||||
gvr: "v1/services",
|
gvr: client.SvcGVR.String(),
|
||||||
ns: "zorg",
|
ns: "zorg",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
v := NewCustomView()
|
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 {
|
for k, u := range uu {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, v.getVS(u.gvr, u.ns))
|
assert.Equal(t, u.e, v.getVS(u.gvr, u.ns))
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -26,7 +28,7 @@ func TestCustomViewLoad(t *testing.T) {
|
||||||
|
|
||||||
"gvr": {
|
"gvr": {
|
||||||
path: "testdata/views/views.yaml",
|
path: "testdata/views/views.yaml",
|
||||||
key: "v1/pods",
|
key: client.PodGVR.String(),
|
||||||
e: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
e: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -41,7 +43,7 @@ func TestCustomViewLoad(t *testing.T) {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
cfg := config.NewCustomView()
|
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)
|
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/client"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/view/cmd"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Accessor = (*Alias)(nil)
|
var _ Accessor = (*Alias)(nil)
|
||||||
|
|
@ -32,22 +32,18 @@ func NewAlias(f Factory) *Alias {
|
||||||
a := Alias{
|
a := Alias{
|
||||||
Aliases: config.NewAliases(),
|
Aliases: config.NewAliases(),
|
||||||
}
|
}
|
||||||
a.Init(f, client.NewGVR("aliases"))
|
a.Init(f, client.AliGVR)
|
||||||
|
|
||||||
return &a
|
return &a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Alias) AliasesFor(s string) []string {
|
// AliasesFor returns a set of aliases for a given gvr.
|
||||||
return a.Aliases.AliasesFor(s)
|
func (a *Alias) AliasesFor(gvr *client.GVR) sets.Set[string] {
|
||||||
}
|
return a.Aliases.AliasesFor(gvr)
|
||||||
|
|
||||||
// Check verifies an alias is defined for this command.
|
|
||||||
func (a *Alias) Check(cmd string) (string, bool) {
|
|
||||||
return a.Aliases.Get(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns a collection of aliases.
|
// 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)
|
aa, ok := ctx.Value(internal.KeyAliases).(*Alias)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("expecting *Alias but got %T", ctx.Value(internal.KeyAliases))
|
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.
|
// AsGVR returns a matching gvr if it exists.
|
||||||
func (a *Alias) AsGVR(c string) (client.GVR, string, bool) {
|
func (a *Alias) AsGVR(alias string) (*client.GVR, string, bool) {
|
||||||
exp, ok := a.Aliases.Get(c)
|
gvr, ok := a.Aliases.Get(alias)
|
||||||
if !ok {
|
if ok {
|
||||||
return client.NoGVR, "", ok
|
if pgvr := MetaAccess.Lookup(alias); pgvr != client.NoGVR {
|
||||||
}
|
return pgvr, "", 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.NoGVR, "", false
|
return gvr, "", ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get fetch a resource.
|
// 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")
|
return nil, errors.New("nyi")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,25 +100,21 @@ func (a *Alias) load(path string) error {
|
||||||
if IsK9sMeta(meta) {
|
if IsK9sMeta(meta) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
gvrStr := gvr.String()
|
|
||||||
if IsCRD(meta) {
|
if IsCRD(meta) {
|
||||||
crdGVRS = append(crdGVRS, gvr)
|
crdGVRS = append(crdGVRS, gvr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
a.Define(gvr, gvr.AsResourceName())
|
||||||
a.Define(gvrStr, gvr.AsResourceName())
|
|
||||||
|
|
||||||
// Allow single shot commands for k8s resources only!
|
// Allow single shot commands for k8s resources only!
|
||||||
if isStandardGroup(gvr.GVSub()) {
|
if isStandardGroup(gvr.GVSub()) {
|
||||||
a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
|
a.Define(gvr, meta.Name)
|
||||||
a.Define(gvrStr, meta.SingularName)
|
a.Define(gvr, meta.SingularName)
|
||||||
|
|
||||||
}
|
}
|
||||||
if len(meta.ShortNames) > 0 {
|
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 {
|
for _, gvr := range crdGVRS {
|
||||||
|
|
@ -135,15 +122,14 @@ func (a *Alias) load(path string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gvrStr := gvr.String()
|
a.Define(gvr, strings.ToLower(meta.Kind), meta.Name)
|
||||||
a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
|
a.Define(gvr, meta.SingularName)
|
||||||
a.Define(gvrStr, meta.SingularName)
|
|
||||||
|
|
||||||
if len(meta.ShortNames) > 0 {
|
if len(meta.ShortNames) > 0 {
|
||||||
a.Define(gvrStr, meta.ShortNames...)
|
a.Define(gvr, meta.ShortNames...)
|
||||||
}
|
}
|
||||||
a.Define(gvrStr, gvrStr)
|
a.Define(gvr, gvr.String())
|
||||||
a.Define(gvrStr, meta.Name+"."+meta.Group)
|
a.Define(gvr, meta.Name+"."+meta.Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -13,27 +13,28 @@ import (
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAsGVR(t *testing.T) {
|
func TestAsGVR(t *testing.T) {
|
||||||
a := dao.NewAlias(makeFactory())
|
a := dao.NewAlias(makeFactory())
|
||||||
a.Define("v1/pods", "po", "pod", "pods")
|
a.Define(client.PodGVR, "po", "pod", "pods")
|
||||||
a.Define("workloads", "workloads", "workload", "wkl")
|
a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl")
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
cmd string
|
cmd string
|
||||||
ok bool
|
ok bool
|
||||||
gvr client.GVR
|
gvr *client.GVR
|
||||||
}{
|
}{
|
||||||
"ok": {
|
"ok": {
|
||||||
cmd: "pods",
|
cmd: "pods",
|
||||||
ok: true,
|
ok: true,
|
||||||
gvr: client.NewGVR("v1/pods"),
|
gvr: client.PodGVR,
|
||||||
},
|
},
|
||||||
"ok-short": {
|
"ok-short": {
|
||||||
cmd: "po",
|
cmd: "po",
|
||||||
ok: true,
|
ok: true,
|
||||||
gvr: client.NewGVR("v1/pods"),
|
gvr: client.PodGVR,
|
||||||
},
|
},
|
||||||
"missing": {
|
"missing": {
|
||||||
cmd: "zorg",
|
cmd: "zorg",
|
||||||
|
|
@ -41,7 +42,7 @@ func TestAsGVR(t *testing.T) {
|
||||||
"alias": {
|
"alias": {
|
||||||
cmd: "wkl",
|
cmd: "wkl",
|
||||||
ok: true,
|
ok: true,
|
||||||
gvr: client.NewGVR("workloads"),
|
gvr: client.WkGVR,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,27 +60,30 @@ func TestAsGVR(t *testing.T) {
|
||||||
|
|
||||||
func TestAliasList(t *testing.T) {
|
func TestAliasList(t *testing.T) {
|
||||||
a := dao.Alias{}
|
a := dao.Alias{}
|
||||||
a.Init(makeFactory(), client.NewGVR("aliases"))
|
a.Init(makeFactory(), client.AliGVR)
|
||||||
|
|
||||||
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
|
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
|
||||||
oo, err := a.List(ctx, "-")
|
oo, err := a.List(ctx, "-")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 2, len(oo))
|
assert.Len(t, oo, 2)
|
||||||
assert.Equal(t, 2, len(oo[0].(render.AliasRes).Aliases))
|
assert.Len(t, oo[0].(render.AliasRes).Aliases, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func makeAliases() *dao.Alias {
|
func makeAliases() *dao.Alias {
|
||||||
|
gvr1 := client.NewGVR("v1/fred")
|
||||||
|
gvr2 := client.NewGVR("v1/blee")
|
||||||
|
|
||||||
return &dao.Alias{
|
return &dao.Alias{
|
||||||
Aliases: &config.Aliases{
|
Aliases: &config.Aliases{
|
||||||
Alias: config.Alias{
|
Alias: config.Alias{
|
||||||
"fred": "v1/fred",
|
"fred": gvr1,
|
||||||
"f": "v1/fred",
|
"f": gvr1,
|
||||||
"blee": "v1/blee",
|
"blee": gvr2,
|
||||||
"b": "v1/blee",
|
"b": gvr2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,17 @@ type Benchmark struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete nukes a resource.
|
// 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)
|
return os.Remove(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a resource.
|
// 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")
|
panic("NYI")
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns a collection of resources.
|
// 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)
|
dir, ok := ctx.Value(internal.KeyDir).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no benchmark dir found in context")
|
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/dao"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBenchmarkList(t *testing.T) {
|
func TestBenchmarkList(t *testing.T) {
|
||||||
a := dao.Benchmark{}
|
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(context.Background(), internal.KeyDir, "testdata/bench")
|
||||||
ctx = context.WithValue(ctx, internal.KeyPath, "")
|
ctx = context.WithValue(ctx, internal.KeyPath, "")
|
||||||
oo, err := a.List(ctx, "-")
|
oo, err := a.List(ctx, "-")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(oo))
|
assert.Len(t, oo, 1)
|
||||||
assert.Equal(t, "testdata/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
|
assert.Equal(t, "testdata/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
|
@ -19,9 +18,12 @@ import (
|
||||||
// RefScanner represents a resource reference scanner.
|
// RefScanner represents a resource reference scanner.
|
||||||
type RefScanner interface {
|
type RefScanner interface {
|
||||||
// Init initializes the scanner
|
// Init initializes the scanner
|
||||||
Init(Factory, client.GVR)
|
Init(Factory, *client.GVR)
|
||||||
|
|
||||||
// Scan scan the resource for references.
|
// 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)
|
ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,27 +42,21 @@ var (
|
||||||
_ RefScanner = (*DaemonSet)(nil)
|
_ RefScanner = (*DaemonSet)(nil)
|
||||||
_ RefScanner = (*Job)(nil)
|
_ RefScanner = (*Job)(nil)
|
||||||
_ RefScanner = (*CronJob)(nil)
|
_ RefScanner = (*CronJob)(nil)
|
||||||
// _ RefScanner = (*Pod)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func scanners() map[string]RefScanner {
|
func scanners() map[*client.GVR]RefScanner {
|
||||||
return map[string]RefScanner{
|
return map[*client.GVR]RefScanner{
|
||||||
"apps/v1/deployments": &Deployment{},
|
client.DpGVR: new(Deployment),
|
||||||
"apps/v1/statefulsets": &StatefulSet{},
|
client.DsGVR: new(DaemonSet),
|
||||||
"apps/v1/daemonsets": &DaemonSet{},
|
client.StsGVR: new(StatefulSet),
|
||||||
"batch/v1/jobs": &Job{},
|
client.CjGVR: new(CronJob),
|
||||||
"batch/v1/cronjobs": &CronJob{},
|
client.JobGVR: new(Job),
|
||||||
// "v1/pods": &Pod{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanForRefs scans cluster resources for resource references.
|
// ScanForRefs scans cluster resources for resource references.
|
||||||
func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
|
func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
|
||||||
defer func(t time.Time) {
|
rgvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
|
||||||
slog.Debug("Cluster Scan", slogs.Elapsed, time.Since(t))
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("expecting context GVR")
|
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")
|
slog.Warn("Expecting context Wait key. Using default")
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := scanners()
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(ss))
|
|
||||||
out := make(chan Refs)
|
out := make(chan Refs)
|
||||||
for k, s := range ss {
|
for gvr, scanner := range scanners() {
|
||||||
go func(ctx context.Context, kind string, s RefScanner, out chan Refs, wait bool) {
|
wg.Add(1)
|
||||||
|
go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
s.Init(f, client.NewGVR(kind))
|
s.Init(f, gvr)
|
||||||
refs, err := s.Scan(ctx, gvr, fqn, wait)
|
refs, err := s.Scan(ctx, rgvr, fqn, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Reference scan failed for",
|
slog.Error("Reference scan failed for",
|
||||||
slogs.RefType, fmt.Sprintf("%T", s),
|
slogs.RefType, fmt.Sprintf("%T", s),
|
||||||
|
|
@ -94,7 +89,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(ctx, k, s, out, wait)
|
}(ctx, gvr, scanner, out, wait)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -112,10 +107,6 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
|
||||||
|
|
||||||
// ScanForSARefs scans cluster resources for serviceaccount refs.
|
// ScanForSARefs scans cluster resources for serviceaccount refs.
|
||||||
func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
|
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)
|
fqn, ok := ctx.Value(internal.KeyPath).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("expecting context Path")
|
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")
|
return nil, errors.New("expecting context Wait")
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := scanners()
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(ss))
|
|
||||||
out := make(chan Refs)
|
out := make(chan Refs)
|
||||||
for k, s := range ss {
|
for gvr, scanner := range scanners() {
|
||||||
go func(ctx context.Context, kind string, s RefScanner, out chan Refs, wait bool) {
|
wg.Add(1)
|
||||||
|
go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
s.Init(f, client.NewGVR(kind))
|
s.Init(f, gvr)
|
||||||
refs, err := s.ScanSA(ctx, fqn, wait)
|
refs, err := s.ScanSA(ctx, fqn, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("ServiceAccount scan failed",
|
slog.Error("ServiceAccount scan failed",
|
||||||
|
|
@ -146,7 +136,7 @@ func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(ctx, k, s, out, wait)
|
}(ctx, gvr, scanner, out, wait)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,33 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)+len(po.Spec.EphemeralContainers))
|
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)+len(po.Spec.EphemeralContainers))
|
||||||
for i, co := range po.Spec.InitContainers {
|
for i := range po.Spec.InitContainers {
|
||||||
res = append(res, makeContainerRes(initIDX, i, co, po, cmx[co.Name]))
|
res = append(res, makeContainerRes(
|
||||||
|
initIDX,
|
||||||
|
i,
|
||||||
|
&(po.Spec.InitContainers[i]),
|
||||||
|
po,
|
||||||
|
cmx[po.Spec.InitContainers[i].Name]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for i, co := range po.Spec.Containers {
|
for i := range po.Spec.Containers {
|
||||||
res = append(res, makeContainerRes(mainIDX, i, co, po, cmx[co.Name]))
|
res = append(res, makeContainerRes(
|
||||||
|
mainIDX,
|
||||||
|
i,
|
||||||
|
&(po.Spec.Containers[i]),
|
||||||
|
po,
|
||||||
|
cmx[po.Spec.Containers[i].Name]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for i, co := range po.Spec.EphemeralContainers {
|
for i := range po.Spec.EphemeralContainers {
|
||||||
res = append(res, makeContainerRes(ephIDX, i, v1.Container(co.EphemeralContainerCommon), po, cmx[co.Name]))
|
co := v1.Container(po.Spec.EphemeralContainers[i].EphemeralContainerCommon)
|
||||||
|
res = append(res, makeContainerRes(
|
||||||
|
ephIDX,
|
||||||
|
i,
|
||||||
|
&co,
|
||||||
|
po,
|
||||||
|
cmx[co.Name]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
|
@ -70,7 +89,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
||||||
// TailLogs tails a given container logs.
|
// TailLogs tails a given container logs.
|
||||||
func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
||||||
po := Pod{}
|
po := Pod{}
|
||||||
po.Init(c.Factory, client.NewGVR("v1/pods"))
|
po.Init(c.Factory, client.PodGVR)
|
||||||
|
|
||||||
return po.TailLogs(ctx, opts)
|
return po.TailLogs(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
@ -78,34 +97,34 @@ func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan,
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// 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{
|
return render.ContainerRes{
|
||||||
Idx: kind + strconv.Itoa(idx+1),
|
Idx: kind + strconv.Itoa(idx+1),
|
||||||
Container: &co,
|
Container: co,
|
||||||
Status: getContainerStatus(kind, co.Name, po.Status),
|
Status: getContainerStatus(kind, co.Name, &po.Status),
|
||||||
MX: cmx,
|
MX: cmx,
|
||||||
Age: po.GetCreationTimestamp(),
|
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 {
|
switch kind {
|
||||||
case mainIDX:
|
case mainIDX:
|
||||||
for _, s := range status.ContainerStatuses {
|
for i := range status.ContainerStatuses {
|
||||||
if s.Name == name {
|
if status.ContainerStatuses[i].Name == name {
|
||||||
return &s
|
return &status.ContainerStatuses[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case initIDX:
|
case initIDX:
|
||||||
for _, s := range status.InitContainerStatuses {
|
for i := range status.InitContainerStatuses {
|
||||||
if s.Name == name {
|
if status.InitContainerStatuses[i].Name == name {
|
||||||
return &s
|
return &status.InitContainerStatuses[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ephIDX:
|
case ephIDX:
|
||||||
for _, s := range status.EphemeralContainerStatuses {
|
for i := range status.EphemeralContainerStatuses {
|
||||||
if s.Name == name {
|
if status.EphemeralContainerStatuses[i].Name == name {
|
||||||
return &s
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to locate pod %q: %w", fqn, err)
|
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/dao"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
|
@ -28,12 +29,12 @@ import (
|
||||||
|
|
||||||
func TestContainerList(t *testing.T) {
|
func TestContainerList(t *testing.T) {
|
||||||
c := dao.Container{}
|
c := dao.Container{}
|
||||||
c.Init(makePodFactory(), client.NewGVR("containers"))
|
c.Init(makePodFactory(), client.CoGVR)
|
||||||
|
|
||||||
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
|
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
|
||||||
oo, err := c.List(ctx, "")
|
oo, err := c.List(ctx, "")
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(oo))
|
assert.Len(t, oo, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
@ -45,58 +46,58 @@ func makeConn() *conn {
|
||||||
return &conn{}
|
return &conn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) Config() *client.Config { return nil }
|
func (*conn) Config() *client.Config { return nil }
|
||||||
func (c *conn) Dial() (kubernetes.Interface, error) { return nil, nil }
|
func (*conn) Dial() (kubernetes.Interface, error) { return nil, nil }
|
||||||
func (c *conn) DialLogs() (kubernetes.Interface, error) { return nil, nil }
|
func (*conn) DialLogs() (kubernetes.Interface, error) { return nil, nil }
|
||||||
func (c *conn) ConnectionOK() bool { return true }
|
func (*conn) ConnectionOK() bool { return true }
|
||||||
func (c *conn) SwitchContext(ctx string) error { return nil }
|
func (*conn) SwitchContext(string) error { return nil }
|
||||||
func (c *conn) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, nil }
|
func (*conn) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, nil }
|
||||||
func (c *conn) RestConfig() (*restclient.Config, error) { return nil, nil }
|
func (*conn) RestConfig() (*restclient.Config, error) { return nil, nil }
|
||||||
func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
|
func (*conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
|
||||||
func (c *conn) DynDial() (dynamic.Interface, error) { return nil, nil }
|
func (*conn) DynDial() (dynamic.Interface, error) { return nil, nil }
|
||||||
func (c *conn) HasMetrics() bool { return false }
|
func (*conn) HasMetrics() bool { return false }
|
||||||
func (c *conn) CheckConnectivity() bool { return false }
|
func (*conn) CheckConnectivity() bool { return false }
|
||||||
func (c *conn) IsNamespaced(n string) bool { return false }
|
func (*conn) IsNamespaced(string) bool { return false }
|
||||||
func (c *conn) SupportsResource(group string) bool { return false }
|
func (*conn) SupportsResource(string) bool { return false }
|
||||||
func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }
|
func (*conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }
|
||||||
func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error) {
|
func (*conn) SupportsRes(string, []string) (a string, b bool, e error) { return "", false, nil }
|
||||||
return "", false, nil
|
func (*conn) ServerVersion() (*version.Info, error) { return nil, nil }
|
||||||
}
|
func (*conn) CurrentNamespaceName() (string, error) { return "", nil }
|
||||||
func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil }
|
func (*conn) CanI(string, *client.GVR, string, []string) (bool, error) { return true, nil }
|
||||||
func (c *conn) CurrentNamespaceName() (string, error) { return "", nil }
|
func (*conn) ActiveContext() string { return "" }
|
||||||
func (c *conn) CanI(ns, gvr, n string, verbs []string) (bool, error) { return true, nil }
|
func (*conn) ActiveNamespace() string { return "" }
|
||||||
func (c *conn) ActiveContext() string { return "" }
|
func (*conn) IsValidNamespace(string) bool { return true }
|
||||||
func (c *conn) ActiveNamespace() string { return "" }
|
func (*conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil }
|
||||||
func (c *conn) IsValidNamespace(string) bool { return true }
|
func (*conn) IsActiveNamespace(string) bool { return false }
|
||||||
func (c *conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil }
|
|
||||||
func (c *conn) IsActiveNamespace(string) bool { return false }
|
|
||||||
|
|
||||||
type podFactory struct{}
|
type podFactory struct{}
|
||||||
|
|
||||||
var _ dao.Factory = &testFactory{}
|
var _ dao.Factory = &testFactory{}
|
||||||
|
|
||||||
func (f podFactory) Client() client.Connection {
|
func (podFactory) Client() client.Connection {
|
||||||
return makeConn()
|
return makeConn()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f podFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
|
func (podFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
|
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &unstructured.Unstructured{Object: m}, nil
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (f podFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) { return nil, nil }
|
func (podFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
|
||||||
func (f podFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (f podFactory) WaitForCacheSync() {}
|
func (podFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
|
||||||
func (f podFactory) Forwarders() watch.Forwarders { return nil }
|
return nil, nil
|
||||||
func (f podFactory) DeleteForwarder(string) {}
|
}
|
||||||
|
func (podFactory) WaitForCacheSync() {}
|
||||||
|
func (podFactory) Forwarders() watch.Forwarders { return nil }
|
||||||
|
func (podFactory) DeleteForwarder(string) {}
|
||||||
|
|
||||||
func makePodFactory() dao.Factory {
|
func makePodFactory() dao.Factory {
|
||||||
return podFactory{}
|
return podFactory{}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func (c *Context) config() *client.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a Context.
|
// 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)
|
co, err := c.config().GetContext(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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()
|
ctxs, err := c.config().Contexts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/rand"
|
"k8s.io/apimachinery/pkg/util/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const maxJobNameSize = 42
|
||||||
maxJobNameSize = 42
|
|
||||||
jobGVR = "batch/v1/jobs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Accessor = (*CronJob)(nil)
|
_ Accessor = (*CronJob)(nil)
|
||||||
|
|
@ -37,7 +34,7 @@ type CronJob struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListImages lists container images.
|
// 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)
|
cj, err := c.GetInstance(fqn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -49,7 +46,7 @@ func (c *CronJob) ListImages(ctx context.Context, fqn string) ([]string, error)
|
||||||
// Run a CronJob.
|
// Run a CronJob.
|
||||||
func (c *CronJob) Run(path string) error {
|
func (c *CronJob) Run(path string) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +54,7 @@ func (c *CronJob) Run(path string) error {
|
||||||
return fmt.Errorf("user is not authorized to run jobs")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +67,7 @@ func (c *CronJob) Run(path string) error {
|
||||||
if len(cj.Name) >= maxJobNameSize {
|
if len(cj.Name) >= maxJobNameSize {
|
||||||
jobName = cj.Name[0:maxJobNameSize]
|
jobName = cj.Name[0:maxJobNameSize]
|
||||||
}
|
}
|
||||||
true := true
|
trueVal := true
|
||||||
job := &batchv1.Job{
|
job := &batchv1.Job{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: jobName + "-manual-" + rand.String(3),
|
Name: jobName + "-manual-" + rand.String(3),
|
||||||
|
|
@ -81,8 +78,8 @@ func (c *CronJob) Run(path string) error {
|
||||||
{
|
{
|
||||||
APIVersion: c.gvr.GV().String(),
|
APIVersion: c.gvr.GV().String(),
|
||||||
Kind: "CronJob",
|
Kind: "CronJob",
|
||||||
BlockOwnerDeletion: &true,
|
BlockOwnerDeletion: &trueVal,
|
||||||
Controller: &true,
|
Controller: &trueVal,
|
||||||
Name: cj.Name,
|
Name: cj.Name,
|
||||||
UID: cj.UID,
|
UID: cj.UID,
|
||||||
},
|
},
|
||||||
|
|
@ -102,9 +99,9 @@ func (c *CronJob) Run(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanSA scans for serviceaccount refs.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// GetInstance fetch a matching cronjob.
|
||||||
func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +143,7 @@ func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
|
||||||
// ToggleSuspend toggles suspend/resume on a CronJob.
|
// ToggleSuspend toggles suspend/resume on a CronJob.
|
||||||
func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
|
func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -166,8 +163,8 @@ func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
|
||||||
current := !*cj.Spec.Suspend
|
current := !*cj.Spec.Suspend
|
||||||
cj.Spec.Suspend = ¤t
|
cj.Spec.Suspend = ¤t
|
||||||
} else {
|
} else {
|
||||||
true := true
|
trueVal := true
|
||||||
cj.Spec.Suspend = &true
|
cj.Spec.Suspend = &trueVal
|
||||||
}
|
}
|
||||||
_, err = dial.BatchV1().CronJobs(ns).Update(ctx, cj, metav1.UpdateOptions{})
|
_, 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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return nil, errors.New("expecting CronJob resource")
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case CmGVR:
|
case client.CmGVR:
|
||||||
if !hasConfigMap(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
|
if !hasConfigMap(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +195,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait boo
|
||||||
GVR: c.GVR(),
|
GVR: c.GVR(),
|
||||||
FQN: client.FQN(cj.Namespace, cj.Name),
|
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)
|
found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to locate secret",
|
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(),
|
GVR: c.GVR(),
|
||||||
FQN: client.FQN(cj.Namespace, cj.Name),
|
FQN: client.FQN(cj.Namespace, cj.Name),
|
||||||
})
|
})
|
||||||
case PcGVR:
|
case client.PcGVR:
|
||||||
if !hasPC(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
|
if !hasPC(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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)
|
u, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("no unstructured")
|
panic("no unstructured")
|
||||||
}
|
}
|
||||||
m, ok := u.Object[field].(map[string]interface{})
|
m, ok := u.Object[field].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("map extract failed for %q", field))
|
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
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustSlice(o runtime.Object, field string) []interface{} {
|
func mustSlice(o runtime.Object, field string) []any {
|
||||||
u, ok := o.(*unstructured.Unstructured)
|
u, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return []interface{}{}
|
return nil
|
||||||
}
|
}
|
||||||
s, ok := u.Object[field].([]interface{})
|
s, ok := u.Object[field].([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return []interface{}{}
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustField(o map[string]interface{}, field string) interface{} {
|
func mustField(o map[string]any, field string) any {
|
||||||
f, ok := o[field]
|
f, ok := o[field]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("no field for %q", field))
|
panic(fmt.Sprintf("no field for %q", field))
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,20 +25,20 @@ func TestCruiserSlice(t *testing.T) {
|
||||||
o := loadJSON(t, "crb")
|
o := loadJSON(t, "crb")
|
||||||
|
|
||||||
s := mustSlice(o, "subjects")
|
s := mustSlice(o, "subjects")
|
||||||
assert.Equal(t, 1, len(s))
|
assert.Len(t, s, 1)
|
||||||
assert.Equal(t, "fernand", mustField(s[0].(map[string]interface{}), "name"))
|
assert.Equal(t, "fernand", mustField(s[0].(map[string]any), "name"))
|
||||||
assert.Equal(t, "User", mustField(s[0].(map[string]interface{}), "kind"))
|
assert.Equal(t, "User", mustField(s[0].(map[string]any), "kind"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
// 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))
|
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var o unstructured.Unstructured
|
var o unstructured.Unstructured
|
||||||
err = json.Unmarshal(raw, &o)
|
err = json.Unmarshal(raw, &o)
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return &o
|
return &o
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Describe describes a resource.
|
// 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}
|
mapper := RestMapper{Connection: c}
|
||||||
m, err := mapper.ToRESTMapper()
|
m, err := mapper.ToRESTMapper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,14 @@ type Dir struct {
|
||||||
// NewDir returns a new set of aliases.
|
// NewDir returns a new set of aliases.
|
||||||
func NewDir(f Factory) *Dir {
|
func NewDir(f Factory) *Dir {
|
||||||
var a Dir
|
var a Dir
|
||||||
a.Init(f, client.NewGVR("dir"))
|
a.Init(f, client.DirGVR)
|
||||||
return &a
|
return &a
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlRX = regexp.MustCompile(`.*\.(yml|yaml|json)`)
|
var yamlRX = regexp.MustCompile(`.*\.(yml|yaml|json)`)
|
||||||
|
|
||||||
// List returns a collection of aliases.
|
// 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)
|
dir, ok := ctx.Value(internal.KeyPath).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no dir in context")
|
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.
|
// 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")
|
return nil, errors.New("nyi")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewDir(t *testing.T) {
|
func TestNewDir(t *testing.T) {
|
||||||
|
|
@ -17,6 +18,6 @@ func TestNewDir(t *testing.T) {
|
||||||
ctx := context.WithValue(context.Background(), internal.KeyPath, "testdata/dir")
|
ctx := context.WithValue(context.Background(), internal.KeyPath, "testdata/dir")
|
||||||
oo, err := d.List(ctx, "")
|
oo, err := d.List(ctx, "")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 2, len(oo))
|
assert.Len(t, oo, 2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ type Deployment struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListImages lists container images.
|
// 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)
|
dp, err := d.GetInstance(fqn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -52,76 +52,12 @@ func (d *Deployment) ListImages(ctx context.Context, fqn string) ([]string, erro
|
||||||
|
|
||||||
// Scale a Deployment.
|
// Scale a Deployment.
|
||||||
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
|
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
|
||||||
ns, n := client.Namespaced(path)
|
return scaleRes(ctx, d.getFactory(), client.DpGVR, path, replicas)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a Deployment rollout.
|
// Restart a Deployment rollout.
|
||||||
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
||||||
o, err := d.getFactory().Get("apps/v1/deployments", path, true, labels.Everything())
|
return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailLogs tail logs for all pods represented by this Deployment.
|
// 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.
|
// GetInstance fetch a matching deployment.
|
||||||
func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -164,9 +100,9 @@ func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanSA scans for serviceaccount refs.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return nil, errors.New("expecting Deployment resource")
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case CmGVR:
|
case client.CmGVR:
|
||||||
if !hasConfigMap(&dp.Spec.Template.Spec, n) {
|
if !hasConfigMap(&dp.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +149,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
|
||||||
GVR: d.GVR(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(dp.Namespace, dp.Name),
|
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)
|
found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Fail to locate secret",
|
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(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(dp.Namespace, dp.Name),
|
FQN: client.FQN(dp.Namespace, dp.Name),
|
||||||
})
|
})
|
||||||
case PvcGVR:
|
case client.PvcGVR:
|
||||||
if !hasPVC(&dp.Spec.Template.Spec, n) {
|
if !hasPVC(&dp.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +173,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait
|
||||||
GVR: d.GVR(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(dp.Namespace, dp.Name),
|
FQN: client.FQN(dp.Namespace, dp.Name),
|
||||||
})
|
})
|
||||||
case PcGVR:
|
case client.PcGVR:
|
||||||
if !hasPC(&dp.Spec.Template.Spec, n) {
|
if !hasPC(&dp.Spec.Template.Spec, n) {
|
||||||
continue
|
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),
|
FQN: client.FQN(dp.Namespace, dp.Name),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return refs, nil
|
return refs, nil
|
||||||
|
|
@ -265,7 +200,7 @@ func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) {
|
||||||
// SetImages sets container images.
|
// SetImages sets container images.
|
||||||
func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -290,9 +225,11 @@ func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs Imag
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
func hasPVC(spec *v1.PodSpec, name string) bool {
|
func hasPVC(spec *v1.PodSpec, name string) bool {
|
||||||
for _, v := range spec.Volumes {
|
for i := range spec.Volumes {
|
||||||
if v.PersistentVolumeClaim != nil && v.PersistentVolumeClaim.ClaimName == name {
|
if spec.Volumes[i].PersistentVolumeClaim != nil && spec.Volumes[i].PersistentVolumeClaim.ClaimName == name {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -304,24 +241,24 @@ func hasPC(spec *v1.PodSpec, name string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasConfigMap(spec *v1.PodSpec, name string) bool {
|
func hasConfigMap(spec *v1.PodSpec, name string) bool {
|
||||||
for _, c := range spec.InitContainers {
|
for i := range spec.InitContainers {
|
||||||
if containerHasConfigMap(c.EnvFrom, c.Env, name) {
|
if containerHasConfigMap(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range spec.Containers {
|
for i := range spec.Containers {
|
||||||
if containerHasConfigMap(c.EnvFrom, c.Env, name) {
|
if containerHasConfigMap(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range spec.EphemeralContainers {
|
for i := range spec.EphemeralContainers {
|
||||||
if containerHasConfigMap(c.EnvFrom, c.Env, name) {
|
if containerHasConfigMap(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range spec.Volumes {
|
for i := range spec.Volumes {
|
||||||
if cm := v.ConfigMap; cm != nil {
|
if cm := spec.Volumes[i].ConfigMap; cm != nil {
|
||||||
if cm.Name == name {
|
if cm.Name == name {
|
||||||
return true
|
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) {
|
func hasSecret(f Factory, spec *v1.PodSpec, ns, name string, wait bool) (bool, error) {
|
||||||
for _, c := range spec.InitContainers {
|
for i := range spec.InitContainers {
|
||||||
if containerHasSecret(c.EnvFrom, c.Env, name) {
|
if containerHasSecret(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range spec.Containers {
|
for i := range spec.Containers {
|
||||||
if containerHasSecret(c.EnvFrom, c.Env, name) {
|
if containerHasSecret(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range spec.EphemeralContainers {
|
for i := range spec.EphemeralContainers {
|
||||||
if containerHasSecret(c.EnvFrom, c.Env, name) {
|
if containerHasSecret(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
|
||||||
return true, nil
|
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 != "" {
|
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 {
|
if err != nil {
|
||||||
return false, err
|
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 {
|
for i := range spec.Volumes {
|
||||||
if sec := v.Secret; sec != nil {
|
if sec := spec.Volumes[i].Secret; sec != nil {
|
||||||
if sec.SecretName == name {
|
if sec.SecretName == name {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,3 +357,110 @@ func containerHasConfigMap(envFrom []v1.EnvFromSource, env []v1.EnvVar, name str
|
||||||
|
|
||||||
return false
|
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/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
|
||||||
"k8s.io/kubectl/pkg/scheme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -43,7 +40,7 @@ type DaemonSet struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListImages lists container images.
|
// 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)
|
ds, err := d.GetInstance(fqn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -54,51 +51,7 @@ func (d *DaemonSet) ListImages(ctx context.Context, fqn string) ([]string, error
|
||||||
|
|
||||||
// Restart a DaemonSet rollout.
|
// Restart a DaemonSet rollout.
|
||||||
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||||
o, err := d.getFactory().Get("apps/v1/daemonsets", path, true, labels.Everything())
|
return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailLogs tail logs for all pods represented by this DaemonSet.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts.MultiPods = true
|
opts.MultiPods = true
|
||||||
|
|
||||||
var po Pod
|
var po Pod
|
||||||
po.Init(f, client.NewGVR("v1/pods"))
|
po.Init(f, client.PodGVR)
|
||||||
|
|
||||||
outs := make([]LogChan, 0, len(oo))
|
outs := make([]LogChan, 0, len(oo))
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
|
|
@ -169,7 +122,7 @@ func (d *DaemonSet) Pod(fqn string) (string, error) {
|
||||||
|
|
||||||
// GetInstance returns a daemonset instance.
|
// GetInstance returns a daemonset instance.
|
||||||
func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -184,9 +137,9 @@ func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanSA scans for serviceaccount refs.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return nil, errors.New("expecting StatefulSet resource")
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case CmGVR:
|
case client.CmGVR:
|
||||||
if !hasConfigMap(&ds.Spec.Template.Spec, n) {
|
if !hasConfigMap(&ds.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +186,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
|
||||||
GVR: d.GVR(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(ds.Namespace, ds.Name),
|
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)
|
found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Unable to locate secret",
|
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(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(ds.Namespace, ds.Name),
|
FQN: client.FQN(ds.Namespace, ds.Name),
|
||||||
})
|
})
|
||||||
case PvcGVR:
|
case client.PvcGVR:
|
||||||
if !hasPVC(&ds.Spec.Template.Spec, n) {
|
if !hasPVC(&ds.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -257,7 +210,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b
|
||||||
GVR: d.GVR(),
|
GVR: d.GVR(),
|
||||||
FQN: client.FQN(ds.Namespace, ds.Name),
|
FQN: client.FQN(ds.Namespace, ds.Name),
|
||||||
})
|
})
|
||||||
case PcGVR:
|
case client.PcGVR:
|
||||||
if !hasPC(&ds.Spec.Template.Spec, n) {
|
if !hasPC(&ds.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +237,7 @@ func (d *DaemonSet) GetPodSpec(path string) (*v1.PodSpec, error) {
|
||||||
// SetImages sets container images.
|
// SetImages sets container images.
|
||||||
func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) {
|
||||||
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
|
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
|
||||||
|
|
||||||
opts := []string{d.gvr.AsResourceName()}
|
opts := []string{d.gvr.AsResourceName()}
|
||||||
ns, n := client.Namespaced(fqn)
|
ns, n := client.Namespaced(fqn)
|
||||||
if n != "" {
|
if n != "" {
|
||||||
opts = append(opts, n)
|
opts = append(opts, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
allNS := client.IsAllNamespaces(ns)
|
allNS := client.IsAllNamespaces(ns)
|
||||||
flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags())
|
flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags())
|
||||||
f := cmdutil.NewFactory(flags)
|
f := cmdutil.NewFactory(flags)
|
||||||
|
|
@ -70,7 +68,6 @@ func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oo := make([]runtime.Object, 0, len(infos))
|
oo := make([]runtime.Object, 0, len(infos))
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
o, err := decodeIntoTable(info.Object, allNS)
|
o, err := decodeIntoTable(info.Object, allNS)
|
||||||
|
|
@ -93,7 +90,6 @@ func decodeIntoTable(obj runtime.Object, allNs bool) (runtime.Object, error) {
|
||||||
if isEvent {
|
if isEvent {
|
||||||
obj = event.Object.Object
|
obj = event.Object.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] {
|
if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] {
|
||||||
return nil, fmt.Errorf("attempt to decode non-Table object: %v", 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()
|
ns = m.GetNamespace()
|
||||||
}
|
}
|
||||||
if allNs {
|
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, ns)
|
||||||
cells = append(cells, row.Cells...)
|
cells = append(cells, row.Cells...)
|
||||||
row.Cells = cells
|
row.Cells = cells
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ func (g *Generic) ToYAML(path string, showManaged bool) (string, error) {
|
||||||
// Delete deletes a resource.
|
// Delete deletes a resource.
|
||||||
func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
|
func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ type HelmChart struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns a collection of resources.
|
// 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)
|
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -102,7 +102,7 @@ func (h *HelmChart) Describe(path string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToYAML returns the chart manifest.
|
// 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)
|
ns, n := client.Namespaced(path)
|
||||||
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
|
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -164,7 +164,7 @@ func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action.
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func helmLogger(fmat string, args ...interface{}) {
|
func helmLogger(fmat string, args ...any) {
|
||||||
slog.Debug("Log",
|
slog.Debug("Log",
|
||||||
slogs.Log, fmt.Sprintf(fmat, args...),
|
slogs.Log, fmt.Sprintf(fmat, args...),
|
||||||
slogs.Subsys, "helm",
|
slogs.Subsys, "helm",
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, err
|
||||||
// Get returns a resource.
|
// Get returns a resource.
|
||||||
func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) {
|
func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||||
fqn, rev, found := strings.Cut(path, ":")
|
fqn, rev, found := strings.Cut(path, ":")
|
||||||
if !found || len(rev) == 0 {
|
if !found || rev == "" {
|
||||||
return nil, fmt.Errorf("invalid path %q", path)
|
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.
|
// 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)
|
rel, err := h.Get(context.Background(), path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetDefaultContainer returns a container name if specified in an annotation.
|
// 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]
|
defaultContainer, ok := m.Annotations[DefaultContainerAnnotation]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range spec.Containers {
|
for i := range spec.Containers {
|
||||||
if container.Name == defaultContainer {
|
if spec.Containers[i].Name == defaultContainer {
|
||||||
return defaultContainer, true
|
return defaultContainer, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ func ToYAML(o runtime.Object, showManaged bool) (string, error) {
|
||||||
if !showManaged {
|
if !showManaged {
|
||||||
o = o.DeepCopyObject()
|
o = o.DeepCopyObject()
|
||||||
uo := o.(*unstructured.Unstructured).Object
|
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")
|
delete(meta, "managedFields")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ func TestToPerc(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range uu {
|
for _, u := range uu {
|
||||||
|
//nolint:testifylint
|
||||||
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
|
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ type ImageScan struct {
|
||||||
NonResource
|
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)
|
res, err := AccessorFor(is.Factory, gvr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -40,7 +40,7 @@ func (is *ImageScan) List(ctx context.Context, _ string) ([]runtime.Object, erro
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no context path for %q", is.gvr)
|
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 {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no context gvr for %q", is.gvr)
|
return nil, fmt.Errorf("no context gvr for %q", is.gvr)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ type Job struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListImages lists container images.
|
// 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)
|
job, err := j.GetInstance(fqn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// TailLogs tail logs for all pods represented by this Job.
|
||||||
func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -108,9 +108,9 @@ func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanSA scans for serviceaccount refs.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return nil, errors.New("expecting Job resource")
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case CmGVR:
|
case client.CmGVR:
|
||||||
if !hasConfigMap(&job.Spec.Template.Spec, n) {
|
if !hasConfigMap(&job.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +157,7 @@ func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
|
||||||
GVR: j.GVR(),
|
GVR: j.GVR(),
|
||||||
FQN: client.FQN(job.Namespace, job.Name),
|
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)
|
found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Locate secret failed",
|
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(),
|
GVR: j.GVR(),
|
||||||
FQN: client.FQN(job.Namespace, job.Name),
|
FQN: client.FQN(job.Namespace, job.Name),
|
||||||
})
|
})
|
||||||
case PcGVR:
|
case client.PcGVR:
|
||||||
if !hasPC(&job.Spec.Template.Spec, n) {
|
if !hasPC(&job.Spec.Template.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,11 @@ func (l *LogItem) Render(paint string, showTime bool, bb *bytes.Buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !l.SingleContainer && l.Container != "" {
|
if !l.SingleContainer && l.Container != "" {
|
||||||
if len(l.Pod) > 0 {
|
if l.Pod != "" {
|
||||||
bb.WriteString(" ")
|
bb.WriteString(" ")
|
||||||
}
|
}
|
||||||
bb.WriteString("[" + paint + "::b]" + l.Container + "[-::-] ")
|
bb.WriteString("[" + paint + "::b]" + l.Container + "[-::-] ")
|
||||||
} else if len(l.Pod) > 0 {
|
} else if l.Pod != "" {
|
||||||
bb.WriteString("[-::] ")
|
bb.WriteString("[-::] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ func BenchmarkLogItemRenderTS(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for n := 0; n < b.N; n++ {
|
for range b.N {
|
||||||
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
|
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
|
||||||
i.Render("yellow", true, bb)
|
i.Render("yellow", true, bb)
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +125,7 @@ func BenchmarkLogItemRenderNoTS(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for n := 0; n < b.N; n++ {
|
for range b.N {
|
||||||
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
|
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
|
||||||
i.Render("yellow", false, bb)
|
i.Render("yellow", false, bb)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,25 +171,25 @@ func (l *LogItems) DumpDebug(m string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter filters out log items based on given filter.
|
// 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 == "" {
|
if q == "" {
|
||||||
return nil, nil, nil
|
return
|
||||||
}
|
}
|
||||||
if f, ok := internal.IsFuzzySelector(q); ok {
|
if f, ok := internal.IsFuzzySelector(q); ok {
|
||||||
mm, ii := l.fuzzyFilter(index, f, showTime)
|
matches, indices = l.fuzzyFilter(index, f, showTime)
|
||||||
return mm, ii, nil
|
return
|
||||||
}
|
}
|
||||||
matches, indices, err := l.filterLogs(index, q, showTime)
|
matches, indices, err = l.filterLogs(index, q, showTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches, indices, nil
|
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)
|
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))
|
mm := fuzzy.Find(q, l.StrLines(index, showTime))
|
||||||
for _, m := range mm {
|
for _, m := range mm {
|
||||||
matches = append(matches, m.Index)
|
matches = append(matches, m.Index)
|
||||||
|
|
@ -199,7 +199,7 @@ func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) ([]int, [][]i
|
||||||
return matches, indices
|
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
|
var invert bool
|
||||||
if internal.IsInverseSelector(q) {
|
if internal.IsInverseSelector(q) {
|
||||||
invert = true
|
invert = true
|
||||||
|
|
@ -209,7 +209,7 @@ func (l *LogItems) filterLogs(index int, q string, showTime bool) ([]int, [][]in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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:]))
|
ll := make([][]byte, len(l.items[index:]))
|
||||||
l.Lines(index, showTime, ll)
|
l.Lines(index, showTime, ll)
|
||||||
for i, line := range ll {
|
for i, line := range ll {
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,10 @@ type LogOptions struct {
|
||||||
|
|
||||||
// Info returns the option pod and container info.
|
// Info returns the option pod and container info.
|
||||||
func (o *LogOptions) Info() string {
|
func (o *LogOptions) Info() string {
|
||||||
if len(o.Container) != 0 {
|
if o.Container != "" {
|
||||||
return fmt.Sprintf("%s (%s)", o.Path, o.Container)
|
return fmt.Sprintf("%s (%s)", o.Path, o.Container)
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.Path
|
return o.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +132,7 @@ func (o *LogOptions) ToLogItem(bytes []byte) *LogItem {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogOptions) ToErrLogItem(err error) *LogItem {
|
func (*LogOptions) ToErrLogItem(err error) *LogItem {
|
||||||
t := time.Now().UTC().Format(time.RFC3339Nano)
|
t := time.Now().UTC().Format(time.RFC3339Nano)
|
||||||
item := NewLogItem([]byte(fmt.Sprintf("%s [orange::b]%s[::-]\n", t, err)))
|
item := NewLogItem([]byte(fmt.Sprintf("%s [orange::b]%s[::-]\n", t, err)))
|
||||||
item.IsError = true
|
item.IsError = true
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,8 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cordoned {
|
if !cordoned {
|
||||||
if err = n.ToggleCordon(path, true); err != nil {
|
if e := n.ToggleCordon(path, true); e != nil {
|
||||||
return err
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +168,13 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldCountPods, _ := ctx.Value(internal.KeyPodCounting).(bool)
|
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))
|
res := make([]runtime.Object, 0, len(oo))
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
u, ok := o.(*unstructured.Unstructured)
|
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)
|
_, name := client.Namespaced(fqn)
|
||||||
podCount := -1
|
podCount := -1
|
||||||
if shouldCountPods {
|
if shouldCountPods {
|
||||||
podCount, err = n.CountPods(name)
|
podCount, err = n.CountPods(pods, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Unable to get pods count",
|
slog.Error("Unable to get pods count",
|
||||||
slogs.ResName, name,
|
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.
|
// 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
|
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 {
|
for _, o := range oo {
|
||||||
u, ok := o.(*unstructured.Unstructured)
|
u, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
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 {
|
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 {
|
if node, ok := spec["nodeName"]; ok && node == nodeName {
|
||||||
count++
|
count++
|
||||||
|
|
@ -225,7 +226,7 @@ func (n *Node) CountPods(nodeName string) (int, error) {
|
||||||
|
|
||||||
// GetPods returns all pods running on given node.
|
// GetPods returns all pods running on given node.
|
||||||
func (n *Node) GetPods(nodeName string) ([]*v1.Pod, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -258,9 +259,9 @@ func (n *Node) ensureCordoned(path string) (bool, error) {
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
// FetchNode retrieves a node.
|
// 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)
|
_, 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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -283,8 +284,8 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchNodes retrieves all nodes.
|
// FetchNodes retrieves all nodes.
|
||||||
func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) {
|
func FetchNodes(_ context.Context, f Factory, _ string) (*v1.NodeList, error) {
|
||||||
auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", "", client.ListAccess)
|
auth, err := f.Client().CanI(client.ClusterScope, client.NodeGVR, "", client.ListAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,20 +16,19 @@ import (
|
||||||
type NonResource struct {
|
type NonResource struct {
|
||||||
Factory
|
Factory
|
||||||
|
|
||||||
gvr client.GVR
|
gvr *client.GVR
|
||||||
mx sync.RWMutex
|
mx sync.RWMutex
|
||||||
includeObj bool
|
includeObj bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the resource.
|
// 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.mx.Lock()
|
||||||
{
|
n.Factory, n.gvr = f, gvr
|
||||||
n.Factory, n.gvr = f, gvr
|
|
||||||
}
|
|
||||||
n.mx.Unlock()
|
n.mx.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIncludeObject sets if resource object should be included in the api server response.
|
||||||
func (n *NonResource) SetIncludeObject(f bool) {
|
func (n *NonResource) SetIncludeObject(f bool) {
|
||||||
n.includeObj = f
|
n.includeObj = f
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +56,6 @@ func (n *NonResource) GVR() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the given resource.
|
// 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")
|
return nil, fmt.Errorf("nyi")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func getPatchPodSpec(imageSpecs ImageSpecs) PodSpec {
|
||||||
return 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 {
|
for _, spec := range imageSpecs {
|
||||||
if spec.Init {
|
if spec.Init {
|
||||||
initElementsOrders = append(initElementsOrders, Element{Name: spec.Name})
|
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.
|
// 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)
|
pod, err := p.GetInstance(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -108,7 +108,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, ok := u.Object["spec"].(map[string]interface{})
|
spec, ok := u.Object["spec"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("expecting interface map but got `%T", o)
|
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.
|
// Logs fetch container logs for a given pod and container.
|
||||||
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
|
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
cc := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||||
for _, c := range pod.Spec.Containers {
|
for i := range pod.Spec.Containers {
|
||||||
cc = append(cc, c.Name)
|
cc = append(cc, pod.Spec.Containers[i].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeInit {
|
if includeInit {
|
||||||
for _, c := range pod.Spec.InitContainers {
|
for i := range pod.Spec.InitContainers {
|
||||||
cc = append(cc, c.Name)
|
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.
|
// 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
|
return fqn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInstance returns a pod instance.
|
// GetInstance returns a pod instance.
|
||||||
func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no factory in context")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -201,26 +201,26 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
outs := make([]LogChan, 0, coCounts)
|
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
|
opts.DefaultContainer = co
|
||||||
return append(outs, tailLogs(ctx, p, opts)), nil
|
return append(outs, tailLogs(ctx, p, opts)), nil
|
||||||
}
|
}
|
||||||
if opts.HasContainer() && !opts.AllContainers {
|
if opts.HasContainer() && !opts.AllContainers {
|
||||||
return append(outs, tailLogs(ctx, p, opts)), nil
|
return append(outs, tailLogs(ctx, p, opts)), nil
|
||||||
}
|
}
|
||||||
for _, co := range po.Spec.InitContainers {
|
for i := range po.Spec.InitContainers {
|
||||||
cfg := opts.Clone()
|
cfg := opts.Clone()
|
||||||
cfg.Container = co.Name
|
cfg.Container = po.Spec.InitContainers[i].Name
|
||||||
outs = append(outs, tailLogs(ctx, p, cfg))
|
outs = append(outs, tailLogs(ctx, p, cfg))
|
||||||
}
|
}
|
||||||
for _, co := range po.Spec.Containers {
|
for i := range po.Spec.Containers {
|
||||||
cfg := opts.Clone()
|
cfg := opts.Clone()
|
||||||
cfg.Container = co.Name
|
cfg.Container = po.Spec.Containers[i].Name
|
||||||
outs = append(outs, tailLogs(ctx, p, cfg))
|
outs = append(outs, tailLogs(ctx, p, cfg))
|
||||||
}
|
}
|
||||||
for _, co := range po.Spec.EphemeralContainers {
|
for i := range po.Spec.EphemeralContainers {
|
||||||
cfg := opts.Clone()
|
cfg := opts.Clone()
|
||||||
cfg.Container = co.Name
|
cfg.Container = po.Spec.EphemeralContainers[i].Name
|
||||||
outs = append(outs, tailLogs(ctx, p, cfg))
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +277,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case CmGVR:
|
case client.CmGVR:
|
||||||
if !hasConfigMap(&pod.Spec, n) {
|
if !hasConfigMap(&pod.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +285,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
|
||||||
GVR: p.GVR(),
|
GVR: p.GVR(),
|
||||||
FQN: client.FQN(pod.Namespace, pod.Name),
|
FQN: client.FQN(pod.Namespace, pod.Name),
|
||||||
})
|
})
|
||||||
case SecGVR:
|
case client.SecGVR:
|
||||||
found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
|
found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Locate secret failed",
|
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(),
|
GVR: p.GVR(),
|
||||||
FQN: client.FQN(pod.Namespace, pod.Name),
|
FQN: client.FQN(pod.Namespace, pod.Name),
|
||||||
})
|
})
|
||||||
case PvcGVR:
|
case client.PvcGVR:
|
||||||
if !hasPVC(&pod.Spec, n) {
|
if !hasPVC(&pod.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +309,7 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (
|
||||||
GVR: p.GVR(),
|
GVR: p.GVR(),
|
||||||
FQN: client.FQN(pod.Namespace, pod.Name),
|
FQN: client.FQN(pod.Namespace, pod.Name),
|
||||||
})
|
})
|
||||||
case PcGVR:
|
case client.PcGVR:
|
||||||
if !hasPC(&pod.Spec, n) {
|
if !hasPC(&pod.Spec, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -336,20 +336,20 @@ func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
podOpts := opts.ToPodLogOptions()
|
podOpts := opts.ToPodLogOptions()
|
||||||
for r := 0; r < logRetryCount; r++ {
|
for range logRetryCount {
|
||||||
req, err := logger.Logs(opts.Path, podOpts)
|
req, err := logger.Logs(opts.Path, podOpts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// This call will block if nothing is in the stream!!
|
// 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)
|
wg.Add(1)
|
||||||
go readLogs(ctx, &wg, stream, out, opts)
|
go readLogs(ctx, &wg, stream, out, opts)
|
||||||
return
|
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 {
|
} else {
|
||||||
slog.Error("Log request failed",
|
slog.Error("Log request failed",
|
||||||
slogs.Container, opts.Info(),
|
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.
|
// MetaFQN returns a fully qualified resource name.
|
||||||
func MetaFQN(m metav1.ObjectMeta) string {
|
func MetaFQN(m *metav1.ObjectMeta) string {
|
||||||
if m.Namespace == "" {
|
if m.Namespace == "" {
|
||||||
return m.Name
|
return m.Name
|
||||||
}
|
}
|
||||||
|
|
@ -434,13 +434,14 @@ func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
podSpec := pod.Spec
|
podSpec := pod.Spec
|
||||||
|
|
||||||
return &podSpec, nil
|
return &podSpec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetImages sets container images.
|
// SetImages sets container images.
|
||||||
func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -469,10 +470,11 @@ func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs)
|
||||||
jsonPatch,
|
jsonPatch,
|
||||||
metav1.PatchOptions{},
|
metav1.PatchOptions{},
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
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)
|
pod, err := p.GetInstance(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
|
|
@ -481,6 +483,7 @@ func (p *Pod) isControlled(path string) (string, bool, error) {
|
||||||
if len(references) > 0 {
|
if len(references) > 0 {
|
||||||
return fmt.Sprintf("%s/%s", references[0].Kind, references[0].Name), true, nil
|
return fmt.Sprintf("%s/%s", references[0].Kind, references[0].Name), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", false, nil
|
return "", false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func TestGetDefaultContainer(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
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.wantContainer, container)
|
||||||
assert.Equal(t, u.wantOk, ok)
|
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()
|
p.path, p.tunnel, p.age = path, tt, time.Now()
|
||||||
|
|
||||||
ns, n := client.Namespaced(path)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +132,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por
|
||||||
|
|
||||||
podName := strings.Split(n, "|")[0]
|
podName := strings.Split(n, "|")[0]
|
||||||
var res Pod
|
var res Pod
|
||||||
res.Init(p, client.NewGVR("v1/pods"))
|
res.Init(p, client.PodGVR)
|
||||||
pod, err := res.GetInstance(client.FQN(ns, podName))
|
pod, err := res.GetInstance(client.FQN(ns, podName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ type Pulse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List lists out pulses.
|
// 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")
|
return nil, fmt.Errorf("NYI")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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 (
|
var (
|
||||||
_ Accessor = (*Rbac)(nil)
|
_ Accessor = (*Rbac)(nil)
|
||||||
_ Nuker = (*Rbac)(nil)
|
_ Nuker = (*Rbac)(nil)
|
||||||
|
|
@ -37,7 +30,7 @@ type Rbac struct {
|
||||||
|
|
||||||
// List lists out rbac resources.
|
// List lists out rbac resources.
|
||||||
func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
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 {
|
if !ok {
|
||||||
return nil, fmt.Errorf("expecting a context gvr")
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var crb rbacv1.ClusterRoleBinding
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var cr rbacv1.ClusterRole
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rb rbacv1.RoleBinding
|
var rb rbacv1.RoleBinding
|
||||||
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb); err != nil {
|
if e := runtime.DefaultUnstructuredConverter.FromUnstructured(rbo.(*unstructured.Unstructured).Object, &rb); e != nil {
|
||||||
return nil, err
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
if rb.RoleRef.Kind == "ClusterRole" {
|
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 {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
var cr rbacv1.ClusterRole
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) {
|
||||||
slog.Debug("LOAD-CR", slogs.FQN, fqn)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cr rbacv1.ClusterRole
|
var cr rbacv1.ClusterRole
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
|
||||||
if err != nil {
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ro rbacv1.Role
|
var ro rbacv1.Role
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro)
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ type Policy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns available policies.
|
// 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)
|
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("expecting a context subject kind")
|
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)
|
ns, n := client.Namespaced(name)
|
||||||
var nn []string
|
var nn []string
|
||||||
for _, crb := range crbs {
|
for i := range crbs {
|
||||||
for _, s := range crb.Subjects {
|
for _, s := range crbs[i].Subjects {
|
||||||
s := s
|
|
||||||
if isSameSubject(kind, ns, n, &s) {
|
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))
|
rows := make(render.Policies, 0, len(nn))
|
||||||
for _, cr := range crs {
|
for i := range crs {
|
||||||
if !inList(nn, cr.Name) {
|
if !inList(nn, crs[i].Name) {
|
||||||
continue
|
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
|
return rows, nil
|
||||||
|
|
@ -101,13 +100,13 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rows := make(render.Policies, 0, len(crs))
|
rows := make(render.Policies, 0, len(crs))
|
||||||
for _, cr := range crs {
|
for i := range crs {
|
||||||
if rbNs, ok := rbsMap["ClusterRole:"+cr.Name]; ok {
|
if rbNs, ok := rbsMap["ClusterRole:"+crs[i].Name]; ok {
|
||||||
slog.Debug("Loading rules for clusterrole",
|
slog.Debug("Loading rules for clusterrole",
|
||||||
slogs.Namespace, rbNs,
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, ro := range ros {
|
for i := range ros {
|
||||||
if _, ok := rbsMap["Role:"+ro.Name]; !ok {
|
if _, ok := rbsMap["Role:"+ros[i].Name]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slog.Debug("Loading rules for role",
|
slog.Debug("Loading rules for role",
|
||||||
slogs.Namespace, ro.Namespace,
|
slogs.Namespace, ros[i].Namespace,
|
||||||
slogs.ResName, ro.Name,
|
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
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +147,7 @@ func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -173,11 +172,10 @@ func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]strin
|
||||||
|
|
||||||
ns, n := client.Namespaced(name)
|
ns, n := client.Namespaced(name)
|
||||||
ss := make(map[string]string, len(rbs))
|
ss := make(map[string]string, len(rbs))
|
||||||
for _, rb := range rbs {
|
for i := range rbs {
|
||||||
for _, s := range rb.Subjects {
|
for _, s := range rbs[i].Subjects {
|
||||||
s := s
|
|
||||||
if isSameSubject(kind, ns, n, &s) {
|
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) {
|
func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
|
||||||
const gvr = "rbac.authorization.k8s.io/v1/clusterroles"
|
oo, err := p.getFactory().List(client.CrGVR, client.ClusterScope, false, labels.Everything())
|
||||||
|
|
||||||
oo, err := p.getFactory().List(gvr, client.ClusterScope, false, labels.Everything())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -220,9 +216,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) fetchRoles() ([]rbacv1.Role, error) {
|
func (p *Policy) fetchRoles() ([]rbacv1.Role, error) {
|
||||||
const gvr = "rbac.authorization.k8s.io/v1/roles"
|
oo, err := p.getFactory().List(client.RoGVR, client.BlankNamespace, false, labels.Everything())
|
||||||
|
|
||||||
oo, err := p.getFactory().List(gvr, client.BlankNamespace, false, labels.Everything())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ type Subject struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns a collection of subjects.
|
// 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)
|
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("expecting a SubjectKind")
|
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))
|
oo := make(render.Subjects, 0, len(crbs))
|
||||||
for _, crb := range crbs {
|
for i := range crbs {
|
||||||
for _, su := range crb.Subjects {
|
for _, su := range crbs[i].Subjects {
|
||||||
if su.Kind != kind {
|
if su.Kind != kind {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oo = oo.Upsert(render.SubjectRes{
|
oo = oo.Upsert(render.SubjectRes{
|
||||||
Name: su.Name,
|
Name: su.Name,
|
||||||
Kind: "ClusterRoleBinding",
|
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))
|
oo := make(render.Subjects, 0, len(rbs))
|
||||||
for _, rb := range rbs {
|
for i := range rbs {
|
||||||
for _, su := range rb.Subjects {
|
for _, su := range rbs[i].Subjects {
|
||||||
if su.Kind != kind {
|
if su.Kind != kind {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oo = oo.Upsert(render.SubjectRes{
|
oo = oo.Upsert(render.SubjectRes{
|
||||||
Name: su.Name,
|
Name: su.Name,
|
||||||
Kind: "RoleBinding",
|
Kind: "RoleBinding",
|
||||||
FirstLocation: rb.Name,
|
FirstLocation: rbs[i].Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ type Reference struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List collects all references.
|
// List collects all references.
|
||||||
func (r *Reference) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
func (r *Reference) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||||
gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR)
|
gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no context for gvr found")
|
return nil, errors.New("no context for gvr found")
|
||||||
}
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case SaGVR:
|
case client.SaGVR:
|
||||||
return r.ScanSA(ctx)
|
return r.ScanSA(ctx)
|
||||||
default:
|
default:
|
||||||
return r.Scan(ctx)
|
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.
|
// 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")
|
panic("NYI")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ package dao
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
|
@ -27,12 +27,8 @@ const (
|
||||||
k9sCat = "k9s"
|
k9sCat = "k9s"
|
||||||
helmCat = "helm"
|
helmCat = "helm"
|
||||||
scaleCat = "scale"
|
scaleCat = "scale"
|
||||||
crdGVR = "apiextensions.k8s.io/v1/customresourcedefinitions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MetaAccess tracks resources metadata.
|
|
||||||
var MetaAccess = NewMeta()
|
|
||||||
|
|
||||||
var stdGroups = sets.New[string](
|
var stdGroups = sets.New[string](
|
||||||
"apps/v1",
|
"apps/v1",
|
||||||
"autoscaling/v1",
|
"autoscaling/v1",
|
||||||
|
|
@ -47,12 +43,18 @@ var stdGroups = sets.New[string](
|
||||||
"v1",
|
"v1",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ResourceMetas represents a collection of resource metadata.
|
||||||
|
type ResourceMetas map[*client.GVR]*metav1.APIResource
|
||||||
|
|
||||||
func (m ResourceMetas) clear() {
|
func (m ResourceMetas) clear() {
|
||||||
for k := range m {
|
for k := range m {
|
||||||
delete(m, k)
|
delete(m, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MetaAccess tracks resources metadata.
|
||||||
|
var MetaAccess = NewMeta()
|
||||||
|
|
||||||
// Meta represents available resource metas.
|
// Meta represents available resource metas.
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
resMetas ResourceMetas
|
resMetas ResourceMetas
|
||||||
|
|
@ -64,71 +66,40 @@ func NewMeta() *Meta {
|
||||||
return &Meta{resMetas: make(ResourceMetas)}
|
return &Meta{resMetas: make(ResourceMetas)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessorFor returns a client accessor for a resource if registered.
|
func (m *Meta) Lookup(cmd string) *client.GVR {
|
||||||
// Otherwise it returns a generic accessor.
|
m.mx.RLock()
|
||||||
// Customize here for non resource types or types with metrics or logs.
|
defer m.mx.RUnlock()
|
||||||
func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
for gvr, meta := range m.resMetas {
|
||||||
m := Accessors{
|
if slices.Contains(meta.ShortNames, cmd) {
|
||||||
client.NewGVR("workloads"): &Workload{},
|
return gvr
|
||||||
client.NewGVR("contexts"): &Context{},
|
}
|
||||||
client.NewGVR("containers"): &Container{},
|
if meta.Name == cmd || meta.SingularName == cmd || meta.Kind == cmd {
|
||||||
client.NewGVR("scans"): &ImageScan{},
|
return gvr
|
||||||
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{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := m[gvr]
|
return client.NoGVR
|
||||||
if !ok {
|
|
||||||
r = new(Scaler)
|
|
||||||
slog.Debug("No DAO registry entry. Using generics!", slogs.GVR, gvr)
|
|
||||||
}
|
|
||||||
r.Init(f, gvr)
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterMeta registers a new resource meta object.
|
// 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()
|
m.mx.Lock()
|
||||||
defer m.mx.Unlock()
|
defer m.mx.Unlock()
|
||||||
|
|
||||||
m.resMetas[client.NewGVR(gvr)] = res
|
m.resMetas[client.NewGVR(gvr)] = res
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllGVRs returns all cluster resources.
|
// AllGVRs returns all sorted cluster resources.
|
||||||
func (m *Meta) AllGVRs() client.GVRs {
|
func (m *Meta) AllGVRs() client.GVRs {
|
||||||
m.mx.RLock()
|
m.mx.RLock()
|
||||||
defer m.mx.RUnlock()
|
defer m.mx.RUnlock()
|
||||||
|
kk := slices.Collect(maps.Keys(m.resMetas))
|
||||||
|
|
||||||
kk := make(client.GVRs, 0, len(m.resMetas))
|
return client.GVRs(kk)
|
||||||
for k := range m.resMetas {
|
|
||||||
kk = append(kk, k)
|
|
||||||
}
|
|
||||||
sort.Sort(kk)
|
|
||||||
|
|
||||||
return kk
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVK2GVR convert gvk to gvr
|
// 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()
|
m.mx.RLock()
|
||||||
defer m.mx.RUnlock()
|
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.
|
// 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()
|
m.mx.RLock()
|
||||||
defer m.mx.RUnlock()
|
defer m.mx.RUnlock()
|
||||||
|
|
||||||
meta, ok := m.resMetas[gvr]
|
if meta, ok := m.resMetas[gvr]; ok {
|
||||||
if !ok {
|
return meta, nil
|
||||||
return metav1.APIResource{}, fmt.Errorf("no resource meta defined for %q", gvr)
|
|
||||||
}
|
}
|
||||||
return meta, nil
|
|
||||||
|
return new(metav1.APIResource), fmt.Errorf("no resource meta defined for\n %q", gvr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCRD checks if resource represents a CRD
|
// 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)
|
return slices.Contains(r.Categories, crdCat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsK8sMeta checks for non resource meta.
|
// 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 !slices.ContainsFunc(m.Categories, func(category string) bool {
|
||||||
return category == k9sCat || category == helmCat
|
return category == k9sCat || category == helmCat
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsK9sMeta checks for non resource meta.
|
// IsK9sMeta checks for non resource meta.
|
||||||
func IsK9sMeta(m metav1.APIResource) bool {
|
func IsK9sMeta(m *metav1.APIResource) bool {
|
||||||
return slices.Contains(m.Categories, k9sCat)
|
return slices.Contains(m.Categories, k9sCat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsScalable check if the resource can be scaled
|
// 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)
|
return slices.Contains(m.Categories, scaleCat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +172,7 @@ func loadNonResource(m ResourceMetas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadK9s(m ResourceMetas) {
|
func loadK9s(m ResourceMetas) {
|
||||||
m[client.NewGVR("workloads")] = metav1.APIResource{
|
m[client.WkGVR] = &metav1.APIResource{
|
||||||
Name: "workloads",
|
Name: "workloads",
|
||||||
Kind: "Workload",
|
Kind: "Workload",
|
||||||
SingularName: "workload",
|
SingularName: "workload",
|
||||||
|
|
@ -209,48 +180,48 @@ func loadK9s(m ResourceMetas) {
|
||||||
ShortNames: []string{"wk"},
|
ShortNames: []string{"wk"},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("pulses")] = metav1.APIResource{
|
m[client.PuGVR] = &metav1.APIResource{
|
||||||
Name: "pulses",
|
Name: "pulses",
|
||||||
Kind: "Pulse",
|
Kind: "Pulse",
|
||||||
SingularName: "pulses",
|
SingularName: "pulse",
|
||||||
ShortNames: []string{"hz", "pu"},
|
ShortNames: []string{"hz", "pu"},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("dir")] = metav1.APIResource{
|
m[client.DirGVR] = &metav1.APIResource{
|
||||||
Name: "dir",
|
Name: "dirs",
|
||||||
Kind: "Dir",
|
Kind: "Dir",
|
||||||
SingularName: "dir",
|
SingularName: "dir",
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("xrays")] = metav1.APIResource{
|
m[client.XGVR] = &metav1.APIResource{
|
||||||
Name: "xray",
|
Name: "xrays",
|
||||||
Kind: "XRays",
|
Kind: "XRays",
|
||||||
SingularName: "xray",
|
SingularName: "xray",
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("references")] = metav1.APIResource{
|
m[client.RefGVR] = &metav1.APIResource{
|
||||||
Name: "references",
|
Name: "references",
|
||||||
Kind: "References",
|
Kind: "References",
|
||||||
SingularName: "reference",
|
SingularName: "reference",
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("aliases")] = metav1.APIResource{
|
m[client.AliGVR] = &metav1.APIResource{
|
||||||
Name: "aliases",
|
Name: "aliases",
|
||||||
Kind: "Aliases",
|
Kind: "Aliases",
|
||||||
SingularName: "alias",
|
SingularName: "alias",
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("contexts")] = metav1.APIResource{
|
m[client.CtGVR] = &metav1.APIResource{
|
||||||
Name: "contexts",
|
Name: client.CtGVR.String(),
|
||||||
Kind: "Contexts",
|
Kind: "Contexts",
|
||||||
SingularName: "context",
|
SingularName: "context",
|
||||||
ShortNames: []string{"ctx"},
|
ShortNames: []string{"ctx"},
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("screendumps")] = metav1.APIResource{
|
m[client.SdGVR] = &metav1.APIResource{
|
||||||
Name: "screendumps",
|
Name: "screendumps",
|
||||||
Kind: "ScreenDumps",
|
Kind: "ScreenDumps",
|
||||||
SingularName: "screendump",
|
SingularName: "screendump",
|
||||||
|
|
@ -258,7 +229,7 @@ func loadK9s(m ResourceMetas) {
|
||||||
Verbs: []string{"delete"},
|
Verbs: []string{"delete"},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("benchmarks")] = metav1.APIResource{
|
m[client.BeGVR] = &metav1.APIResource{
|
||||||
Name: "benchmarks",
|
Name: "benchmarks",
|
||||||
Kind: "Benchmarks",
|
Kind: "Benchmarks",
|
||||||
SingularName: "benchmark",
|
SingularName: "benchmark",
|
||||||
|
|
@ -266,7 +237,7 @@ func loadK9s(m ResourceMetas) {
|
||||||
Verbs: []string{"delete"},
|
Verbs: []string{"delete"},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("portforwards")] = metav1.APIResource{
|
m[client.PfGVR] = &metav1.APIResource{
|
||||||
Name: "portforwards",
|
Name: "portforwards",
|
||||||
Namespaced: true,
|
Namespaced: true,
|
||||||
Kind: "PortForwards",
|
Kind: "PortForwards",
|
||||||
|
|
@ -275,14 +246,14 @@ func loadK9s(m ResourceMetas) {
|
||||||
Verbs: []string{"delete"},
|
Verbs: []string{"delete"},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("containers")] = metav1.APIResource{
|
m[client.CoGVR] = &metav1.APIResource{
|
||||||
Name: "containers",
|
Name: "containers",
|
||||||
Kind: "Containers",
|
Kind: "Containers",
|
||||||
SingularName: "container",
|
SingularName: "container",
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("scans")] = metav1.APIResource{
|
m[client.ScnGVR] = &metav1.APIResource{
|
||||||
Name: "scans",
|
Name: "scans",
|
||||||
Kind: "Scans",
|
Kind: "Scans",
|
||||||
SingularName: "scan",
|
SingularName: "scan",
|
||||||
|
|
@ -292,14 +263,14 @@ func loadK9s(m ResourceMetas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadHelm(m ResourceMetas) {
|
func loadHelm(m ResourceMetas) {
|
||||||
m[client.NewGVR("helm")] = metav1.APIResource{
|
m[client.HmGVR] = &metav1.APIResource{
|
||||||
Name: "helm",
|
Name: "helm",
|
||||||
Kind: "Helm",
|
Kind: "Helm",
|
||||||
Namespaced: true,
|
Namespaced: true,
|
||||||
Verbs: []string{"delete"},
|
Verbs: []string{"delete"},
|
||||||
Categories: []string{helmCat},
|
Categories: []string{helmCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("helm-history")] = metav1.APIResource{
|
m[client.HmhGVR] = &metav1.APIResource{
|
||||||
Name: "history",
|
Name: "history",
|
||||||
Kind: "History",
|
Kind: "History",
|
||||||
Namespaced: true,
|
Namespaced: true,
|
||||||
|
|
@ -309,23 +280,23 @@ func loadHelm(m ResourceMetas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadRBAC(m ResourceMetas) {
|
func loadRBAC(m ResourceMetas) {
|
||||||
m[client.NewGVR("rbac")] = metav1.APIResource{
|
m[client.RbacGVR] = &metav1.APIResource{
|
||||||
Name: "rbacs",
|
Name: "rbacs",
|
||||||
Kind: "Rules",
|
Kind: "Rules",
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("policy")] = metav1.APIResource{
|
m[client.PolGVR] = &metav1.APIResource{
|
||||||
Name: "policies",
|
Name: "policies",
|
||||||
Kind: "Rules",
|
Kind: "Rules",
|
||||||
Namespaced: true,
|
Namespaced: true,
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("users")] = metav1.APIResource{
|
m[client.UsrGVR] = &metav1.APIResource{
|
||||||
Name: "users",
|
Name: "users",
|
||||||
Kind: "User",
|
Kind: "User",
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
}
|
}
|
||||||
m[client.NewGVR("groups")] = metav1.APIResource{
|
m[client.GrpGVR] = &metav1.APIResource{
|
||||||
Name: "groups",
|
Name: "groups",
|
||||||
Kind: "Group",
|
Kind: "Group",
|
||||||
Categories: []string{k9sCat},
|
Categories: []string{k9sCat},
|
||||||
|
|
@ -333,7 +304,7 @@ func loadRBAC(m ResourceMetas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPreferred(f Factory, m ResourceMetas) error {
|
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")
|
slog.Error("Load cluster resources - No API server connection")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +318,8 @@ func loadPreferred(f Factory, m ResourceMetas) error {
|
||||||
slog.Debug("Failed to load preferred resources", slogs.Error, err)
|
slog.Debug("Failed to load preferred resources", slogs.Error, err)
|
||||||
}
|
}
|
||||||
for _, r := range rr {
|
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)
|
gvr := client.FromGVAndR(r.GroupVersion, res.Name)
|
||||||
if isDeprecated(gvr) {
|
if isDeprecated(gvr) {
|
||||||
continue
|
continue
|
||||||
|
|
@ -359,7 +331,7 @@ func loadPreferred(f Factory, m ResourceMetas) error {
|
||||||
if !isStandardGroup(r.GroupVersion) {
|
if !isStandardGroup(r.GroupVersion) {
|
||||||
res.Categories = append(res.Categories, crdCat)
|
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")
|
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"),
|
client.NewGVR("extensions/v1beta1/ingresses"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func isDeprecated(gvr client.GVR) bool {
|
func isDeprecated(gvr *client.GVR) bool {
|
||||||
return deprecatedGVRs.Has(gvr)
|
return deprecatedGVRs.Has(gvr) || gvr.V() == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadCRDs Wait for the cache to synced and then add some additional properties to CRD.
|
// loadCRDs Wait for the cache to synced and then add some additional properties to CRD.
|
||||||
func loadCRDs(f Factory, m ResourceMetas) {
|
func loadCRDs(f Factory, m ResourceMetas) {
|
||||||
if f.Client() == nil || !f.Client().ConnectionOK() {
|
if f == nil || f.Client() == nil || !f.Client().ConnectionOK() {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
slog.Warn("CRDs load Fail", slogs.Error, err)
|
slog.Warn("CRDs load Fail", slogs.Error, err)
|
||||||
return
|
return
|
||||||
|
|
@ -397,125 +370,13 @@ func loadCRDs(f Factory, m ResourceMetas) {
|
||||||
slog.Error("CRD conversion failed", slogs.Error, err)
|
slog.Error("CRD conversion failed", slogs.Error, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gvr, version, ok := newGVRFromCRD(&crd)
|
for gvr, version := range client.NewGVRFromCRD(&crd) {
|
||||||
if !ok {
|
if meta, ok := m[gvr]; ok && version.Subresources != nil && version.Subresources.Scale != nil {
|
||||||
continue
|
if !slices.Contains(meta.Categories, scaleCat) {
|
||||||
}
|
meta.Categories = append(meta.Categories, scaleCat)
|
||||||
|
m[gvr] = meta
|
||||||
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
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractMeta(t *testing.T) {
|
func TestMetaFor(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) {
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
m map[string]interface{}
|
gvr *client.GVR
|
||||||
n string
|
err error
|
||||||
nn []string
|
e metav1.APIResource
|
||||||
ee []error
|
|
||||||
}{
|
}{
|
||||||
"plain": {
|
"xray-gvr": {
|
||||||
m: map[string]interface{}{"shortNames": []string{"a", "b", "c"}},
|
gvr: client.XGVR,
|
||||||
n: "shortNames",
|
e: metav1.APIResource{
|
||||||
nn: []string{"a", "b", "c"},
|
Name: "xrays",
|
||||||
|
Kind: "XRays",
|
||||||
|
SingularName: "xray",
|
||||||
|
Categories: []string{k9sCat},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"empty": {
|
|
||||||
m: map[string]interface{}{},
|
"xray": {
|
||||||
n: "shortNames",
|
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 {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
ss, e := extractSlice(u.m, u.n, ee)
|
meta, err := m.MetaFor(u.gvr)
|
||||||
assert.Equal(t, u.ee, e)
|
assert.Equal(t, u.err, err)
|
||||||
assert.Equal(t, u.nn, ss)
|
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.
|
// Get returns a resource instance if found, else an error.
|
||||||
func (r *Resource) Get(_ context.Context, path string) (runtime.Object, 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.
|
// ToYAML returns a resource yaml.
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,12 @@ func (r *RestMapper) resourceFor(resourceArg string) (schema.GroupVersionResourc
|
||||||
|
|
||||||
gvr, err = mapper.ResourceFor(gr.WithVersion(""))
|
gvr, err = mapper.ResourceFor(gr.WithVersion(""))
|
||||||
if err != nil {
|
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'", gr.Resource)
|
||||||
}
|
}
|
||||||
return gvr, fmt.Errorf("the server doesn't have a resource type '%s' in group '%s'", gr.Resource, gr.Group)
|
return gvr, fmt.Errorf("the server doesn't have a resource type '%s' in group '%s'", gr.Resource, gr.Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gvr, nil
|
return gvr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue