K9s/rel v0.30.0 (#2361)
* [Maint] Refactor VS col handling * [Bug] Add helm hist <enter> values cmd * [Bug] Add context specific skins within a given cluster config. * [Maint] Image scan controls * [Bug] Fix fwd+bench timestamp * [Refact] all-ns const * [Maint] update tabledefs from metav1beta1 to metav1 * [Feat] Introduce workload view * [Maint] Add convenience to map out ns names - Refactor allnamespaces * [Cleanup] axe pegomock * [Refact] Use gvr type vs string * [Feat] Add blacklist scans exclusions * [Feat] setLabels for stored commands * [Maint] Rename api-group column * [Refact] gvr type refactor * [Maint] Cleaning up * [Bug] Add ability to skin based on context - Handles cluster spanning *contexts * [Maint] Cleaning up * [Feat] Cmd interpreter * [Maint] Clean up + bug fixes * [Feat] Changed k9s config loader > NOTE: !!Breaking change!! - Make k9s config readonly - Move writable artifacts to XDG data dir - Move transient artifacts to XDG state dir - Add per context skin option - Add per context readonly option - Consistent pluralization file names to yaml section * [Docs] Update release and README docs * [Maint] Rebase + cleanup * [Maint] Normalize config extensions all yml -> yaml * [Maint] Cleaning up + fixesmine
parent
7897fb0eef
commit
dcec53e061
|
|
@ -4,7 +4,7 @@
|
|||
.envrc
|
||||
cov.out
|
||||
execs
|
||||
k9s
|
||||
/k9s
|
||||
/k8s
|
||||
dist
|
||||
notes
|
||||
|
|
@ -23,3 +23,4 @@ demos
|
|||
/code
|
||||
kind
|
||||
*.snap
|
||||
/stresser
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
project_name: k9s
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
- go generate ./...
|
||||
|
||||
release:
|
||||
prerelease: false
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu
|
|||
# Build the final Docker image
|
||||
|
||||
FROM alpine:3.19.0
|
||||
ARG KUBECTL_VERSION="v1.27.3"
|
||||
ARG KUBECTL_VERSION="v1.29.0"
|
||||
|
||||
COPY --from=build /k9s/execs/k9s /bin/k9s
|
||||
RUN apk add --update ca-certificates \
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
|||
else
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
endif
|
||||
VERSION ?= v0.29.0
|
||||
VERSION ?= v0.30.0
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
420
README.md
420
README.md
|
|
@ -11,7 +11,10 @@ for changes and offers subsequent commands to interact with your observed resour
|
|||
|
||||
## Note...
|
||||
|
||||
As you may know k9s is not pimped out by a big corporation with deep pockets. It is a complex OSS project that demands a lot of my time to maintain and support. K9s will always remain OSS and therefore free! That said if you feel, k9s makes your day to day Kubernetes journey a tad brighter, please consider sponsoring us or purchase a [K9sAlpha license](https://k9salpha.io). Your donations will go a long way in keeping our servers lights on and beers in our fridge!
|
||||
K9s is not pimped out by a big corporation with deep pockets.
|
||||
It is a complex OSS project that demands a lot of my time to maintain and support.
|
||||
K9s will always remain OSS and therefore free! That said, if you feel k9s makes your day to day Kubernetes journey a tad brighter, saves you time and makes you more productive, please consider [sponsoring us!](https://github.com/sponsors/derailed)
|
||||
Your donations will go a long way in keeping our servers lights on and beers in our fridge!
|
||||
|
||||
**Thank you!**
|
||||
|
||||
|
|
@ -28,6 +31,35 @@ As you may know k9s is not pimped out by a big corporation with deep pockets. It
|
|||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
1. Pods
|
||||
<img src="assets/screen_po.png"/>
|
||||
2. Logs
|
||||
<img src="assets/screen_logs.png"/>
|
||||
3. Deployments
|
||||
<img src="assets/screen_dp.png"/>
|
||||
|
||||
---
|
||||
|
||||
## Demo Videos/Recordings
|
||||
|
||||
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
|
||||
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
|
||||
* [K9s v0.29.0](https://youtu.be/oiU3wmoAkBo)
|
||||
* [K9s v0.21.3](https://youtu.be/wG8KCwDAhnw)
|
||||
* [K9s v0.19.X](https://youtu.be/kj-WverKZ24)
|
||||
* [K9s v0.18.0](https://www.youtube.com/watch?v=zMnD5e53yRw)
|
||||
* [K9s v0.17.0](https://www.youtube.com/watch?v=7S33CNLAofk&feature=youtu.be)
|
||||
* [K9s Pulses](https://asciinema.org/a/UbXKPal6IWpTaVAjBBFmizcGN)
|
||||
* [K9s v0.15.1](https://youtu.be/7Fx4XQ2ftpM)
|
||||
* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s)
|
||||
* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I)
|
||||
* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8)
|
||||
* [K9s v0 Demo](https://youtu.be/k7zseUhaXeU)
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Please refer to our [K9s documentation](https://k9scli.io) site for installation, usage, customization and tips.
|
||||
|
|
@ -42,8 +74,7 @@ Wanna discuss K9s features with your fellow `K9sers` or simply show your support
|
|||
## Installation
|
||||
|
||||
K9s is available on Linux, macOS and Windows platforms.
|
||||
|
||||
* Binaries for Linux, Windows and Mac are available as tarballs in the [release](https://github.com/derailed/k9s/releases) page.
|
||||
Binaries for Linux, Windows and Mac are available as tarballs in the [release page](https://github.com/derailed/k9s/releases).
|
||||
|
||||
* Via [Homebrew](https://brew.sh/) for macOS or Linux
|
||||
|
||||
|
|
@ -56,6 +87,7 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
```shell
|
||||
sudo port install k9s
|
||||
```
|
||||
|
||||
* Via [snap](https://snapcraft.io/k9s) for Linux
|
||||
|
||||
```shell
|
||||
|
|
@ -81,6 +113,7 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
```
|
||||
|
||||
* Via [Winget](https://github.com/microsoft/winget-cli) for Windows
|
||||
|
||||
```shell
|
||||
winget install k9s
|
||||
```
|
||||
|
|
@ -132,7 +165,8 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
|
||||
## Building From Source
|
||||
|
||||
K9s is currently using go v1.14 or above. In order to build K9s from source you must:
|
||||
K9s is currently using GO v1.21.X or above.
|
||||
In order to build K9s from source you must:
|
||||
|
||||
1. Clone the repo
|
||||
2. Build and run the executable
|
||||
|
|
@ -164,7 +198,7 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
You can build your own Docker image of k9s from the [Dockerfile](Dockerfile) with the following:
|
||||
|
||||
```shell
|
||||
docker build -t k9s-docker:0.1 .
|
||||
docker build -t k9s-docker:v0.0.1 .
|
||||
```
|
||||
|
||||
You can get the latest stable `kubectl` version and pass it to the `docker build` command with the `--build-arg` option.
|
||||
|
|
@ -200,7 +234,7 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
export K9S_EDITOR=my_fav_editor
|
||||
```
|
||||
|
||||
* K9s prefers recent kubernetes versions ie 1.16+
|
||||
* K9s prefers recent kubernetes versions ie 1.28+
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -208,54 +242,79 @@ K9s is available on Linux, macOS and Windows platforms.
|
|||
|
||||
| k9s | k8s client |
|
||||
| ------------------ | ---------- |
|
||||
| >= v0.27.0 | 0.26.1 |
|
||||
| v0.26.7 - v0.26.6 | 0.25.3 |
|
||||
| v0.26.5 - v0.26.4 | 0.25.1 |
|
||||
| v0.26.3 - v0.26.1 | 0.24.3 |
|
||||
| v0.26.0 - v0.25.19 | 0.24.2 |
|
||||
| v0.25.18 - v0.25.3 | 0.22.3 |
|
||||
| v0.25.2 - v0.25.0 | 0.22.0 |
|
||||
| <= v0.24 | 0.21.3 |
|
||||
| >= v0.27.0 | 1.26.1 |
|
||||
| v0.26.7 - v0.26.6 | 1.25.3 |
|
||||
| v0.26.5 - v0.26.4 | 1.25.1 |
|
||||
| v0.26.3 - v0.26.1 | 1.24.3 |
|
||||
| v0.26.0 - v0.25.19 | 1.24.2 |
|
||||
| v0.25.18 - v0.25.3 | 1.22.3 |
|
||||
| v0.25.2 - v0.25.0 | 1.22.0 |
|
||||
| <= v0.24 | 1.21.3 |
|
||||
|
||||
---
|
||||
|
||||
## The Command Line
|
||||
|
||||
```shell
|
||||
# List all available CLI options
|
||||
k9s help
|
||||
# List current version
|
||||
k9s version
|
||||
|
||||
# To get info about K9s runtime (logs, configs, etc..)
|
||||
k9s info
|
||||
|
||||
# List all available CLI options
|
||||
k9s help
|
||||
|
||||
# To run K9s in a given namespace
|
||||
k9s -n mycoolns
|
||||
|
||||
# Start K9s in an existing KubeConfig context
|
||||
k9s --context coolCtx
|
||||
|
||||
# Start K9s in readonly mode - with all cluster modification commands disabled
|
||||
k9s --readonly
|
||||
```
|
||||
|
||||
## Logs
|
||||
## Logs And Debug Logs
|
||||
|
||||
Given the nature of the ui k9s does produce logs to a specific location. To view the logs and turn on debug mode, use the following commands:
|
||||
Given the nature of the ui k9s does produce logs to a specific location.
|
||||
To view the logs and turn on debug mode, use the following commands:
|
||||
|
||||
```shell
|
||||
# Find out where the logs are stored
|
||||
k9s info
|
||||
# Will produces something like this
|
||||
# ____ __.________
|
||||
# | |/ _/ __ \______
|
||||
# | < \____ / ___/
|
||||
# | | \ / /\___ \
|
||||
# |____|__ \ /____//____ >
|
||||
# \/ \/
|
||||
#
|
||||
# Configuration: ~/Library/Preferences/k9s/config.yml
|
||||
# Logs: /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-fernand.log
|
||||
# Screen Dumps: /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-screens-fernand
|
||||
```
|
||||
|
||||
# To view k9s logs
|
||||
tail -f /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-fernand.log
|
||||
```text
|
||||
____ __.________
|
||||
| |/ _/ __ \______
|
||||
| < \____ / ___/
|
||||
| | \ / /\___ \
|
||||
|____|__ \ /____//____ >
|
||||
\/ \/
|
||||
|
||||
# Start K9s in debug mode
|
||||
Version: vX.Y.Z
|
||||
Config: /Users/fernand/.config/k9s/config.yaml
|
||||
Logs: /Users/fernand/.local/state/k9s/k9s.log
|
||||
Dumps dir: /Users/fernand/.local/state/k9s/screen-dumps
|
||||
Benchmarks dir: /Users/fernand/.local/state/k9s/benchmarks
|
||||
Skins dir: /Users/fernand/.local/share/k9s/skins
|
||||
Contexts dir: /Users/fernand/.local/share/k9s/clusters
|
||||
Custom views file: /Users/fernand/.local/share/k9s/views.yaml
|
||||
Plugins file: /Users/fernand/.local/share/k9s/plugins.yaml
|
||||
Hotkeys file: /Users/fernand/.local/share/k9s/hotkeys.yaml
|
||||
Alias file: /Users/fernand/.local/share/k9s/aliases.yaml
|
||||
```
|
||||
|
||||
### View K9s logs
|
||||
|
||||
```shell
|
||||
tail -f /Users/fernand/.local/data/k9s/k9s.log
|
||||
```
|
||||
|
||||
### Start K9s in debug mode
|
||||
|
||||
```shell
|
||||
k9s -l debug
|
||||
```
|
||||
|
||||
|
|
@ -263,57 +322,31 @@ k9s -l debug
|
|||
|
||||
K9s uses aliases to navigate most K8s resources.
|
||||
|
||||
| Action | Command | Comment |
|
||||
|----------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------|
|
||||
| Show active keyboard mnemonics and help | `?` | |
|
||||
| Show all available resource alias | `ctrl-a` | |
|
||||
| To bail out of K9s | `:q`, `ctrl-c` | |
|
||||
| View a Kubernetes resource using singular/plural or short-name | `:`po⏎ | accepts singular, plural, short-name or alias ie pod or pods |
|
||||
| View a Kubernetes resource in a given namespace | `:`alias namespace⏎ | |
|
||||
| Filter out a resource view given a filter | `/`filter⏎ | Regex2 supported ie `fred|blee` to filter resources named fred or blee |
|
||||
| Inverse regex filter | `/`! filter⏎ | Keep everything that *doesn't* match. |
|
||||
| Filter resource view by labels | `/`-l label-selector⏎ | |
|
||||
| Fuzzy find a resource given a filter | `/`-f filter⏎ | |
|
||||
| Bails out of view/command/filter mode | `<esc>` | |
|
||||
| Key mapping to describe, view, edit, view logs,... | `d`,`v`, `e`, `l`,... | |
|
||||
| To view and switch to another Kubernetes context (Pod view) | `:`ctx⏎ | |
|
||||
| To view and switch directly to another Kubernetes context (Last used view) | `:`ctx context-name⏎ | |
|
||||
| To view and switch to another Kubernetes namespace | `:`ns⏎ | |
|
||||
| To view all saved resources | `:`screendump or sd⏎ | |
|
||||
| To delete a resource (TAB and ENTER to confirm) | `ctrl-d` | |
|
||||
| To kill a resource (no confirmation dialog, equivalent to kubectl delete --now) | `ctrl-k` | |
|
||||
| Launch pulses view | `:`pulses or pu⏎ | |
|
||||
| Launch XRay view | `:`xray RESOURCE [NAMESPACE]⏎ | RESOURCE can be one of po, svc, dp, rs, sts, ds, NAMESPACE is optional |
|
||||
| Launch Popeye view | `:`popeye or pop⏎ | See [popeye](#popeye) |
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
1. Pods
|
||||
<img src="assets/screen_po.png"/>
|
||||
1. Logs
|
||||
<img src="assets/screen_logs.png"/>
|
||||
1. Deployments
|
||||
<img src="assets/screen_dp.png"/>
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Demo Videos/Recordings
|
||||
|
||||
* [k9s Kubernetes UI - A Terminal-Based Vim-Like Kubernetes Dashboard](https://youtu.be/boaW9odvRCc)
|
||||
* [K9s v0.21.3](https://youtu.be/wG8KCwDAhnw)
|
||||
* [K9s v0.19.X](https://youtu.be/kj-WverKZ24)
|
||||
* [K9s v0.18.0](https://www.youtube.com/watch?v=zMnD5e53yRw)
|
||||
* [K9s v0.17.0](https://www.youtube.com/watch?v=7S33CNLAofk&feature=youtu.be)
|
||||
* [K9s Pulses](https://asciinema.org/a/UbXKPal6IWpTaVAjBBFmizcGN)
|
||||
* [K9s v0.15.1](https://youtu.be/7Fx4XQ2ftpM)
|
||||
* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s)
|
||||
* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I)
|
||||
* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8)
|
||||
* [K9s v0 Demo](https://youtu.be/k7zseUhaXeU)
|
||||
| Action | Command | Comment |
|
||||
|---------------------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------|
|
||||
| Show active keyboard mnemonics and help | `?` | |
|
||||
| Show all available resource alias | `ctrl-a` | |
|
||||
| To bail out of K9s | `:q`, `ctrl-c` | |
|
||||
| View a Kubernetes resource using singular/plural or short-name | `:`pod⏎ | accepts singular, plural, short-name or alias ie pod or pods |
|
||||
| View a Kubernetes resource in a given namespace | `:`pod ns-x⏎ | |
|
||||
| View filtered pods (New v0.30.0!) | `:`pod /fred⏎ | View all pods filtered by fred |
|
||||
| View labeled pods (New v0.30.0!) | `:`pod app=fred,env=dev⏎ | View all pods with labels matching app=fred and env=dev |
|
||||
| View pods in a given context (New v0.30.0!) | `:`pod @ctx1⏎ | View all pods in context ctx1. Switches out your current k9s context! |
|
||||
| Filter out a resource view given a filter | `/`filter⏎ | Regex2 supported ie `fred|blee` to filter resources named fred or blee |
|
||||
| Inverse regex filter | `/`! filter⏎ | Keep everything that *doesn't* match. |
|
||||
| Filter resource view by labels | `/`-l label-selector⏎ | |
|
||||
| Fuzzy find a resource given a filter | `/`-f filter⏎ | |
|
||||
| Bails out of view/command/filter mode | `<esc>` | |
|
||||
| Key mapping to describe, view, edit, view logs,... | `d`,`v`, `e`, `l`,... | |
|
||||
| To view and switch to another Kubernetes context (Pod view) | `:`ctx⏎ | |
|
||||
| To view and switch directly to another Kubernetes context (Last used view) | `:`ctx context-name⏎ | |
|
||||
| To view and switch to another Kubernetes namespace | `:`ns⏎ | |
|
||||
| To view all saved resources | `:`screendump or sd⏎ | |
|
||||
| To delete a resource (TAB and ENTER to confirm) | `ctrl-d` | |
|
||||
| To kill a resource (no confirmation dialog, equivalent to kubectl delete --now) | `ctrl-k` | |
|
||||
| Launch pulses view | `:`pulses or pu⏎ | |
|
||||
| Launch XRay view | `:`xray RESOURCE [NAMESPACE]⏎ | RESOURCE can be one of po, svc, dp, rs, sts, ds, NAMESPACE is optional |
|
||||
| Launch Popeye view | `:`popeye or pop⏎ | See [popeye](#popeye) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -327,13 +360,13 @@ K9s uses aliases to navigate most K8s resources.
|
|||
|
||||
> NOTE: This is still in flux and will change while in pre-release stage!
|
||||
|
||||
> NOTE! Thanks to [Mr Alexandru Placenta](https://github.com/placintaalexandru) the config files can now use either `.yml` or `.yaml` mimes.
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yaml
|
||||
k9s:
|
||||
# Enable periodic refresh of resource browser windows. Default false
|
||||
liveViewAutoRefresh: false
|
||||
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
|
||||
screenDumpDir: /tmp/dumps
|
||||
# Represents ui poll intervals. Default 2secs
|
||||
refreshRate: 2
|
||||
# Number of retries once the connection to the api-server is lost. Default 15.
|
||||
|
|
@ -368,12 +401,6 @@ K9s uses aliases to navigate most K8s resources.
|
|||
textWrap: false
|
||||
# Toggles log line timestamp info. Default false
|
||||
showTime: false
|
||||
# Indicates the current kube context. Defaults to current context
|
||||
currentContext: minikube
|
||||
# Indicates the current kube cluster. Defaults to current context cluster
|
||||
currentCluster: minikube
|
||||
# KeepMissingClusters will keep clusters in the config if they are missing from the current kubeconfig file. Default false
|
||||
KeepMissingClusters: false
|
||||
# Provide shell pod customization when nodeShell feature gate is enabled!
|
||||
shellPod:
|
||||
# The shell pod image to use.
|
||||
|
|
@ -386,41 +413,13 @@ K9s uses aliases to navigate most K8s resources.
|
|||
memory: 100Mi
|
||||
# Enable TTY
|
||||
tty: true
|
||||
# Persists per cluster preferences for favorite namespaces and view.
|
||||
clusters:
|
||||
coolio:
|
||||
namespace:
|
||||
active: coolio
|
||||
# With this set, the favorites list won't be updated as you switch namespaces
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- cassandra
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
# Toggles NodeShell support. Allow K9s to shell into nodes if needed. Default false.
|
||||
nodeShell: true
|
||||
# The IP Address to use when launching a port-forward.
|
||||
portForwardAddress: 1.2.3.4
|
||||
kind:
|
||||
namespace:
|
||||
active: all
|
||||
favorites:
|
||||
- all
|
||||
- kube-system
|
||||
- default
|
||||
view:
|
||||
active: dp
|
||||
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
|
||||
screenDumpDir: /tmp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## <a id="popeye"></a>Popeye Configuration
|
||||
|
||||
K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes cluster sanitizer. Popeye itself uses a configuration called `spinach.yml`, but when integrating with K9s the cluster-specific file should be name `$XDG_CONFIG_HOME/k9s/<context>_spinach.yml`. This allows you to have a different spinach config per cluster.
|
||||
K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes cluster sanitizer. Popeye itself uses a configuration called `spinach.yml`, but when integrating with K9s the cluster-specific file should be name `$XDG_CONFIG_HOME/share/k9s/clusters/clusterX/contextY/spinach.yml`. This allows you to have a different spinach config per cluster.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -429,7 +428,7 @@ K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes
|
|||
By enabling the nodeShell feature gate on a given cluster, K9s allows you to shell into your cluster nodes. Once enabled, you will have a new `s` for `shell` menu option while in node view. K9s will launch a pod on the selected node using a special k9s_shell pod. Furthermore, you can refine your shell pod by using a custom docker image preloaded with the shell tools you love. By default k9s uses a BusyBox image, but you can configure it as follows:
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yaml
|
||||
k9s:
|
||||
# You can also further tune the shell pod specification
|
||||
shellPod:
|
||||
|
|
@ -438,41 +437,63 @@ k9s:
|
|||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
clusters:
|
||||
# Configures node shell on cluster blee
|
||||
blee:
|
||||
featureGates:
|
||||
# You must enable the nodeShell feature gate to enable shelling into nodes
|
||||
nodeShell: true
|
||||
```
|
||||
|
||||
Then in your cluster configuration file...
|
||||
|
||||
```yaml
|
||||
# $XDG_DATA_HOME/k9s/clusters/cluster-1/context-1
|
||||
k9s:
|
||||
cluster: cluster-1
|
||||
readOnly: false
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- kube-system
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: true # => Enable this feature gate to make nodeShell available on this cluster
|
||||
portForwardAddress: localhost
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Aliases
|
||||
|
||||
In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.config/k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file:
|
||||
In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.config/k9s` define a file called `aliases.yaml`.
|
||||
A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file:
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/alias.yml
|
||||
alias:
|
||||
# $XDG_DATA_HOME/k9s/aliases.yaml
|
||||
aliases:
|
||||
pp: v1/pods
|
||||
crb: rbac.authorization.k8s.io/v1/clusterrolebindings
|
||||
# As of v0.30.0 you can also refer to another command alias...
|
||||
fred: pod fred app=blee # => view pods in namespace fred with labels matching app=blee
|
||||
```
|
||||
|
||||
Using this alias file, you can now type pp/crb to list pods or ClusterRoleBindings respectively.
|
||||
Using this aliases file, you can now type `:pp` or `:crb` or `:fred` to activate their respective commands.
|
||||
|
||||
---
|
||||
|
||||
## HotKey Support
|
||||
|
||||
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps:
|
||||
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources.
|
||||
We're introducing hotkeys that allow users to define their own key combination to activate their favorite resource views.
|
||||
|
||||
1. Create a file named `$XDG_CONFIG_HOME/k9s/hotkey.yml`
|
||||
2. Add the following to your `hotkey.yml`. You can use resource name/short name to specify a command ie same as typing it while in command mode.
|
||||
Additionally, you can define context specific hotkeys by add a context level configuration file in `$XDG_DATA_HOME/k9s/clusters/clusterX/contextY/hotkeys.yaml`
|
||||
|
||||
In order to surface hotkeys globally please follow these steps:
|
||||
|
||||
1. Create a file named `$XDG_CONFIG_HOME/k9s/hotkeys.yaml`
|
||||
2. Add the following to your `hotkeys.yaml`. You can use resource name/short name to specify a command ie same as typing it while in command mode.
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/hotkey.yml
|
||||
hotKey:
|
||||
# $XDG_CONFIG_HOME/k9s/hotkeys.yaml
|
||||
hotKeys:
|
||||
# Hitting Shift-0 navigates to your pod view
|
||||
shift-0:
|
||||
shortCut: Shift-0
|
||||
|
|
@ -490,7 +511,8 @@ Entering the command mode and typing a resource name or alias, could be cumberso
|
|||
command: xray deploy
|
||||
```
|
||||
|
||||
Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them.
|
||||
Not feeling so hot? Your custom hotkeys will be listed in the help view `?`.
|
||||
Also your hotkeys file will be automatically reloaded so you can readily use your hotkeys as you define them.
|
||||
|
||||
You can choose any keyboard shortcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
|
||||
|
||||
|
|
@ -502,9 +524,9 @@ Entering the command mode and typing a resource name or alias, could be cumberso
|
|||
|
||||
As of v0.25.0, you can leverage the `FastForwards` feature to tell K9s how to default port-forwards. In situations where you are dealing with multiple containers or containers exposing multiple ports, it can be cumbersome to specify the desired port-forward from the dialog as in most cases, you already know which container/port tuple you desire. For these use cases, you can now annotate your manifests with the following annotations:
|
||||
|
||||
- `k9scli.io/auto-port-forwards`
|
||||
@ `k9scli.io/auto-port-forwards`
|
||||
activates one or more port-forwards directly bypassing the port-forward dialog all together.
|
||||
- `k9scli.io/port-forwards`
|
||||
@ `k9scli.io/port-forwards`
|
||||
pre-selects one or more port-forwards when launching the port-forward dialog.
|
||||
|
||||
The annotation value takes on the shape `container-name::[local-port:]container-port`
|
||||
|
|
@ -553,14 +575,14 @@ The annotation value must specify a container to forward to as well as a local p
|
|||
|
||||
[SneakCast v0.17.0 on The Beach! - Yup! sound is sucking but what a setting!](https://youtu.be/7S33CNLAofk)
|
||||
|
||||
You can change which columns shows up for a given resource via custom views. To surface this feature, you will need to create a new configuration file, namely `$XDG_CONFIG_HOME/k9s/views.yml`. This file leverages GVR (Group/Version/Resource) to configure the associated table view columns. If no GVR is found for a view the default rendering will take over (ie what we have now). Going wide will add all the remaining columns that are available on the given resource after your custom columns. To boot, you can edit your views config file and tune your resources views live!
|
||||
You can change which columns shows up for a given resource via custom views. To surface this feature, you will need to create a new configuration file, namely `$XDG_CONFIG_HOME/k9s/views.yaml`. This file leverages GVR (Group/Version/Resource) to configure the associated table view columns. If no GVR is found for a view the default rendering will take over (ie what we have now). Going wide will add all the remaining columns that are available on the given resource after your custom columns. To boot, you can edit your views config file and tune your resources views live!
|
||||
|
||||
> NOTE: This is experimental and will most likely change as we iron this out!
|
||||
|
||||
Here is a sample views configuration that customize a pods and services views.
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/views.yml
|
||||
# $XDG_CONFIG_HOME/k9s/views.yaml
|
||||
k9s:
|
||||
views:
|
||||
v1/pods:
|
||||
|
|
@ -585,7 +607,9 @@ k9s:
|
|||
|
||||
## Plugins
|
||||
|
||||
K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$XDG_CONFIG_HOME/k9s/plugin.yml` to locate all available plugins. A plugin is defined as follows:
|
||||
K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$XDG_CONFIG_HOME/k9s/plugins.yaml` to locate all available plugins.
|
||||
|
||||
A plugin is defined as follows:
|
||||
|
||||
* Shortcut option represents the key combination a user would type to activate the plugin
|
||||
* Confirm option (when enabled) lets you see the command that is going to be executed and gives you an option to confirm or prevent execution
|
||||
|
|
@ -614,13 +638,13 @@ K9s does provide additional environment variables for you to customize your plug
|
|||
|
||||
Curly braces can be used to embed an environment variable inside another string, or if the column name contains special characters. (e.g. `${NAME}-example` or `${COL-%CPU/L}`)
|
||||
|
||||
### Example
|
||||
### Plugin Example
|
||||
|
||||
This defines a plugin for viewing logs on a selected pod using `ctrl-l` for shortcut.
|
||||
This defines a plugin for viewing logs on a selected pod using `ctrl-l` as shortcut.
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/plugin.yml
|
||||
plugin:
|
||||
# $XDG_DATA_HOME/k9s/plugins.yaml
|
||||
plugins:
|
||||
# Defines a plugin to provide a `ctrl-l` shortcut to tail the logs while in pod view.
|
||||
fred:
|
||||
shortCut: Ctrl-L
|
||||
|
|
@ -657,12 +681,14 @@ Initially, the benchmarks will run with the following defaults:
|
|||
* HTTP Verb: GET
|
||||
* Path: /
|
||||
|
||||
The PortForward view is backed by a new K9s config file namely: `$XDG_CONFIG_HOME/k9s/bench-<k8s_context>.yml` (note: extension is `yml` and not `yaml`). Each cluster you connect to will have its own bench config file, containing the name of the K8s context for the cluster. Changes to this file should automatically update the PortForward view to indicate how you want to run your benchmarks.
|
||||
The PortForward view is backed by a new K9s config file namely: `$XDG_DATA_HOME/k9s/clusters/clusterX/contextY/benchmarks.yaml`. Each cluster you connect to will have its own bench config file, containing the name of the K8s context for the cluster. Changes to this file should automatically update the PortForward view to indicate how you want to run your benchmarks.
|
||||
|
||||
Here is a sample benchmarks.yml configuration. Please keep in mind this file will likely change in subsequent releases!
|
||||
Benchmarks result reports are stored in `$XDG_STATE_HOME/k9s/clusters/clusterX/contextY`
|
||||
|
||||
Here is a sample benchmarks.yaml configuration. Please keep in mind this file will likely change in subsequent releases!
|
||||
|
||||
```yaml
|
||||
# This file resides in $XDG_CONFIG_HOME/k9s/bench-mycontext.yml
|
||||
# This file resides in $XDG_DATA_HOME/k9s/clusters/clusterX/contextY/benchmarks.yaml
|
||||
benchmarks:
|
||||
# Indicates the default concurrency and number of requests setting if a container or service rule does not match.
|
||||
defaults:
|
||||
|
|
@ -810,36 +836,90 @@ Example: Dracula Skin ;)
|
|||
|
||||
<img src="assets/skins/dracula.png" alt="Dracula Skin">
|
||||
|
||||
You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s default skin is loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin will be loaded if not the current stock skin remains in effect.
|
||||
You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. See this repo `skins` directory for examples.
|
||||
You can skin k9s by default by specifying a UI.skin attribute. You can also change K9s skins based on the context you are connecting too.
|
||||
In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin file without the extension!) and copy this repo
|
||||
`skins/dracula.yaml` to `$XDG_CONFIG_HOME/k9s/skins/` directory.
|
||||
|
||||
You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin!) and copy this repo skins/dracula.yml to `$XDG_CONFIG_HOME/k9s/skins` directory.
|
||||
Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your k9s home dir as `skin.yml`.
|
||||
In the case where your cluster spans several contexts, you can add a skin context configuration to your context configuration.
|
||||
This is a collection of {context_name, skin} tuples (please see example below!)
|
||||
|
||||
Colors can be defined by name or using a hex representation. Of recent, we've added a color named `default` to indicate a transparent background color to preserve your terminal background color settings if so desired.
|
||||
|
||||
> NOTE: This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly!
|
||||
> NOTE: Please see [K9s Skins](https://k9scli.io/topics/skins/) for a list of available colors.
|
||||
|
||||
To skin a specific context and provided the file `in_the_navy.yaml` is present in your skins directory.
|
||||
|
||||
```yaml
|
||||
# Make cluster fred display in_the_navy skin when loaded...
|
||||
# $XDG_DATA_HOME/k9s/clusters/clusterX/contextY/config.yaml
|
||||
k9s:
|
||||
...
|
||||
clusters:
|
||||
fred:
|
||||
# Override the default skin and use this skin for this cluster.
|
||||
# NOTE: Just the skin file name to extension!
|
||||
skin: in_the_navy # -> Look for a skin file in ~/.config/k9s/skins/in_the_navy.yml
|
||||
namespace:
|
||||
...
|
||||
view:
|
||||
active: pod
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
cluster: clusterX
|
||||
skin: in_the_navy
|
||||
readOnly: false
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- kube-system
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
```
|
||||
|
||||
You can also specify a default skin for all contexts in the root k9s config file as so:
|
||||
|
||||
```yaml
|
||||
k9s:
|
||||
liveViewAutoRefresh: false
|
||||
screenDumpDir: /tmp/dumps
|
||||
refreshRate: 2
|
||||
maxConnRetry: 5
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
ui:
|
||||
enableMouse: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
noIcons: false
|
||||
# By default all contexts wil use the dracula skin unless explicitly overridden in the context config file.
|
||||
skin: dracula # => assumes the file skins/dracular.yaml is present in the $XDG_DATA_HOME/k9s/skins directory
|
||||
skipLatestRevCheck: false
|
||||
disablePodCounting: false
|
||||
shellPod:
|
||||
image: busybox
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
imageScans:
|
||||
enable: false
|
||||
blackList:
|
||||
namespaces: []
|
||||
labels: {}
|
||||
logger:
|
||||
tail: 100
|
||||
buffer: 5000
|
||||
sinceSeconds: -1
|
||||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
thresholds:
|
||||
cpu:
|
||||
critical: 90
|
||||
warn: 70
|
||||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
```
|
||||
|
||||
```yaml
|
||||
# in_the_navy.yml: Skin InTheNavy...
|
||||
# $XDG_DATA_HOME/k9s/skins/in_the_navy.yaml
|
||||
# Skin InTheNavy!
|
||||
k9s:
|
||||
# General K9s styles
|
||||
body:
|
||||
|
|
@ -935,7 +1015,7 @@ that you want, please file an issue and if so inclined submit a PR!
|
|||
|
||||
K9s will most likely blow up if...
|
||||
|
||||
1. You're running older versions of Kubernetes. K9s works best on Kubernetes latest.
|
||||
1. You're running older versions of Kubernetes. K9s works best on later Kubernetes versions.
|
||||
2. You don't have enough RBAC fu to manage your cluster.
|
||||
|
||||
---
|
||||
|
|
@ -966,4 +1046,4 @@ We always enjoy hearing from folks who benefit from our work!
|
|||
|
||||
---
|
||||
|
||||
<img src="assets/imhotep_logo.png" width="32" height="auto" alt="Imhotep"/> © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
<img src="assets/imhotep_logo.png" width="32" height="auto" alt="Imhotep"/> © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,313 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s-xmas.png" align="center" width="800" height="auto"/>
|
||||
|
||||
# Release v0.30.0
|
||||
|
||||
## 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 ♭
|
||||
|
||||
Going back to the classics...
|
||||
|
||||
* [Home For Christmas - Fats Domino](https://www.youtube.com/watch?v=ykAVdPz8o1Q)
|
||||
* [Our Love - Al Jarreau](https://www.youtube.com/watch?v=9ztMe6GIwi8)
|
||||
* [Body And Soul - Louis Armstrong](https://www.youtube.com/watch?v=2Gnz69TbqHQ)
|
||||
* [On The Dunes - Donald Fagen](https://www.youtube.com/watch?v=QoVT3XcMVvk)
|
||||
* [Ciao - Lucio Dalla](https://www.youtube.com/watch?v=qcqXcmKu_I4)
|
||||
* [Basin Street Blues - Louis Prima](https://www.youtube.com/watch?v=IijXXXpUefM&list=RDIijXXXpUefM&start_radio=1)
|
||||
|
||||
---
|
||||
|
||||
## A Word From Our Sponsors...
|
||||
|
||||
To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!!
|
||||
|
||||
* [Bojan](https://github.com/rbojan)
|
||||
|
||||
> Sponsorship cancellations since the last release: **5!** 🥹
|
||||
|
||||
---
|
||||
|
||||
## 🎄 Feature Release! 🎄
|
||||
|
||||
🎅 Merry Christmas to all and Best wishes for the new year!!🧑🎄
|
||||
|
||||
---
|
||||
|
||||
### Videos Are In The Can!
|
||||
|
||||
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
|
||||
|
||||
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
|
||||
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
|
||||
|
||||
---
|
||||
|
||||
### Breaking Bad!
|
||||
|
||||
> ☢️ !!Prior to installing v0.30.0!! Please be sure to backup your k9s configs directories or move them somewhere safe!!
|
||||
|
||||
> ☢️ Please watch the v0.30.0 Sneak peek series (links below) for detailed information.
|
||||
>
|
||||
> ☢️ Most K9s configuration files have either split or changed location or names on this drop!!
|
||||
|
||||
> We recommend moving your current k9s config dirs to another location and start k9s from scratch and let it create and initialize the various configs
|
||||
> to their new spec and location. You can then use your existing setup and patch with the new layout/spec.
|
||||
> As of v0.30.0 all config files now use the `*.yaml` extension. We did our best to update all the docs to match the new version.
|
||||
> If you find doc issues either file an issue or better yet submit a PR!
|
||||
|
||||
Some of you might say: `You're on the roll their bud! Two breaking changes drops in a row!!`
|
||||
Per the wise words of my beloved Grand mama! `One can't cook a decent meal without creating a mess!`
|
||||
Not to mention we're still at v0.x.y so `Open season on breaking changes` is very much in full effect.
|
||||
|
||||
Tho I have tested this drop quite a bit, there is a strong chance that I've broken some stuff.
|
||||
The key here is to walk the fine line of improving k9s code base and features set with minimal impact to you.
|
||||
As you know by now, I am committed to ease the pain and resolve issues quickly to get you all back up and running.
|
||||
|
||||
From the scope changes in this release, I would caution that this drop will likely break you!
|
||||
If so, worry not! We will fix the duds so we are `Happy as a Hippo` once again.
|
||||
|
||||
There was a few issues with the way K9s persists it's configuration and various artifacts. So we rewrote it!
|
||||
First and foremost all k9s related YAML resources, will now use the standard ".yaml" extension.
|
||||
I think we've bloated the code checking for both extensions with no real actionable value!
|
||||
|
||||
As it stands the main K9s configuration `config.yml` will now be static. These settings are now readonly! All the dynamic configurations that K9s manages now live in a new directory aka `clusters`. The clusters directory manages your k8s cluster/context configurations. So things like active view, namespace, favorites, etc... now live in this directory. K9s configurations are still managed using either xdg `XDG_CONFIG_HOME` or you can set `K9S_CONFIG_DIR` to specify a your preferred k9s configs location. Also all config files will now use the ".yaml" extension vs ".yml"!!
|
||||
|
||||
So the main k9s configuration (static) now looks like this:
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yaml
|
||||
# File will be autogenerated will all the default fixins if not found in the config specification.
|
||||
k9s:
|
||||
liveViewAutoRefresh: false
|
||||
refreshRate: 2
|
||||
maxConnRetry: 5
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
ui: # NOTE! New level!!
|
||||
enableMouse: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
noIcons: false
|
||||
skipLatestRevCheck: false
|
||||
disablePodCounting: false
|
||||
# ShellPod configuration applies to all your clusters
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
# ImageScan config changed from v0.29.0!
|
||||
imageScans:
|
||||
enable: false
|
||||
# Now figures exclusions ie blacklist namespaces or specific workload labels
|
||||
blackList:
|
||||
# Exclude the following namespaces for image vulscans!
|
||||
namespaces:
|
||||
- kube-system
|
||||
- fred
|
||||
# Exclude the following labels from image vulscans!
|
||||
labels:
|
||||
k8s-app:
|
||||
- kindnet
|
||||
- bozo
|
||||
env:
|
||||
- dev
|
||||
logger:
|
||||
tail: 100
|
||||
buffer: 5000
|
||||
sinceSeconds: -1
|
||||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
thresholds:
|
||||
cpu:
|
||||
critical: 90
|
||||
warn: 70
|
||||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
```
|
||||
|
||||
Next context specific configurations that are managed by you and k9s live in the XDG data directory
|
||||
i.e `$XDG_DATA_HOME/k9s/clusters` or `$K9S_CONFIG_DIR/clusters` if the env var is set.
|
||||
|
||||
```text
|
||||
$XDG_DATA_HOME/k9s
|
||||
// Clusters tracks visited kubeconfig cluster/contexts
|
||||
├── clusters
|
||||
│ ├── fred
|
||||
│ │ └── bozo
|
||||
│ │ └── config.yaml
|
||||
│ ├── bozorg
|
||||
│ │ ├── kind-bozo-1
|
||||
│ │ │ └── config.yaml
|
||||
│ │ ├── kind-bozo-2
|
||||
│ │ │ └── config.yaml
|
||||
│ │ └── kind-bozo-3
|
||||
│ │ └── config.yaml
|
||||
│ └── bumblebeetuna
|
||||
│ └── blee
|
||||
│ └── config.yaml
|
||||
└── skins
|
||||
├── black_and_wtf.yaml
|
||||
├── dracula.yaml
|
||||
├── in_the_navy.yml
|
||||
├── ...
|
||||
```
|
||||
|
||||
Now looking at a given context configuration i.e cluster-1/context-1/config.yaml
|
||||
|
||||
```yaml
|
||||
# $XDG_DATA_HOME/k9s/clusters/bumblebeetuna/blee/config.yaml
|
||||
k9s:
|
||||
cluster: bumblebeetuna
|
||||
readOnly: false # [New!] you can now single out a given context and make it readonly. Woof!
|
||||
skin: in_the_navy # [NEW!] you can also skin individual contexts. Woof Woof!
|
||||
namespace:
|
||||
active: all
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- all
|
||||
- kube-system
|
||||
- default
|
||||
view:
|
||||
active: dp
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
```
|
||||
|
||||
Transient artifacts ie k9s logs, screen-dumps, benchmarks etc now live in the state config dir.
|
||||
|
||||
```text
|
||||
$XDG_STATE_HOME/k9s
|
||||
├── k9s.log # K9s log files
|
||||
└── screen-dumps
|
||||
└── bumblebeetuna # Screen dumps location for context blee
|
||||
└── blee
|
||||
└── deployments-kube-system-1703018199222861000.csv
|
||||
```
|
||||
|
||||
If you get stuck or if my instructions are just `clear as mud`... `k9s info` is always your friend!!
|
||||
|
||||
I feel this is an improvement (tho I might be unanimous on this!) especially for folks dealing with multi-clusters or swapping out there kubeconfigs...
|
||||
|
||||
> NOTE! Paint is still fresh on this deal. Proceed with caution and please help us flush this feature out!
|
||||
|
||||
---
|
||||
|
||||
# Got Prompt?
|
||||
|
||||
In this drop, we've also gave the k9s command prompt aka `:xxx` some love.
|
||||
You have the ability to specify filter directly in the prompt.
|
||||
|
||||
So for example, you can now run something like `:po /fred` to run pod view with a filter to just show pods containing `fred`. Likewise `:po k8s-app=fred,env=blee` to filter by labels.
|
||||
And now for the`Krampus` special... you can see pods in a different context all together via `:pod @ctx-2`.
|
||||
Finally you can combo and send the `whole enchilada` via `:po k8s-app=fred /blee ns-1 @ctx-x`
|
||||
Did I mention with completion where applicable? Yes Please!!
|
||||
Compliments of [Jayson Wang](https://github.com/wjiec). Be sure to thank him!!
|
||||
|
||||
Put these frequent flyers command in an alias and now you can nav your clusters with `even more style`!
|
||||
|
||||
---
|
||||
|
||||
# All Is Love?
|
||||
|
||||
🎵 `On The twentieth day of Christmas my true love gave to me... Ten worklords a-leaping??...` 🎵
|
||||
|
||||
This is a feature reported by many of you and its (finally!) here. As of this drop, we intro the `workload` view aka `wk` which is similar to `kubetcl get all`. I was reluctant to intro it given the potential hazards on larger clusters but figured why not? YOLO. I think using it in combo with the prompt updates it could pack a serious punch to observe workload related artifacts.
|
||||
|
||||
---
|
||||
|
||||
# The Black List...
|
||||
|
||||
As it seems customary with all k9s new features, folks want to turn them off ;(
|
||||
The `Vulscan` feature did not get out unscaped ;(
|
||||
As it was rightfully so pointed out, you may want to opted out scans for images that you do not control.
|
||||
Tho I think it might be a good idea to run wide open once in a while to see if your cluster has any holes??
|
||||
For this reason, we've opted to intro a blacklist section under the image scan configuration to exclude certain images from the scans.
|
||||
|
||||
Here is a sample configuration:
|
||||
|
||||
```yaml
|
||||
k9s:
|
||||
liveViewAutoRefresh: false
|
||||
refreshRate: 2
|
||||
ui:
|
||||
enableMouse: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
noIcons: false
|
||||
imageScans:
|
||||
enable: true
|
||||
blackList:
|
||||
# Skip scans on these namespaces
|
||||
namespaces:
|
||||
- ns-1
|
||||
- ns-2
|
||||
# Skip scans for pods matching these labels
|
||||
labels:
|
||||
- app:
|
||||
- fred
|
||||
- blee
|
||||
- duh
|
||||
- env:
|
||||
- dev
|
||||
```
|
||||
|
||||
This is a bit of a blur now, but I think that it! We hope you guys will dig this drop or at least the concepts as likely this is going to be `Open Season` on bugs ;(
|
||||
|
||||
🎵 `On The second day of Christmas my true love gave to me... Eleven buggers bugging??...` 🎵
|
||||
|
||||
Lastly looks like the sponsorship stream is down to an alarming trickle so if you dig this project and find it useful be sure `to give til it hurts!`
|
||||
|
||||
---
|
||||
|
||||
🎅 Best wishes to you and yours for good health and happiness this holiday season!! 🎉
|
||||
|
||||
AndJoy!
|
||||
Fernand
|
||||
|
||||
---
|
||||
|
||||
## Resolved Issues
|
||||
|
||||
* [#2346](https://github.com/derailed/k9s/issues/2346) k9s should not write state to config.yaml
|
||||
* [#2335](https://github.com/derailed/k9s/issues/2335) Restore 0.28 column order on pod view bug
|
||||
* [#2331](https://github.com/derailed/k9s/issues/2331) Set a shortcut key to run Vuln Scanning on a resource. Don't scan every resource at every startup.
|
||||
* [#2283](https://github.com/derailed/k9s/issues/2283) Adding auto complete in search bar
|
||||
|
||||
---
|
||||
|
||||
## 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!!
|
||||
|
||||
* [#2357](https://github.com/derailed/k9s/pull/2357) Added ln check for snap
|
||||
* [#2350](https://github.com/derailed/k9s/pull/2350) Add symlink into snap
|
||||
* [#2348](https://github.com/derailed/k9s/pull/2348) Fix(misc plugins): split up multiline commands, use less -K everywhere
|
||||
* [#2343](https://github.com/derailed/k9s/pull/2343) Passing on the correct suggestion parameters
|
||||
* [#2341](https://github.com/derailed/k9s/pull/2340) Adding value, yaml and describe views to helm-history
|
||||
* [#2340](https://github.com/derailed/k9s/pull/2340) Add pkgx to installation section
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
44
cmd/info.go
44
cmd/info.go
|
|
@ -5,7 +5,6 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/derailed/k9s/internal/color"
|
||||
|
|
@ -19,21 +18,31 @@ import (
|
|||
func infoCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Print configuration info",
|
||||
Long: "Print configuration information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
printInfo()
|
||||
},
|
||||
Short: "List K9s configurations info",
|
||||
RunE: printInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func printInfo() {
|
||||
const fmat = "%-25s %s\n"
|
||||
func printInfo(cmd *cobra.Command, args []string) error {
|
||||
if err := config.InitLocs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const fmat = "%-27s %s\n"
|
||||
printLogo(color.Cyan)
|
||||
printTuple(fmat, "Configuration", config.K9sConfigFile, color.Cyan)
|
||||
printTuple(fmat, "Logs", config.DefaultLogFile, color.Cyan)
|
||||
printTuple(fmat, "Screen Dumps", getScreenDumpDirForInfo(), color.Cyan)
|
||||
printTuple(fmat, "Version", version, color.Cyan)
|
||||
printTuple(fmat, "Config", config.AppConfigFile, color.Cyan)
|
||||
printTuple(fmat, "Custom Views", config.AppViewsFile, color.Cyan)
|
||||
printTuple(fmat, "Plugins", config.AppPluginsFile, color.Cyan)
|
||||
printTuple(fmat, "Hotkeys", config.AppHotKeysFile, color.Cyan)
|
||||
printTuple(fmat, "Aliases", config.AppAliasesFile, color.Cyan)
|
||||
printTuple(fmat, "Skins", config.AppSkinsDir, color.Cyan)
|
||||
printTuple(fmat, "Context Configs", config.AppContextsDir, color.Cyan)
|
||||
printTuple(fmat, "Logs", config.AppLogFile, color.Cyan)
|
||||
printTuple(fmat, "Benchmarks", config.AppBenchmarksDir, color.Cyan)
|
||||
printTuple(fmat, "ScreenDumps", getScreenDumpDirForInfo(), color.Cyan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printLogo(c color.Paint) {
|
||||
|
|
@ -45,23 +54,20 @@ func printLogo(c color.Paint) {
|
|||
|
||||
// getScreenDumpDirForInfo get default screen dump config dir or from config.K9sConfigFile configuration.
|
||||
func getScreenDumpDirForInfo() string {
|
||||
if config.K9sConfigFile == "" {
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
if config.AppConfigFile == "" {
|
||||
return config.AppDumpsDir
|
||||
}
|
||||
|
||||
f, err := os.ReadFile(config.K9sConfigFile)
|
||||
f, err := os.ReadFile(config.AppConfigFile)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Reads k9s config file %v", err)
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
return config.AppDumpsDir
|
||||
}
|
||||
|
||||
var cfg config.Config
|
||||
if err := yaml.Unmarshal(f, &cfg); err != nil {
|
||||
log.Error().Err(err).Msgf("Unmarshal k9s config %v", err)
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
}
|
||||
if cfg.K9s == nil {
|
||||
cfg.K9s = config.NewK9s()
|
||||
return config.AppDumpsDir
|
||||
}
|
||||
|
||||
return cfg.K9s.GetScreenDumpDir()
|
||||
|
|
|
|||
|
|
@ -16,32 +16,31 @@ func Test_getScreenDumpDirForInfo(t *testing.T) {
|
|||
expectedScreenDumpDir string
|
||||
}{
|
||||
"withK9sConfigFile": {
|
||||
k9sConfigFile: "testdata/k9s.yml",
|
||||
k9sConfigFile: "testdata/k9s.yaml",
|
||||
expectedScreenDumpDir: "/tmp",
|
||||
},
|
||||
"withEmptyK9sConfigFile": {
|
||||
k9sConfigFile: "",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
expectedScreenDumpDir: config.AppDumpsDir,
|
||||
},
|
||||
"withInvalidK9sConfigFilePath": {
|
||||
k9sConfigFile: "invalid",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
expectedScreenDumpDir: config.AppDumpsDir,
|
||||
},
|
||||
"withScreenDumpDirEmptyInK9sConfigFile": {
|
||||
k9sConfigFile: "testdata/k9s1.yml",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
k9sConfigFile: "testdata/k9s1.yaml",
|
||||
expectedScreenDumpDir: config.AppDumpsDir,
|
||||
},
|
||||
}
|
||||
for k := range tests {
|
||||
u := tests[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
initK9sConfigFile := config.K9sConfigFile
|
||||
|
||||
config.K9sConfigFile = u.k9sConfigFile
|
||||
initK9sConfigFile := config.AppConfigFile
|
||||
config.AppConfigFile = u.k9sConfigFile
|
||||
|
||||
assert.Equal(t, u.expectedScreenDumpDir, getScreenDumpDirForInfo())
|
||||
|
||||
config.K9sConfigFile = initK9sConfigFile
|
||||
config.AppConfigFile = initK9sConfigFile
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
cmd/root.go
49
cmd/root.go
|
|
@ -8,6 +8,8 @@ import (
|
|||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/color"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
|
|
@ -20,12 +22,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
appName = "k9s"
|
||||
appName = config.AppName
|
||||
shortAppDesc = "A graphical CLI for your Kubernetes cluster management."
|
||||
longAppDesc = "K9s is a CLI to view and manage your Kubernetes clusters."
|
||||
)
|
||||
|
||||
var _ config.KubeSettings = (*client.Config)(nil)
|
||||
var _ data.KubeSettings = (*client.Config)(nil)
|
||||
|
||||
var (
|
||||
version, commit, date = "dev", "dev", client.NA
|
||||
|
|
@ -43,6 +45,10 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
if err := config.InitLogLoc(); err != nil {
|
||||
fmt.Printf("Fail to init k9s logs location %s\n", err)
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(versionCmd(), infoCmd())
|
||||
initK9sFlags()
|
||||
initK8sFlags()
|
||||
|
|
@ -51,18 +57,21 @@ func init() {
|
|||
// Execute root command.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Panic().Err(err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
if err := config.EnsureDirPath(*k9sFlags.LogFile, config.DefaultDirMod); err != nil {
|
||||
if err := config.InitLocs(); err != nil {
|
||||
return err
|
||||
}
|
||||
mod := os.O_CREATE | os.O_APPEND | os.O_WRONLY
|
||||
file, err := os.OpenFile(*k9sFlags.LogFile, mod, config.DefaultFileMod)
|
||||
file, err := os.OpenFile(
|
||||
*k9sFlags.LogFile,
|
||||
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
|
||||
data.DefaultFileMod,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Log file %q init failed: %w", *k9sFlags.LogFile, err)
|
||||
}
|
||||
defer func() {
|
||||
if file != nil {
|
||||
|
|
@ -80,8 +89,8 @@ func run(cmd *cobra.Command, args []string) error {
|
|||
}()
|
||||
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
|
||||
|
||||
zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel))
|
||||
|
||||
app := view.NewApp(loadConfiguration())
|
||||
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil {
|
||||
return err
|
||||
|
|
@ -99,38 +108,24 @@ func run(cmd *cobra.Command, args []string) error {
|
|||
func loadConfiguration() *config.Config {
|
||||
log.Info().Msg("🐶 K9s starting up...")
|
||||
|
||||
// Load K9s config file...
|
||||
k8sCfg := client.NewConfig(k8sFlags)
|
||||
k9sCfg := config.NewConfig(k8sCfg)
|
||||
|
||||
if err := k9sCfg.Load(config.K9sConfigFile); err != nil {
|
||||
if err := k9sCfg.Load(config.AppConfigFile); err != nil {
|
||||
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
||||
k9sCfg.K9s.Generate(k9sFlags)
|
||||
}
|
||||
|
||||
if *k9sFlags.RefreshRate != config.DefaultRefreshRate {
|
||||
k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
||||
}
|
||||
|
||||
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
||||
k9sCfg.K9s.OverrideLogoless(*k9sFlags.Logoless)
|
||||
k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
||||
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||
k9sCfg.K9s.OverrideWrite(*k9sFlags.Write)
|
||||
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
||||
k9sCfg.K9s.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir)
|
||||
|
||||
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil {
|
||||
log.Error().Err(err).Msgf("refine failed")
|
||||
}
|
||||
conn, err := client.InitConnection(k8sCfg)
|
||||
k9sCfg.SetConnection(conn)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("failed to connect to cluster %q", k9sCfg.K9s.CurrentContext)
|
||||
log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName())
|
||||
return k9sCfg
|
||||
}
|
||||
// Try to access server version if that fail. Connectivity issue?
|
||||
if !k9sCfg.GetConnection().CheckConnectivity() {
|
||||
log.Panic().Msgf("Cannot connect to cluster %s", k9sCfg.K9s.CurrentCluster)
|
||||
log.Panic().Msgf("Cannot connect to context %s", k9sCfg.K9s.ActiveContextName())
|
||||
}
|
||||
if !k9sCfg.GetConnection().ConnectionOK() {
|
||||
panic("No connectivity")
|
||||
|
|
@ -177,7 +172,7 @@ func initK9sFlags() {
|
|||
rootCmd.Flags().StringVarP(
|
||||
k9sFlags.LogFile,
|
||||
"logFile", "",
|
||||
config.DefaultLogFile,
|
||||
config.AppLogFile,
|
||||
"Specify the log file",
|
||||
)
|
||||
rootCmd.Flags().BoolVar(
|
||||
|
|
|
|||
34
go.mod
34
go.mod
|
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/anchore/grype v0.73.4
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/derailed/popeye v0.11.1
|
||||
github.com/derailed/popeye v0.11.2
|
||||
github.com/derailed/tcell/v2 v2.3.1-rc.3
|
||||
github.com/derailed/tview v0.8.2
|
||||
github.com/fatih/color v1.16.0
|
||||
|
|
@ -27,15 +27,15 @@ require (
|
|||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/text v0.14.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
helm.sh/helm/v3 v3.13.3
|
||||
k8s.io/api v0.28.4
|
||||
k8s.io/apiextensions-apiserver v0.28.4
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/cli-runtime v0.28.4
|
||||
k8s.io/client-go v0.28.4
|
||||
helm.sh/helm/v3 v3.13.2
|
||||
k8s.io/api v0.29.0
|
||||
k8s.io/apiextensions-apiserver v0.29.0
|
||||
k8s.io/apimachinery v0.29.0
|
||||
k8s.io/cli-runtime v0.29.0
|
||||
k8s.io/client-go v0.29.0
|
||||
k8s.io/klog/v2 v2.110.1
|
||||
k8s.io/kubectl v0.28.4
|
||||
k8s.io/metrics v0.28.4
|
||||
k8s.io/kubectl v0.29.0
|
||||
k8s.io/metrics v0.29.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ require (
|
|||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
|
|
@ -153,6 +153,7 @@ require (
|
|||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
|
||||
|
|
@ -216,9 +217,10 @@ require (
|
|||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/gomega v1.27.10 // indirect
|
||||
github.com/onsi/gomega v1.29.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
|
|
@ -310,10 +312,10 @@ require (
|
|||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/gorm v1.25.5 // indirect
|
||||
k8s.io/apiserver v0.28.4 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
k8s.io/apiserver v0.29.0 // indirect
|
||||
k8s.io/component-base v0.29.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
modernc.org/libc v1.29.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
|
|
@ -322,5 +324,5 @@ require (
|
|||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
|
|
|||
76
go.sum
76
go.sum
|
|
@ -384,8 +384,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
|
||||
github.com/derailed/popeye v0.11.1 h1:bjt5mXkcXY696ipuJqwY1sa5s3i431L9BlkQc6EuaqE=
|
||||
github.com/derailed/popeye v0.11.1/go.mod h1:NkvjHH1F94tE7Ui17PlYiagQcFt7yXUV2hIhPzSK+0w=
|
||||
github.com/derailed/popeye v0.11.2 h1:8MKMjYBJdYNktTKeh98TeT127jZY6CFAsurrENoTZCY=
|
||||
github.com/derailed/popeye v0.11.2/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY=
|
||||
github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY=
|
||||
github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY=
|
||||
github.com/derailed/tview v0.8.2 h1:8b+QwVECV1lZ6VV7Vf1tergpJxJ+ReA/JhIBYyUVSFI=
|
||||
|
|
@ -423,8 +423,8 @@ github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
|||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
|
@ -670,6 +670,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
|
|||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
|
||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
|
|
@ -918,6 +920,8 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
|
|
@ -926,10 +930,10 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
|||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
|
|
@ -1204,8 +1208,8 @@ go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93V
|
|||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
|
|
@ -1834,8 +1838,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
|||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY=
|
||||
helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg=
|
||||
helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w=
|
||||
helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
@ -1843,30 +1847,30 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
|
||||
k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
|
||||
k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
|
||||
k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
|
||||
k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
|
||||
k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
|
||||
k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg=
|
||||
k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w=
|
||||
k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q=
|
||||
k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k=
|
||||
k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
|
||||
k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
|
||||
k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
|
||||
k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
|
||||
k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=
|
||||
k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=
|
||||
k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0=
|
||||
k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc=
|
||||
k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o=
|
||||
k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis=
|
||||
k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o=
|
||||
k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM=
|
||||
k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4=
|
||||
k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk=
|
||||
k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=
|
||||
k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=
|
||||
k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s=
|
||||
k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M=
|
||||
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
|
||||
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
|
||||
k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ=
|
||||
k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c=
|
||||
k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk=
|
||||
k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI=
|
||||
k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs=
|
||||
k8s.io/metrics v0.29.0 h1:a6dWcNM+EEowMzMZ8trka6wZtSRIfEA/9oLjuhBksGc=
|
||||
k8s.io/metrics v0.29.0/go.mod h1:UCuTT4dC/x/x6ODSk87IWIZQnuAfcwxOjb1gjWJdjMA=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
|
||||
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
|
|
@ -1886,8 +1890,8 @@ sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKU
|
|||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import (
|
|||
|
||||
"github.com/rs/zerolog/log"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
|
|
@ -35,8 +34,8 @@ const (
|
|||
|
||||
var supportedMetricsAPIVersions = []string{"v1beta1"}
|
||||
|
||||
// Namespaces tracks a collection of namespace names.
|
||||
type Namespaces map[string]struct{}
|
||||
// NamespaceNames tracks a collection of namespace names.
|
||||
type NamespaceNames map[string]struct{}
|
||||
|
||||
// APIClient represents a Kubernetes api client.
|
||||
type APIClient struct {
|
||||
|
|
@ -86,7 +85,7 @@ func (a *APIClient) ConnectionOK() bool {
|
|||
|
||||
func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
|
||||
if ns == ClusterScope {
|
||||
ns = AllNamespaces
|
||||
ns = BlankNamespace
|
||||
}
|
||||
spec := NewGVR(gvr)
|
||||
res := spec.GVR()
|
||||
|
|
@ -107,9 +106,9 @@ func makeCacheKey(ns, gvr string, vv []string) string {
|
|||
return ns + ":" + gvr + "::" + strings.Join(vv, ",")
|
||||
}
|
||||
|
||||
// ActiveCluster returns the current cluster name.
|
||||
func (a *APIClient) ActiveCluster() string {
|
||||
c, err := a.config.CurrentClusterName()
|
||||
// ActiveContext returns the current context name.
|
||||
func (a *APIClient) ActiveContext() string {
|
||||
c, err := a.config.CurrentContextName()
|
||||
if err != nil {
|
||||
log.Error().Msgf("Unable to located active cluster")
|
||||
return ""
|
||||
|
|
@ -119,9 +118,10 @@ func (a *APIClient) ActiveCluster() string {
|
|||
|
||||
// IsActiveNamespace returns true if namespaces matches.
|
||||
func (a *APIClient) IsActiveNamespace(ns string) bool {
|
||||
if a.ActiveNamespace() == AllNamespaces {
|
||||
if a.ActiveNamespace() == BlankNamespace {
|
||||
return true
|
||||
}
|
||||
|
||||
return a.ActiveNamespace() == ns
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ func (a *APIClient) ActiveNamespace() string {
|
|||
return ns
|
||||
}
|
||||
|
||||
return AllNamespaces
|
||||
return BlankNamespace
|
||||
}
|
||||
|
||||
func (a *APIClient) clearCache() {
|
||||
|
|
@ -149,7 +149,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
|
|||
return false, errors.New("ACCESS -- No API server connection")
|
||||
}
|
||||
if IsClusterWide(ns) {
|
||||
ns = AllNamespaces
|
||||
ns = BlankNamespace
|
||||
}
|
||||
key := makeCacheKey(ns, gvr, verbs)
|
||||
if v, ok := a.cache.Get(key); ok {
|
||||
|
|
@ -212,14 +212,27 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
|
|||
return info, nil
|
||||
}
|
||||
|
||||
// ValidNamespaces returns all available namespaces.
|
||||
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
||||
func (a *APIClient) IsValidNamespace(ns string) bool {
|
||||
if IsAllNamespace(ns) {
|
||||
return true
|
||||
}
|
||||
nn, err := a.ValidNamespaceNames()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, ok := nn[ns]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidNamespaceNames returns all available namespaces.
|
||||
func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) {
|
||||
if a == nil {
|
||||
return nil, fmt.Errorf("validNamespaces: no available client found")
|
||||
}
|
||||
|
||||
if nn, ok := a.cache.Get("validNamespaces"); ok {
|
||||
if nss, ok := nn.([]v1.Namespace); ok {
|
||||
if nss, ok := nn.(NamespaceNames); ok {
|
||||
return nss, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -233,9 +246,13 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.cache.Add("validNamespaces", nn.Items, cacheExpiry)
|
||||
nns := make(NamespaceNames, len(nn.Items))
|
||||
for _, n := range nn.Items {
|
||||
nns[n.Name] = struct{}{}
|
||||
}
|
||||
a.cache.Add("validNamespaces", nns, cacheExpiry)
|
||||
|
||||
return nn.Items, nil
|
||||
return nns, nil
|
||||
}
|
||||
|
||||
// CheckConnectivity return true if api server is cool or false otherwise.
|
||||
|
|
|
|||
|
|
@ -10,15 +10,14 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientcmd "k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCallTimeoutDuration time.Duration = 10 * time.Second
|
||||
defaultCallTimeoutDuration time.Duration = 15 * time.Second
|
||||
|
||||
// UsePersistentConfig caches client config to avoid reloads.
|
||||
UsePersistentConfig = true
|
||||
|
|
@ -60,7 +59,7 @@ func (c *Config) Flags() *genericclioptions.ConfigFlags {
|
|||
return c.flags
|
||||
}
|
||||
|
||||
func (c *Config) RawConfig() (clientcmdapi.Config, error) {
|
||||
func (c *Config) RawConfig() (api.Config, error) {
|
||||
return c.clientConfig().RawConfig()
|
||||
}
|
||||
|
||||
|
|
@ -72,11 +71,14 @@ func (c *Config) reset() {}
|
|||
|
||||
// SwitchContext changes the kubeconfig context to a new cluster.
|
||||
func (c *Config) SwitchContext(name string) error {
|
||||
if _, err := c.GetContext(name); err != nil {
|
||||
ct, err := c.GetContext(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("context %q does not exist", name)
|
||||
}
|
||||
// !!BOZO!! Do you need to reset the flags?
|
||||
flags := genericclioptions.NewConfigFlags(UsePersistentConfig)
|
||||
flags.Context = &name
|
||||
flags.Context, flags.ClusterName = &name, &ct.Cluster
|
||||
flags.Namespace = c.flags.Namespace
|
||||
flags.Timeout = c.flags.Timeout
|
||||
flags.KubeConfig = c.flags.KubeConfig
|
||||
c.flags = flags
|
||||
|
|
@ -84,6 +86,22 @@ func (c *Config) SwitchContext(name string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CurrentClusterName returns the currently active cluster name.
|
||||
func (c *Config) CurrentClusterName() (string, error) {
|
||||
if isSet(c.flags.ClusterName) {
|
||||
return *c.flags.ClusterName, nil
|
||||
}
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ct := cfg.Contexts[cfg.CurrentContext]
|
||||
|
||||
return ct.Cluster, nil
|
||||
|
||||
}
|
||||
|
||||
// CurrentContextName returns the currently active config context.
|
||||
func (c *Config) CurrentContextName() (string, error) {
|
||||
if isSet(c.flags.Context) {
|
||||
|
|
@ -110,8 +128,17 @@ func (c *Config) CurrentContextNamespace() (string, error) {
|
|||
return context.Namespace, nil
|
||||
}
|
||||
|
||||
// CurrentContext returns the current context configuration.
|
||||
func (c *Config) CurrentContext() (*api.Context, error) {
|
||||
n, err := c.CurrentContextName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.GetContext(n)
|
||||
}
|
||||
|
||||
// GetContext fetch a given context or error if it does not exists.
|
||||
func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) {
|
||||
func (c *Config) GetContext(n string) (*api.Context, error) {
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -124,7 +151,7 @@ func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) {
|
|||
}
|
||||
|
||||
// Contexts fetch all available contexts.
|
||||
func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) {
|
||||
func (c *Config) Contexts() (map[string]*api.Context, error) {
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -180,63 +207,14 @@ func (c *Config) RenameContext(old string, new string) error {
|
|||
}
|
||||
|
||||
// ContextNames fetch all available contexts.
|
||||
func (c *Config) ContextNames() ([]string, error) {
|
||||
func (c *Config) ContextNames() (map[string]struct{}, error) {
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := make([]string, 0, len(cfg.Contexts))
|
||||
cc := make(map[string]struct{}, len(cfg.Contexts))
|
||||
for n := range cfg.Contexts {
|
||||
cc = append(cc, n)
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// ClusterNameFromContext returns the cluster associated with the given context.
|
||||
func (c *Config) ClusterNameFromContext(context string) (string, error) {
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if ctx, ok := cfg.Contexts[context]; ok {
|
||||
return ctx.Cluster, nil
|
||||
}
|
||||
return "", fmt.Errorf("unable to locate cluster from context %s", context)
|
||||
}
|
||||
|
||||
// CurrentClusterName returns the active cluster name.
|
||||
func (c *Config) CurrentClusterName() (string, error) {
|
||||
if isSet(c.flags.ClusterName) {
|
||||
return *c.flags.ClusterName, nil
|
||||
}
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
context, err := c.CurrentContextName()
|
||||
if err != nil {
|
||||
context = cfg.CurrentContext
|
||||
}
|
||||
|
||||
if ctx, ok := cfg.Contexts[context]; ok {
|
||||
return ctx.Cluster, nil
|
||||
}
|
||||
|
||||
return "", errors.New("unable to locate current cluster")
|
||||
}
|
||||
|
||||
// ClusterNames fetch all kubeconfig defined clusters.
|
||||
func (c *Config) ClusterNames() (map[string]struct{}, error) {
|
||||
cfg, err := c.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := make(map[string]struct{}, len(cfg.Clusters))
|
||||
for name := range cfg.Clusters {
|
||||
cc[name] = struct{}{}
|
||||
cc[n] = struct{}{}
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
|
|
@ -297,16 +275,17 @@ func (c *Config) CurrentUserName() (string, error) {
|
|||
|
||||
// CurrentNamespaceName retrieves the active namespace.
|
||||
func (c *Config) CurrentNamespaceName() (string, error) {
|
||||
ns, _, err := c.clientConfig().Namespace()
|
||||
|
||||
if ns == "default" {
|
||||
ns, err = c.CurrentContextNamespace()
|
||||
if ns == "" && err == nil {
|
||||
return "", errors.New("No namespace specified in context")
|
||||
}
|
||||
ns, overridden, err := c.clientConfig().Namespace()
|
||||
if err != nil {
|
||||
return BlankNamespace, err
|
||||
}
|
||||
// Checks if ns is passed is in args.
|
||||
if overridden {
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
return ns, err
|
||||
// Return ns set in context if any??
|
||||
return c.CurrentContextNamespace()
|
||||
}
|
||||
|
||||
// ConfigAccess return the current kubeconfig api server access configuration.
|
||||
|
|
@ -320,16 +299,6 @@ func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// NamespaceNames fetch all available namespaces on current cluster.
|
||||
func NamespaceNames(nns []v1.Namespace) []string {
|
||||
nn := make([]string, 0, len(nns))
|
||||
for _, ns := range nns {
|
||||
nn = append(nn, ns.Name)
|
||||
}
|
||||
|
||||
return nn
|
||||
}
|
||||
|
||||
func isSet(s *string) bool {
|
||||
return s != nil && len(*s) != 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ package client_test
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
|
|
@ -55,15 +54,15 @@ func TestConfigCurrentCluster(t *testing.T) {
|
|||
name, kubeConfig := "blee", "./testdata/config"
|
||||
uu := map[string]struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
cluster string
|
||||
context string
|
||||
}{
|
||||
"default": {
|
||||
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig},
|
||||
cluster: "fred",
|
||||
context: "fred",
|
||||
},
|
||||
"custom": {
|
||||
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, ClusterName: &name},
|
||||
cluster: "blee",
|
||||
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Context: &name},
|
||||
context: "blee",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -71,9 +70,9 @@ func TestConfigCurrentCluster(t *testing.T) {
|
|||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
cfg := client.NewConfig(u.flags)
|
||||
ctx, err := cfg.CurrentClusterName()
|
||||
ct, err := cfg.CurrentContextName()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, u.cluster, ctx)
|
||||
assert.Equal(t, u.context, ct)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -173,8 +172,8 @@ func TestConfigGetContext(t *testing.T) {
|
|||
func TestConfigSwitchContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
KubeConfig: &kubeConfig,
|
||||
Context: &cluster,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
|
|
@ -185,24 +184,11 @@ func TestConfigSwitchContext(t *testing.T) {
|
|||
assert.Equal(t, "blee", ctx)
|
||||
}
|
||||
|
||||
func TestConfigClusterNameFromContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
cl, err := cfg.ClusterNameFromContext("blee")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "blee", cl)
|
||||
}
|
||||
|
||||
func TestConfigAccess(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
context, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
KubeConfig: &kubeConfig,
|
||||
Context: &context,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
|
|
@ -211,11 +197,24 @@ func TestConfigAccess(t *testing.T) {
|
|||
assert.True(t, len(acc.GetDefaultFilename()) > 0)
|
||||
}
|
||||
|
||||
func TestConfigContexts(t *testing.T) {
|
||||
func TestConfigContextNames(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
KubeConfig: &kubeConfig,
|
||||
Context: &cluster,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
cc, err := cfg.ContextNames()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(cc))
|
||||
}
|
||||
|
||||
func TestConfigContexts(t *testing.T) {
|
||||
context, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
Context: &context,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
|
|
@ -224,46 +223,24 @@ func TestConfigContexts(t *testing.T) {
|
|||
assert.Equal(t, 3, len(cc))
|
||||
}
|
||||
|
||||
func TestConfigContextNames(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
cc, err := cfg.ContextNames()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(cc))
|
||||
}
|
||||
|
||||
func TestConfigClusterNames(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
cc, err := cfg.ClusterNames()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(cc))
|
||||
}
|
||||
|
||||
func TestConfigDelContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./testdata/config.1"
|
||||
assert.NoError(t, cp("./testdata/config.2", "./testdata/config.1"))
|
||||
|
||||
context, kubeConfig := "duh", "./testdata/config.1"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
KubeConfig: &kubeConfig,
|
||||
Context: &context,
|
||||
}
|
||||
|
||||
cfg := client.NewConfig(&flags)
|
||||
err := cfg.DelContext("fred")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cc, err := cfg.ContextNames()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(cc))
|
||||
assert.Equal(t, "blee", cc[0])
|
||||
_, ok := cc["blee"]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestConfigRestConfig(t *testing.T) {
|
||||
|
|
@ -289,13 +266,13 @@ func TestConfigBadConfig(t *testing.T) {
|
|||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestNamespaceNames(t *testing.T) {
|
||||
nn := []v1.Namespace{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "ns1"}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "ns2"}},
|
||||
// Helpers...
|
||||
|
||||
func cp(src string, dst string) error {
|
||||
data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nns := client.NamespaceNames(nn)
|
||||
assert.Equal(t, 2, len(nns))
|
||||
assert.Equal(t, []string{"ns1", "ns2"}, nns)
|
||||
return os.WriteFile(dst, data, 0600)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var NoGVR = GVR{}
|
||||
|
||||
// GVR represents a kubernetes resource schema as a string.
|
||||
// Format is group/version/resources:subresource.
|
||||
type GVR struct {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func TestGVRCan(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAsGVR(t *testing.T) {
|
||||
func TestGVR(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
gvr string
|
||||
e schema.GroupVersionResource
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ var toFileName = regexp.MustCompile(`[^(\w/\.)]`)
|
|||
|
||||
// IsClusterWide returns true if ns designates cluster scope, false otherwise.
|
||||
func IsClusterWide(ns string) bool {
|
||||
return ns == NamespaceAll || ns == AllNamespaces || ns == ClusterScope
|
||||
return ns == NamespaceAll || ns == BlankNamespace || ns == ClusterScope
|
||||
}
|
||||
|
||||
// CleanseNamespace ensures all ns maps to blank.
|
||||
func CleanseNamespace(ns string) string {
|
||||
if IsAllNamespace(ns) {
|
||||
return AllNamespaces
|
||||
return BlankNamespace
|
||||
}
|
||||
|
||||
return ns
|
||||
|
|
@ -36,7 +36,7 @@ func IsAllNamespace(ns string) bool {
|
|||
|
||||
// IsAllNamespaces returns true if all namespaces, false otherwise.
|
||||
func IsAllNamespaces(ns string) bool {
|
||||
return ns == NamespaceAll || ns == AllNamespaces
|
||||
return ns == NamespaceAll || ns == BlankNamespace
|
||||
}
|
||||
|
||||
// IsNamespaced returns true if a specific ns is given.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
const (
|
||||
mxCacheSize = 100
|
||||
mxCacheExpiry = 1 * time.Minute
|
||||
podMXGVR = "metrics.k8s.io/v1beta1/pods"
|
||||
nodeMXGVR = "metrics.k8s.io/v1beta1/nodes"
|
||||
)
|
||||
|
||||
// MetricsDial tracks global metric server handle.
|
||||
|
|
@ -149,7 +151,7 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe
|
|||
const msg = "user is not authorized to list node metrics"
|
||||
|
||||
mx := new(mv1beta1.NodeMetricsList)
|
||||
if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
|
||||
if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +182,7 @@ func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1bet
|
|||
const msg = "user is not authorized to list node metrics"
|
||||
|
||||
mx := new(mv1beta1.NodeMetrics)
|
||||
if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
|
||||
if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
|
|
@ -218,9 +220,9 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
|
|||
const msg = "user is not authorized to list pods metrics"
|
||||
|
||||
if ns == NamespaceAll {
|
||||
ns = AllNamespaces
|
||||
ns = BlankNamespace
|
||||
}
|
||||
if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil {
|
||||
if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
|
|
@ -269,9 +271,9 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be
|
|||
|
||||
ns, _ := Namespaced(fqn)
|
||||
if ns == NamespaceAll {
|
||||
ns = AllNamespaces
|
||||
ns = BlankNamespace
|
||||
}
|
||||
if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil {
|
||||
if err := m.checkAccess(ns, podMXGVR, msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: https://localhost:3001
|
||||
name: blee
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: https://localhost:3002
|
||||
name: fred
|
||||
contexts:
|
||||
- context:
|
||||
cluster: blee
|
||||
user: blee
|
||||
name: blee
|
||||
- context:
|
||||
cluster: fred
|
||||
user: fred
|
||||
name: fred
|
||||
current-context: blee
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users: null
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery/cached/disk"
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
|
@ -21,8 +20,8 @@ const (
|
|||
// NamespaceAll designates the fictional all namespace.
|
||||
NamespaceAll = "all"
|
||||
|
||||
// AllNamespaces designates all namespaces.
|
||||
AllNamespaces = ""
|
||||
// BlankNamespace designates no namespace.
|
||||
BlankNamespace = ""
|
||||
|
||||
// DefaultNamespace designates the default namespace.
|
||||
DefaultNamespace = "default"
|
||||
|
|
@ -118,8 +117,11 @@ type Connection interface {
|
|||
// HasMetrics checks if metrics server is available.
|
||||
HasMetrics() bool
|
||||
|
||||
// ValidNamespaces returns all available namespaces.
|
||||
ValidNamespaces() ([]v1.Namespace, error)
|
||||
// ValidNamespaces returns all available namespace names.
|
||||
ValidNamespaceNames() (NamespaceNames, error)
|
||||
|
||||
// IsValidNamespace checks if given namespace is known.
|
||||
IsValidNamespace(string) bool
|
||||
|
||||
// ServerVersion returns current server version.
|
||||
ServerVersion() (*version.Info, error)
|
||||
|
|
@ -127,8 +129,8 @@ type Connection interface {
|
|||
// CheckConnectivity checks if api server connection is happy or not.
|
||||
CheckConnectivity() bool
|
||||
|
||||
// ActiveCluster returns the current cluster name.
|
||||
ActiveCluster() string
|
||||
// ActiveContext returns the current context name.
|
||||
ActiveContext() string
|
||||
|
||||
// ActiveNamespace returns the current namespace.
|
||||
ActiveNamespace() string
|
||||
|
|
|
|||
|
|
@ -5,16 +5,13 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sAlias manages K9s aliases.
|
||||
var K9sAlias = YamlExtension(filepath.Join(K9sHome(), "alias.yml"))
|
||||
|
||||
// Alias tracks shortname to GVR mappings.
|
||||
type Alias map[string]string
|
||||
|
||||
|
|
@ -23,7 +20,7 @@ type ShortNames map[string][]string
|
|||
|
||||
// Aliases represents a collection of aliases.
|
||||
type Aliases struct {
|
||||
Alias Alias `yaml:"alias"`
|
||||
Alias Alias `yaml:"aliases"`
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
|
|
@ -101,13 +98,28 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
|
|||
}
|
||||
|
||||
// Load K9s aliases.
|
||||
func (a *Aliases) Load() error {
|
||||
func (a *Aliases) Load(path string) error {
|
||||
a.loadDefaultAliases()
|
||||
return a.LoadFileAliases(K9sAlias)
|
||||
|
||||
f, err := EnsureAliasesCfgFile()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to gen config aliases")
|
||||
}
|
||||
|
||||
// load global alias file
|
||||
if err := a.LoadFile(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// load context specific aliases if any
|
||||
return a.LoadFile(path)
|
||||
}
|
||||
|
||||
// LoadFileAliases loads alias from a given file.
|
||||
func (a *Aliases) LoadFileAliases(path string) error {
|
||||
// LoadFile loads alias from a given file.
|
||||
func (a *Aliases) LoadFile(path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
var aa Aliases
|
||||
|
|
@ -136,15 +148,6 @@ func (a *Aliases) loadDefaultAliases() {
|
|||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
a.Alias["dp"] = "apps/v1/deployments"
|
||||
a.Alias["sec"] = "v1/secrets"
|
||||
a.Alias["jo"] = "batch/v1/jobs"
|
||||
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
|
||||
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
|
||||
a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles"
|
||||
a.Alias["rb"] = "rbac.authorization.k8s.io/v1/rolebindings"
|
||||
a.Alias["np"] = "networking.k8s.io/v1/networkpolicies"
|
||||
|
||||
a.declare("help", "h", "?")
|
||||
a.declare("quit", "q", "q!", "qa", "Q")
|
||||
a.declare("aliases", "alias", "a")
|
||||
|
|
@ -155,21 +158,22 @@ func (a *Aliases) loadDefaultAliases() {
|
|||
a.declare("users", "user", "usr")
|
||||
a.declare("groups", "group", "grp")
|
||||
a.declare("portforwards", "portforward", "pf")
|
||||
a.declare("benchmarks", "bench", "benchmark", "be")
|
||||
a.declare("benchmarks", "benchmark", "bench")
|
||||
a.declare("screendumps", "screendump", "sd")
|
||||
a.declare("pulses", "pulse", "pu", "hz")
|
||||
a.declare("xrays", "xray", "x")
|
||||
a.declare("workloads", "workload", "wk")
|
||||
}
|
||||
|
||||
// Save alias to disk.
|
||||
func (a *Aliases) Save() error {
|
||||
log.Debug().Msg("[Config] Saving Aliases...")
|
||||
return a.SaveAliases(K9sAlias)
|
||||
return a.SaveAliases(AppAliasesFile)
|
||||
}
|
||||
|
||||
// SaveAliases saves aliases to a given file.
|
||||
func (a *Aliases) SaveAliases(path string) error {
|
||||
if err := EnsureDirPath(path, DefaultDirMod); err != nil {
|
||||
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := yaml.Marshal(a)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func TestAliasDefine(t *testing.T) {
|
|||
func TestAliasesLoad(t *testing.T) {
|
||||
a := config.NewAliases()
|
||||
|
||||
assert.Nil(t, a.LoadFileAliases("testdata/alias.yml"))
|
||||
assert.Nil(t, a.LoadFile("testdata/alias.yaml"))
|
||||
assert.Equal(t, 2, len(a.Alias))
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ func TestAliasesSave(t *testing.T) {
|
|||
a.Alias["test"] = "fred"
|
||||
a.Alias["blee"] = "duh"
|
||||
|
||||
assert.Nil(t, a.SaveAliases("/tmp/a.yml"))
|
||||
assert.Nil(t, a.LoadFileAliases("/tmp/a.yml"))
|
||||
assert.Nil(t, a.SaveAliases("/tmp/a.yaml"))
|
||||
assert.Nil(t, a.LoadFile("/tmp/a.yaml"))
|
||||
assert.Equal(t, 2, len(a.Alias))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ const (
|
|||
DefaultMethod = "GET"
|
||||
)
|
||||
|
||||
// DefaultBenchSpec returns a default bench spec.
|
||||
func DefaultBenchSpec() BenchConfig {
|
||||
return BenchConfig{
|
||||
C: DefaultC,
|
||||
N: DefaultN,
|
||||
HTTP: HTTP{
|
||||
Method: DefaultMethod,
|
||||
Path: "/",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newBenchmark() Benchmark {
|
||||
return Benchmark{
|
||||
C: DefaultC,
|
||||
|
|
@ -106,15 +118,3 @@ func (s *Bench) load(path string) error {
|
|||
|
||||
return yaml.Unmarshal(f, &s)
|
||||
}
|
||||
|
||||
// DefaultBenchSpec returns a default bench spec.
|
||||
func DefaultBenchSpec() BenchConfig {
|
||||
return BenchConfig{
|
||||
C: DefaultC,
|
||||
N: DefaultN,
|
||||
HTTP: HTTP{
|
||||
Method: DefaultMethod,
|
||||
Path: "/",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -35,14 +35,14 @@ func TestBenchLoad(t *testing.T) {
|
|||
coCount int
|
||||
}{
|
||||
"goodConfig": {
|
||||
"testdata/b_good.yml",
|
||||
"testdata/b_good.yaml",
|
||||
2,
|
||||
1000,
|
||||
2,
|
||||
0,
|
||||
},
|
||||
"malformed": {
|
||||
"testdata/b_toast.yml",
|
||||
"testdata/b_toast.yaml",
|
||||
1,
|
||||
200,
|
||||
0,
|
||||
|
|
@ -103,7 +103,7 @@ func TestBenchServiceLoad(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
b, err := NewBench("testdata/b_good.yml")
|
||||
b, err := NewBench("testdata/b_good.yaml")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
||||
|
|
@ -122,16 +122,16 @@ func TestBenchServiceLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBenchReLoad(t *testing.T) {
|
||||
b, err := NewBench("testdata/b_containers.yml")
|
||||
b, err := NewBench("testdata/b_containers.yaml")
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, b.Benchmarks.Defaults.C)
|
||||
assert.Nil(t, b.Reload("testdata/b_containers_1.yml"))
|
||||
assert.Nil(t, b.Reload("testdata/b_containers_1.yaml"))
|
||||
assert.Equal(t, 20, b.Benchmarks.Defaults.C)
|
||||
}
|
||||
|
||||
func TestBenchLoadToast(t *testing.T) {
|
||||
_, err := NewBench("testdata/toast.yml")
|
||||
_, err := NewBench("testdata/toast.yaml")
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ func TestBenchContainerLoad(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
b, err := NewBench("testdata/b_containers.yml")
|
||||
b, err := NewBench("testdata/b_containers.yaml")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/derailed/k9s/internal/client"
|
||||
|
||||
// DefaultPFAddress specifies the default PortForward host address.
|
||||
const DefaultPFAddress = "localhost"
|
||||
|
||||
// Cluster tracks K9s cluster configuration.
|
||||
type Cluster struct {
|
||||
Namespace *Namespace `yaml:"namespace"`
|
||||
View *View `yaml:"view"`
|
||||
Skin string `yaml:"skin,omitempty"`
|
||||
FeatureGates *FeatureGates `yaml:"featureGates"`
|
||||
PortForwardAddress string `yaml:"portForwardAddress"`
|
||||
}
|
||||
|
||||
// NewCluster creates a new cluster configuration.
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{
|
||||
Namespace: NewNamespace(),
|
||||
View: NewView(),
|
||||
PortForwardAddress: DefaultPFAddress,
|
||||
FeatureGates: NewFeatureGates(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a cluster config.
|
||||
func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) {
|
||||
if c.PortForwardAddress == "" {
|
||||
c.PortForwardAddress = DefaultPFAddress
|
||||
}
|
||||
|
||||
if c.Namespace == nil {
|
||||
c.Namespace = NewNamespace()
|
||||
}
|
||||
if c.Namespace.Active == client.AllNamespaces {
|
||||
c.Namespace.Active = client.NamespaceAll
|
||||
}
|
||||
|
||||
if c.FeatureGates == nil {
|
||||
c.FeatureGates = NewFeatureGates()
|
||||
}
|
||||
|
||||
if c.View == nil {
|
||||
c.View = NewView()
|
||||
}
|
||||
c.View.Validate()
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestClusterValidate(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"})
|
||||
|
||||
c := config.NewCluster()
|
||||
c.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, "po", c.View.Active)
|
||||
assert.Equal(t, "default", c.Namespace.Active)
|
||||
assert.Equal(t, 1, len(c.Namespace.Favorites))
|
||||
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
|
||||
}
|
||||
|
||||
func TestClusterValidateEmpty(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"})
|
||||
|
||||
var c config.Cluster
|
||||
c.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, "po", c.View.Active)
|
||||
assert.Equal(t, "default", c.Namespace.Active)
|
||||
assert.Equal(t, 1, len(c.Namespace.Favorites))
|
||||
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
|
||||
}
|
||||
|
||||
func namespaces() []v1.Namespace {
|
||||
return []v1.Namespace{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fred",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -12,56 +11,26 @@ import (
|
|||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
// K9sConfig represents K9s configuration dir env var.
|
||||
const K9sConfig = "K9SCONFIG"
|
||||
|
||||
var (
|
||||
// K9sConfigFile represents K9s config file location.
|
||||
K9sConfigFile = filepath.Join(K9sHome(), "config.yml")
|
||||
|
||||
// K9sSkinDir represent K9s skin dir
|
||||
K9sSkinDir = filepath.Join(K9sHome(), "skins")
|
||||
|
||||
// K9sDefaultScreenDumpDir represents a default directory where K9s screen dumps will be persisted.
|
||||
K9sDefaultScreenDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser()))
|
||||
)
|
||||
|
||||
type (
|
||||
// KubeSettings exposes kubeconfig context information.
|
||||
KubeSettings interface {
|
||||
// CurrentContextName returns the name of the current context.
|
||||
CurrentContextName() (string, error)
|
||||
|
||||
// CurrentClusterName returns the name of the current cluster.
|
||||
CurrentClusterName() (string, error)
|
||||
|
||||
// CurrentNamespace returns the name of the current namespace.
|
||||
CurrentNamespaceName() (string, error)
|
||||
|
||||
// ClusterNames() returns all available cluster names.
|
||||
ClusterNames() (map[string]struct{}, error)
|
||||
}
|
||||
|
||||
// Config tracks K9s configuration options.
|
||||
Config struct {
|
||||
K9s *K9s `yaml:"k9s"`
|
||||
client client.Connection
|
||||
settings KubeSettings
|
||||
}
|
||||
)
|
||||
// Config tracks K9s configuration options.
|
||||
type Config struct {
|
||||
K9s *K9s `yaml:"k9s"`
|
||||
conn client.Connection
|
||||
settings data.KubeSettings
|
||||
}
|
||||
|
||||
// K9sHome returns k9s configs home directory.
|
||||
func K9sHome() string {
|
||||
if env := os.Getenv(K9sConfig); env != "" {
|
||||
if env := os.Getenv(K9sConfigDir); env != "" {
|
||||
return env
|
||||
}
|
||||
|
||||
xdgK9sHome, err := xdg.ConfigFile("k9s")
|
||||
xdgK9sHome, err := xdg.ConfigFile(AppName)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Unable to create configuration directory for k9s")
|
||||
}
|
||||
|
|
@ -70,35 +39,50 @@ func K9sHome() string {
|
|||
}
|
||||
|
||||
// NewConfig creates a new default config.
|
||||
func NewConfig(ks KubeSettings) *Config {
|
||||
return &Config{K9s: NewK9s(), settings: ks}
|
||||
func NewConfig(ks data.KubeSettings) *Config {
|
||||
return &Config{
|
||||
settings: ks,
|
||||
K9s: NewK9s(nil, ks),
|
||||
}
|
||||
}
|
||||
|
||||
// ContextAliasesPath returns a context specific aliases file spec.
|
||||
func (c *Config) ContextAliasesPath() string {
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return AppContextAliasesFile(ct.ClusterName, c.K9s.activeContextName)
|
||||
}
|
||||
|
||||
// ContextPluginsPath returns a context specific plugins file spec.
|
||||
func (c *Config) ContextPluginsPath() string {
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return AppContextPluginsFile(ct.ClusterName, c.K9s.activeContextName)
|
||||
}
|
||||
|
||||
// Refine the configuration based on cli args.
|
||||
func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error {
|
||||
if isSet(flags.Context) {
|
||||
c.K9s.CurrentContext = *flags.Context
|
||||
if _, err := c.K9s.ActivateContext(*flags.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
context, err := cfg.CurrentContextName()
|
||||
n, err := cfg.CurrentContextName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.K9s.ActivateContext(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.K9s.CurrentContext = context
|
||||
}
|
||||
log.Debug().Msgf("Active Context %q", c.K9s.CurrentContext)
|
||||
if c.K9s.CurrentContext == "" {
|
||||
return errors.New("Invalid kubeconfig context detected")
|
||||
}
|
||||
cc, err := cfg.Contexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context, ok := cc[c.K9s.CurrentContext]
|
||||
if !ok {
|
||||
return fmt.Errorf("the specified context %q does not exists in kubeconfig", c.K9s.CurrentContext)
|
||||
}
|
||||
c.K9s.CurrentCluster = context.Cluster
|
||||
c.K9s.ActivateCluster(context.Namespace)
|
||||
log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName())
|
||||
|
||||
var ns = client.DefaultNamespace
|
||||
switch {
|
||||
|
|
@ -107,96 +91,87 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
|
|||
case isSet(flags.Namespace):
|
||||
ns = *flags.Namespace
|
||||
default:
|
||||
if nss := context.Namespace; nss != "" {
|
||||
ns = nss
|
||||
} else if nss == "" {
|
||||
ns = c.K9s.ActiveCluster().Namespace.Active
|
||||
nss, err := c.K9s.ActiveContextNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ns = nss
|
||||
}
|
||||
|
||||
if err := c.SetActiveNamespace(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
flags.Namespace = &ns
|
||||
|
||||
if isSet(flags.ClusterName) {
|
||||
c.K9s.CurrentCluster = *flags.ClusterName
|
||||
}
|
||||
|
||||
return EnsureDirPath(c.K9s.GetScreenDumpDir(), DefaultDirMod)
|
||||
return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod)
|
||||
}
|
||||
|
||||
// Reset the context to the new current context/cluster.
|
||||
// if it does not exist.
|
||||
// Reset resets the context to the new current context/cluster.
|
||||
func (c *Config) Reset() {
|
||||
c.K9s.CurrentContext, c.K9s.CurrentCluster = "", ""
|
||||
c.K9s.Reset()
|
||||
}
|
||||
|
||||
// CurrentCluster fetch the configuration activeCluster.
|
||||
func (c *Config) CurrentCluster() *Cluster {
|
||||
if c, ok := c.K9s.Clusters[c.K9s.CurrentCluster]; ok {
|
||||
return c
|
||||
func (c *Config) SetCurrentContext(n string) (*data.Context, error) {
|
||||
ct, err := c.K9s.ActivateContext(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set current context %q failed: %w", n, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// ActiveNamespace returns the active namespace in the current cluster.
|
||||
// CurrentContext fetch the configuration active context.
|
||||
func (c *Config) CurrentContext() (*data.Context, error) {
|
||||
return c.K9s.ActiveContext()
|
||||
}
|
||||
|
||||
// ActiveNamespace returns the active namespace in the current context.
|
||||
// If none found return the empty ns.
|
||||
func (c *Config) ActiveNamespace() string {
|
||||
if c.K9s.Clusters == nil {
|
||||
log.Warn().Msgf("No context detected returning default namespace")
|
||||
return "default"
|
||||
}
|
||||
cl := c.CurrentCluster()
|
||||
if cl != nil && cl.Namespace != nil {
|
||||
return cl.Namespace.Active
|
||||
}
|
||||
if cl == nil {
|
||||
cl = NewCluster()
|
||||
c.K9s.Clusters[c.K9s.CurrentCluster] = cl
|
||||
}
|
||||
if ns, err := c.settings.CurrentNamespaceName(); err == nil && ns != "" {
|
||||
if cl.Namespace == nil {
|
||||
cl.Namespace = NewNamespace()
|
||||
}
|
||||
cl.Namespace.Active = ns
|
||||
return ns
|
||||
ns, err := c.K9s.ActiveContextNamespace()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to assert active namespace. Using default")
|
||||
ns = client.DefaultNamespace
|
||||
}
|
||||
|
||||
return "default"
|
||||
return ns
|
||||
}
|
||||
|
||||
// ValidateFavorites ensure favorite ns are legit.
|
||||
func (c *Config) ValidateFavorites() {
|
||||
cl := c.K9s.ActiveCluster()
|
||||
cl.Validate(c.client, c.settings)
|
||||
cl.Namespace.Validate(c.client, c.settings)
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err == nil {
|
||||
ct.Validate(c.conn, c.settings)
|
||||
ct.Namespace.Validate(c.conn, c.settings)
|
||||
}
|
||||
}
|
||||
|
||||
// FavNamespaces returns fav namespaces in the current cluster.
|
||||
// FavNamespaces returns fav namespaces in the current context.
|
||||
func (c *Config) FavNamespaces() []string {
|
||||
cl := c.K9s.ActiveCluster()
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cl.Namespace.Favorites
|
||||
return ct.Namespace.Favorites
|
||||
}
|
||||
|
||||
// SetActiveNamespace set the active namespace in the current cluster.
|
||||
// SetActiveNamespace set the active namespace in the current context.
|
||||
func (c *Config) SetActiveNamespace(ns string) error {
|
||||
if cl := c.K9s.ActiveCluster(); cl != nil {
|
||||
return cl.Namespace.SetActive(ns, c.settings)
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := errors.New("no active cluster. unable to set active namespace")
|
||||
log.Error().Err(err).Msg("SetActiveNamespace")
|
||||
|
||||
return err
|
||||
return ct.Namespace.SetActive(ns, c.settings)
|
||||
}
|
||||
|
||||
// ActiveView returns the active view in the current cluster.
|
||||
// ActiveView returns the active view in the current context.
|
||||
func (c *Config) ActiveView() string {
|
||||
cl := c.K9s.ActiveCluster()
|
||||
if cl == nil {
|
||||
return defaultView
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
return data.DefaultView
|
||||
}
|
||||
cmd := cl.View.Active
|
||||
cmd := ct.View.Active
|
||||
if c.K9s.manualCommand != nil && *c.K9s.manualCommand != "" {
|
||||
cmd = *c.K9s.manualCommand
|
||||
// We reset the manualCommand property because
|
||||
|
|
@ -208,37 +183,41 @@ func (c *Config) ActiveView() string {
|
|||
return cmd
|
||||
}
|
||||
|
||||
// SetActiveView set the currently cluster active view.
|
||||
// SetActiveView sets current context active view.
|
||||
func (c *Config) SetActiveView(view string) {
|
||||
if cl := c.K9s.ActiveCluster(); cl != nil {
|
||||
cl.View.Active = view
|
||||
if ct, err := c.K9s.ActiveContext(); err == nil {
|
||||
ct.View.Active = view
|
||||
}
|
||||
}
|
||||
|
||||
// GetConnection return an api server connection.
|
||||
func (c *Config) GetConnection() client.Connection {
|
||||
return c.client
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// SetConnection set an api server connection.
|
||||
func (c *Config) SetConnection(conn client.Connection) {
|
||||
c.client = conn
|
||||
c.conn, c.K9s.conn = conn, conn
|
||||
c.Validate()
|
||||
}
|
||||
|
||||
// Load K9s configuration from file.
|
||||
func (c *Config) ActiveContextName() string {
|
||||
return c.K9s.activeContextName
|
||||
}
|
||||
|
||||
// Load loads K9s configuration from file.
|
||||
func (c *Config) Load(path string) error {
|
||||
f, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.K9s = NewK9s()
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(f, &cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.K9s != nil {
|
||||
c.K9s = cfg.K9s
|
||||
c.K9s.Refine(cfg.K9s)
|
||||
}
|
||||
if c.K9s.Logger == nil {
|
||||
c.K9s.Logger = NewLogger()
|
||||
|
|
@ -249,13 +228,15 @@ func (c *Config) Load(path string) error {
|
|||
// Save configuration to disk.
|
||||
func (c *Config) Save() error {
|
||||
c.Validate()
|
||||
|
||||
return c.SaveFile(K9sConfigFile)
|
||||
if err := c.K9s.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SaveFile(AppConfigFile)
|
||||
}
|
||||
|
||||
// SaveFile K9s configuration to disk.
|
||||
func (c *Config) SaveFile(path string) error {
|
||||
if err := EnsureDirPath(path, DefaultDirMod); err != nil {
|
||||
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := yaml.Marshal(c)
|
||||
|
|
@ -268,14 +249,14 @@ func (c *Config) SaveFile(path string) error {
|
|||
|
||||
// Validate the configuration.
|
||||
func (c *Config) Validate() {
|
||||
c.K9s.Validate(c.client, c.settings)
|
||||
c.K9s.Validate(c.conn, c.settings)
|
||||
}
|
||||
|
||||
// Dump debug...
|
||||
func (c *Config) Dump(msg string) {
|
||||
log.Debug().Msgf("Current Cluster: %s\n", c.K9s.CurrentCluster)
|
||||
for k, cl := range c.K9s.Clusters {
|
||||
log.Debug().Msgf("K9s cluster: %s -- %+v\n", k, cl.Namespace)
|
||||
ct, err := c.K9s.ActiveContext()
|
||||
if err != nil {
|
||||
log.Debug().Msgf("Current Contexts: %s\n", ct.ClusterName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/config/mock"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -22,19 +22,16 @@ func init() {
|
|||
}
|
||||
|
||||
func TestConfigRefine(t *testing.T) {
|
||||
cfgFile, ctx, cluster, ns := "testdata/kubeconfig-test.yml", "test2", "cluster2", "ns2"
|
||||
var (
|
||||
cfgFile = "testdata/kubeconfig-test.yaml"
|
||||
ctx, cluster, ns = "ct-1-1", "cl-1", "ns-1"
|
||||
)
|
||||
|
||||
uu := map[string]struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
issue bool
|
||||
context, cluster, namespace string
|
||||
}{
|
||||
"plain": {
|
||||
flags: &genericclioptions.ConfigFlags{KubeConfig: &cfgFile},
|
||||
issue: false,
|
||||
context: "test1",
|
||||
cluster: "cluster1",
|
||||
namespace: "ns1",
|
||||
},
|
||||
"overrideNS": {
|
||||
flags: &genericclioptions.ConfigFlags{
|
||||
KubeConfig: &cfgFile,
|
||||
|
|
@ -61,18 +58,14 @@ func TestConfigRefine(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
mk := newMockSettings(u.flags)
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
|
||||
if u.issue {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, u.context, cfg.K9s.CurrentContext)
|
||||
assert.Equal(t, u.cluster, cfg.K9s.CurrentCluster)
|
||||
assert.Equal(t, u.context, cfg.K9s.ActiveContextName())
|
||||
assert.Equal(t, u.namespace, cfg.ActiveNamespace())
|
||||
}
|
||||
})
|
||||
|
|
@ -80,167 +73,60 @@ func TestConfigRefine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
cfg := mock.NewMockConfig()
|
||||
cfg.SetConnection(mock.NewMockConnection())
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
|
||||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
cfg.Validate()
|
||||
}
|
||||
|
||||
func TestConfigLoad(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
assert.Equal(t, 2, cfg.K9s.RefreshRate)
|
||||
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
|
||||
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
|
||||
assert.Equal(t, "minikube", cfg.K9s.CurrentContext)
|
||||
assert.Equal(t, "minikube", cfg.K9s.CurrentCluster)
|
||||
assert.NotNil(t, cfg.K9s.Clusters)
|
||||
assert.Equal(t, 2, len(cfg.K9s.Clusters))
|
||||
|
||||
nn := []string{
|
||||
"default",
|
||||
"kube-public",
|
||||
"istio-system",
|
||||
"all",
|
||||
"kube-system",
|
||||
}
|
||||
|
||||
assert.Equal(t, "kube-system", cfg.K9s.Clusters["minikube"].Namespace.Active)
|
||||
assert.Equal(t, nn, cfg.K9s.Clusters["minikube"].Namespace.Favorites)
|
||||
assert.Equal(t, "ctx", cfg.K9s.Clusters["minikube"].View.Active)
|
||||
}
|
||||
|
||||
func TestConfigCurrentCluster(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.NotNil(t, cfg.CurrentCluster())
|
||||
assert.Equal(t, "kube-system", cfg.CurrentCluster().Namespace.Active)
|
||||
assert.Equal(t, "ctx", cfg.CurrentCluster().View.Active)
|
||||
}
|
||||
|
||||
func TestConfigActiveNamespace(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Equal(t, "kube-system", cfg.ActiveNamespace())
|
||||
}
|
||||
|
||||
func TestConfigActiveNamespaceBlank(t *testing.T) {
|
||||
cfg := config.Config{K9s: new(config.K9s)}
|
||||
assert.Equal(t, "default", cfg.ActiveNamespace())
|
||||
}
|
||||
|
||||
func TestConfigSetActiveNamespace(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Nil(t, cfg.SetActiveNamespace("default"))
|
||||
assert.Equal(t, "default", cfg.ActiveNamespace())
|
||||
}
|
||||
|
||||
func TestConfigActiveView(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Equal(t, "ctx", cfg.ActiveView())
|
||||
}
|
||||
|
||||
func TestConfigActiveViewBlank(t *testing.T) {
|
||||
cfg := config.Config{K9s: new(config.K9s)}
|
||||
assert.Equal(t, "po", cfg.ActiveView())
|
||||
}
|
||||
|
||||
func TestConfigSetActiveView(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.SetActiveView("po")
|
||||
assert.Equal(t, "po", cfg.ActiveView())
|
||||
}
|
||||
|
||||
func TestConfigFavNamespaces(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"}
|
||||
assert.Equal(t, expectedNS, cfg.FavNamespaces())
|
||||
}
|
||||
|
||||
func TestConfigLoadOldCfg(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s_old.yml"))
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s_old.yaml"))
|
||||
}
|
||||
|
||||
func TestConfigLoadCrap(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yml"))
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yaml"))
|
||||
}
|
||||
|
||||
func TestConfigSaveFile(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.CurrentContextName()).ThenReturn("minikube", nil)
|
||||
m.When(mk.CurrentClusterName()).ThenReturn("minikube", nil)
|
||||
m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil)
|
||||
m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"minikube": {}, "fred": {}, "blee": {}}, nil)
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.RefreshRate = 100
|
||||
cfg.K9s.ReadOnly = true
|
||||
cfg.K9s.Logger.TailCount = 500
|
||||
cfg.K9s.Logger.BufferSize = 800
|
||||
cfg.K9s.CurrentContext = "blee"
|
||||
cfg.K9s.CurrentCluster = "blee"
|
||||
cfg.Validate()
|
||||
path := filepath.Join("/tmp", "k9s.yml")
|
||||
|
||||
path := filepath.Join("/tmp", "k9s.yaml")
|
||||
err := cfg.SaveFile(path)
|
||||
assert.Nil(t, err)
|
||||
|
||||
raw, err := os.ReadFile(path)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectedConfig, string(raw))
|
||||
}
|
||||
|
||||
func TestConfigReset(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.CurrentContextName()).ThenReturn("blee", nil)
|
||||
m.When(mk.CurrentClusterName()).ThenReturn("blee", nil)
|
||||
m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil)
|
||||
m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"blee": {}}, nil)
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
|
||||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg := mock.NewMockConfig()
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
cfg.Reset()
|
||||
cfg.Validate()
|
||||
|
||||
path := filepath.Join("/tmp", "k9s.yml")
|
||||
path := filepath.Join("/tmp", "k9s.yaml")
|
||||
err := cfg.SaveFile(path)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
@ -258,46 +144,35 @@ func TestSetup(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
type mockSettings struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
}
|
||||
|
||||
var _ config.KubeSettings = (*mockSettings)(nil)
|
||||
|
||||
func newMockSettings(flags *genericclioptions.ConfigFlags) *mockSettings {
|
||||
return &mockSettings{flags: flags}
|
||||
}
|
||||
func (m *mockSettings) CurrentContextName() (string, error) {
|
||||
return *m.flags.Context, nil
|
||||
}
|
||||
func (m *mockSettings) CurrentClusterName() (string, error) { return "", nil }
|
||||
func (m *mockSettings) CurrentNamespaceName() (string, error) {
|
||||
return *m.flags.Namespace, nil
|
||||
}
|
||||
func (m *mockSettings) ClusterNames() (map[string]struct{}, error) { return nil, nil }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test Data...
|
||||
|
||||
var expectedConfig = `k9s:
|
||||
liveViewAutoRefresh: true
|
||||
screenDumpDir: /tmp
|
||||
refreshRate: 100
|
||||
maxConnRetry: 5
|
||||
enableMouse: false
|
||||
enableImageScan: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
readOnly: true
|
||||
noExitOnCtrlC: false
|
||||
noIcons: false
|
||||
ui:
|
||||
enableMouse: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
noIcons: false
|
||||
skipLatestRevCheck: false
|
||||
disablePodCounting: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
skipLatestRevCheck: false
|
||||
imageScans:
|
||||
enable: false
|
||||
blackList:
|
||||
namespaces: []
|
||||
labels: {}
|
||||
logger:
|
||||
tail: 500
|
||||
buffer: 800
|
||||
|
|
@ -305,51 +180,6 @@ var expectedConfig = `k9s:
|
|||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
currentContext: blee
|
||||
currentCluster: blee
|
||||
keepMissingClusters: false
|
||||
clusters:
|
||||
blee:
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
fred:
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
- kube-public
|
||||
- istio-system
|
||||
- all
|
||||
- kube-system
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
minikube:
|
||||
namespace:
|
||||
active: kube-system
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
- kube-public
|
||||
- istio-system
|
||||
- all
|
||||
- kube-system
|
||||
view:
|
||||
active: ctx
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
thresholds:
|
||||
cpu:
|
||||
critical: 90
|
||||
|
|
@ -357,29 +187,34 @@ var expectedConfig = `k9s:
|
|||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
screenDumpDir: /tmp
|
||||
disablePodCounting: false
|
||||
`
|
||||
|
||||
var resetConfig = `k9s:
|
||||
liveViewAutoRefresh: true
|
||||
screenDumpDir: /tmp
|
||||
refreshRate: 2
|
||||
maxConnRetry: 5
|
||||
enableMouse: false
|
||||
enableImageScan: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
noIcons: false
|
||||
ui:
|
||||
enableMouse: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
noIcons: false
|
||||
skipLatestRevCheck: false
|
||||
disablePodCounting: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
skipLatestRevCheck: false
|
||||
imageScans:
|
||||
enable: false
|
||||
blackList:
|
||||
namespaces: []
|
||||
labels: {}
|
||||
logger:
|
||||
tail: 200
|
||||
buffer: 2000
|
||||
|
|
@ -387,21 +222,6 @@ var resetConfig = `k9s:
|
|||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
currentContext: blee
|
||||
currentCluster: blee
|
||||
keepMissingClusters: false
|
||||
clusters:
|
||||
blee:
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
thresholds:
|
||||
cpu:
|
||||
critical: 90
|
||||
|
|
@ -409,6 +229,4 @@ var resetConfig = `k9s:
|
|||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
screenDumpDir: /tmp
|
||||
disablePodCounting: false
|
||||
`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// Config tracks a context configuration.
|
||||
type Config struct {
|
||||
Context *Context `yaml:"k9s"`
|
||||
}
|
||||
|
||||
func NewConfig(ct *api.Context) *Config {
|
||||
return &Config{
|
||||
Context: NewContextFromConfig(ct),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Validate(conn client.Connection, ks KubeSettings) {
|
||||
c.Context.Validate(conn, ks)
|
||||
}
|
||||
|
||||
func (c *Config) Dump(w io.Writer) {
|
||||
bb, _ := yaml.Marshal(&c)
|
||||
|
||||
fmt.Fprintf(w, "%s\n", string(bb))
|
||||
}
|
||||
|
||||
func (c *Config) Save(path string) error {
|
||||
if err := EnsureDirPath(path, DefaultDirMod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, cfg, DefaultFileMod)
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// DefaultPFAddress specifies the default PortForward host address.
|
||||
const DefaultPFAddress = "localhost"
|
||||
|
||||
// Context tracks K9s context configuration.
|
||||
type Context struct {
|
||||
ClusterName string `yaml:"cluster,omitempty"`
|
||||
ReadOnly bool `yaml:"readOnly"`
|
||||
Skin string `yaml:"skin,omitempty"`
|
||||
Namespace *Namespace `yaml:"namespace"`
|
||||
View *View `yaml:"view"`
|
||||
FeatureGates FeatureGates `yaml:"featureGates"`
|
||||
PortForwardAddress string `yaml:"portForwardAddress"`
|
||||
}
|
||||
|
||||
// NewContext creates a new cluster configuration.
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
Namespace: NewNamespace(),
|
||||
View: NewView(),
|
||||
PortForwardAddress: DefaultPFAddress,
|
||||
FeatureGates: NewFeatureGates(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewContextFromConfig(cfg *api.Context) *Context {
|
||||
return &Context{
|
||||
Namespace: NewActiveNamespace(cfg.Namespace),
|
||||
ClusterName: cfg.Cluster,
|
||||
View: NewView(),
|
||||
PortForwardAddress: DefaultPFAddress,
|
||||
FeatureGates: NewFeatureGates(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a context config.
|
||||
func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
|
||||
if c.PortForwardAddress == "" {
|
||||
c.PortForwardAddress = DefaultPFAddress
|
||||
}
|
||||
|
||||
if cl, err := ks.CurrentClusterName(); err != nil {
|
||||
c.ClusterName = cl
|
||||
}
|
||||
|
||||
if c.Namespace == nil {
|
||||
c.Namespace = NewNamespace()
|
||||
}
|
||||
if c.Namespace.Active == client.BlankNamespace {
|
||||
c.Namespace.Active = client.DefaultNamespace
|
||||
}
|
||||
c.Namespace.Validate(conn, ks)
|
||||
|
||||
if c.View == nil {
|
||||
c.View = NewView()
|
||||
}
|
||||
c.View.Validate()
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/derailed/k9s/internal/config/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClusterValidate(t *testing.T) {
|
||||
c := data.NewContext()
|
||||
c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, "po", c.View.Active)
|
||||
assert.Equal(t, "default", c.Namespace.Active)
|
||||
assert.Equal(t, 1, len(c.Namespace.Favorites))
|
||||
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
|
||||
}
|
||||
|
||||
func TestClusterValidateEmpty(t *testing.T) {
|
||||
c := data.NewContext()
|
||||
c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, "po", c.View.Active)
|
||||
assert.Equal(t, "default", c.Namespace.Active)
|
||||
assert.Equal(t, 1, len(c.Namespace.Favorites))
|
||||
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type Dir struct {
|
||||
root string
|
||||
conn client.Connection
|
||||
ks KubeSettings
|
||||
}
|
||||
|
||||
func NewDir(root string, conn client.Connection, ks KubeSettings) *Dir {
|
||||
return &Dir{
|
||||
root: root,
|
||||
ks: ks,
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (d Dir) Load(n string, ct *api.Context) (*Config, error) {
|
||||
if ct == nil {
|
||||
return nil, errors.New("api.Context must not be nil")
|
||||
}
|
||||
|
||||
var (
|
||||
path = filepath.Join(d.root, ct.Cluster, n, MainConfigFile)
|
||||
cfg *Config
|
||||
err error
|
||||
)
|
||||
if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 {
|
||||
log.Debug().Msgf("Context config not found! Generating... %q", path)
|
||||
cfg, err = d.genConfig(path, ct)
|
||||
} else {
|
||||
log.Debug().Msgf("Found existing context config: %q", path)
|
||||
cfg, err = d.loadConfig(path)
|
||||
}
|
||||
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) {
|
||||
cfg := NewConfig(ct)
|
||||
cfg.Validate(d.conn, d.ks)
|
||||
if err := cfg.Save(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (d *Dir) loadConfig(path string) (*Config, error) {
|
||||
bb, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(bb, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Validate(d.conn, d.ks)
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/derailed/k9s/internal/config/mock"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
func init() {
|
||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||
}
|
||||
|
||||
func TestDirLoad(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
dir string
|
||||
flags *genericclioptions.ConfigFlags
|
||||
err error
|
||||
cfg *data.Config
|
||||
}{
|
||||
"happy-cl-1-ct-1": {
|
||||
dir: "testdata/data/k9s",
|
||||
flags: makeFlags("cl-1", "ct-1-1"),
|
||||
cfg: mustLoadConfig("testdata/configs/ct-1-1.yaml"),
|
||||
},
|
||||
|
||||
"happy-cl-1-ct2": {
|
||||
dir: "testdata/data/k9s",
|
||||
flags: makeFlags("cl-1", "ct-1-2"),
|
||||
cfg: mustLoadConfig("testdata/configs/ct-1-2.yaml"),
|
||||
},
|
||||
|
||||
"happy-cl-2": {
|
||||
dir: "testdata/data/k9s",
|
||||
flags: makeFlags("cl-2", "ct-2-1"),
|
||||
cfg: mustLoadConfig("testdata/configs/ct-2-1.yaml"),
|
||||
},
|
||||
|
||||
"toast": {
|
||||
dir: "/tmp/data/k9s",
|
||||
flags: makeFlags("cl-test", "ct-test-1"),
|
||||
cfg: mustLoadConfig("testdata/configs/def_ct.yaml"),
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.NotNil(t, u.cfg, "test config must not be nil")
|
||||
if u.cfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ks := mock.NewMockKubeSettings(u.flags)
|
||||
if strings.Index(u.dir, "/tmp") == 0 {
|
||||
assert.NoError(t, mock.EnsureDir(u.dir))
|
||||
}
|
||||
|
||||
d := data.NewDir(u.dir, mock.NewMockConnection(), ks)
|
||||
ct, err := ks.CurrentContext()
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := d.Load(*u.flags.Context, ct)
|
||||
assert.Equal(t, u.err, err)
|
||||
if u.err == nil {
|
||||
assert.Equal(t, u.cfg, cfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func makeFlags(cl, ct string) *genericclioptions.ConfigFlags {
|
||||
return &genericclioptions.ConfigFlags{
|
||||
ClusterName: &cl,
|
||||
Context: &ct,
|
||||
}
|
||||
}
|
||||
|
||||
func mustLoadConfig(cfg string) *data.Config {
|
||||
bb, err := os.ReadFile(cfg)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var ct data.Config
|
||||
if err = yaml.Unmarshal(bb, &ct); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ct
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
// FeatureGates represents K9s opt-in features.
|
||||
type FeatureGates struct {
|
||||
NodeShell bool `yaml:"nodeShell"`
|
||||
}
|
||||
|
||||
// NewFeatureGates returns a new feature gate.
|
||||
func NewFeatureGates() FeatureGates {
|
||||
return FeatureGates{}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// InList check if string is in a collection of strings.
|
||||
func InList(ll []string, n string) bool {
|
||||
for _, l := range ll {
|
||||
if l == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EnsureDirPath ensures a directory exist from the given path.
|
||||
func EnsureDirPath(path string, mod os.FileMode) error {
|
||||
return EnsureFullPath(filepath.Dir(path), mod)
|
||||
}
|
||||
|
||||
// EnsureFullPath ensures a directory exist from the given path.
|
||||
func EnsureFullPath(path string, mod os.FileMode) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path, mod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHelperInList(t *testing.T) {
|
||||
uu := []struct {
|
||||
item string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"a", []string{}, false},
|
||||
{"", []string{}, false},
|
||||
{"", []string{""}, true},
|
||||
{"a", []string{"a", "b", "c", "d"}, true},
|
||||
{"z", []string{"a", "b", "c", "d"}, false},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.expected, data.InList(u.list, u.item))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureDirPathNone(t *testing.T) {
|
||||
var mod os.FileMode = 0744
|
||||
dir := filepath.Join("/tmp", "fred")
|
||||
os.Remove(dir)
|
||||
|
||||
path := filepath.Join(dir, "duh.yaml")
|
||||
assert.NoError(t, data.EnsureDirPath(path, mod))
|
||||
|
||||
p, err := os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||
}
|
||||
|
||||
func TestEnsureDirPathNoOpt(t *testing.T) {
|
||||
var mod os.FileMode = 0744
|
||||
dir := filepath.Join("/tmp", "k9s-test")
|
||||
os.Remove(dir)
|
||||
assert.NoError(t, os.Mkdir(dir, mod))
|
||||
|
||||
path := filepath.Join(dir, "duh.yaml")
|
||||
assert.NoError(t, data.EnsureDirPath(path, mod))
|
||||
|
||||
p, err := os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -11,8 +11,6 @@ import (
|
|||
const (
|
||||
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
|
||||
MaxFavoritesNS = 9
|
||||
defaultNS = "default"
|
||||
allNS = "all"
|
||||
)
|
||||
|
||||
// Namespace tracks active and favorites namespaces.
|
||||
|
|
@ -25,27 +23,33 @@ type Namespace struct {
|
|||
// NewNamespace create a new namespace configuration.
|
||||
func NewNamespace() *Namespace {
|
||||
return &Namespace{
|
||||
Active: defaultNS,
|
||||
Favorites: []string{defaultNS},
|
||||
Active: client.DefaultNamespace,
|
||||
Favorites: []string{client.DefaultNamespace},
|
||||
}
|
||||
}
|
||||
|
||||
func NewActiveNamespace(n string) *Namespace {
|
||||
return &Namespace{
|
||||
Active: n,
|
||||
Favorites: []string{client.DefaultNamespace},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a namespace is setup correctly.
|
||||
func (n *Namespace) Validate(c client.Connection, ks KubeSettings) {
|
||||
if c == nil {
|
||||
n = NewActiveNamespace(client.DefaultNamespace)
|
||||
}
|
||||
if c == nil {
|
||||
log.Debug().Msgf("No connection found. Skipping ns validation")
|
||||
return
|
||||
}
|
||||
nns, err := c.ValidNamespaces()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nn := client.NamespaceNames(nns)
|
||||
if !n.isAllNamespaces() && !InList(nn, n.Active) {
|
||||
if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) {
|
||||
log.Error().Msgf("[Config] Validation error active namespace %q does not exists", n.Active)
|
||||
}
|
||||
|
||||
for _, ns := range n.Favorites {
|
||||
if ns != allNS && !InList(nn, ns) {
|
||||
if ns != client.NamespaceAll && !c.IsValidNamespace(ns) {
|
||||
log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces())
|
||||
n.rmFavNS(ns)
|
||||
}
|
||||
|
|
@ -54,8 +58,8 @@ func (n *Namespace) Validate(c client.Connection, ks KubeSettings) {
|
|||
|
||||
// SetActive set the active namespace.
|
||||
func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
|
||||
if ns == client.NotNamespaced {
|
||||
ns = client.AllNamespaces
|
||||
if ns == client.BlankNamespace {
|
||||
ns = client.NamespaceAll
|
||||
}
|
||||
n.Active = ns
|
||||
if ns != "" && !n.LockFavorites {
|
||||
|
|
@ -66,7 +70,7 @@ func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
|
|||
}
|
||||
|
||||
func (n *Namespace) isAllNamespaces() bool {
|
||||
return n.Active == allNS || n.Active == ""
|
||||
return n.Active == client.NamespaceAll || n.Active == ""
|
||||
}
|
||||
|
||||
func (n *Namespace) addFavNS(ns string) {
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/derailed/k9s/internal/config/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNSValidate(t *testing.T) {
|
||||
ns := data.NewNamespace()
|
||||
ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSValidateMissing(t *testing.T) {
|
||||
ns := data.NewNamespace()
|
||||
ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSValidateNoNS(t *testing.T) {
|
||||
ns := data.NewNamespace()
|
||||
ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSSetActive(t *testing.T) {
|
||||
allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"}
|
||||
uu := []struct {
|
||||
ns string
|
||||
fav []string
|
||||
}{
|
||||
{"all", []string{"all", "default"}},
|
||||
{"ns1", []string{"ns1", "all", "default"}},
|
||||
{"ns2", []string{"ns2", "ns1", "all", "default"}},
|
||||
{"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}},
|
||||
{"ns4", allNS},
|
||||
}
|
||||
|
||||
mk := mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))
|
||||
ns := data.NewNamespace()
|
||||
for _, u := range uu {
|
||||
err := ns.SetActive(u.ns, mk)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, u.ns, ns.Active)
|
||||
assert.Equal(t, u.fav, ns.Favorites)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSValidateRmFavs(t *testing.T) {
|
||||
ns := data.NewNamespace()
|
||||
ns.Favorites = []string{"default", "fred"}
|
||||
ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")))
|
||||
|
||||
assert.Equal(t, []string{"default", "fred"}, ns.Favorites)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
k9s:
|
||||
cluster: cl-1
|
||||
skin: skin-1
|
||||
readOnly: false
|
||||
namespace:
|
||||
active: ns-1
|
||||
lockFavorites: true
|
||||
favorites:
|
||||
- default
|
||||
- ns-1
|
||||
- ns-2
|
||||
view:
|
||||
active: dp
|
||||
featureGates:
|
||||
nodeShell: true
|
||||
portForwardAddress: localhost
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
k9s:
|
||||
cluster: cl-1
|
||||
skin: in_the_navy
|
||||
readOnly: true
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
k9s:
|
||||
cluster: cl-2
|
||||
skin: skin-2
|
||||
readOnly: true
|
||||
namespace:
|
||||
active: ns-2
|
||||
lockFavorites: true
|
||||
favorites:
|
||||
- ns-1
|
||||
- ns-2
|
||||
view:
|
||||
active: svc
|
||||
featureGates:
|
||||
nodeShell: true
|
||||
portForwardAddress: fred
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
k9s:
|
||||
cluster: cl-test
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
k9s:
|
||||
cluster: cl-1
|
||||
skin: skin-1
|
||||
readOnly: false
|
||||
namespace:
|
||||
active: ns-1
|
||||
lockFavorites: true
|
||||
favorites:
|
||||
- default
|
||||
- ns-1
|
||||
- ns-2
|
||||
view:
|
||||
active: dp
|
||||
featureGates:
|
||||
nodeShell: true
|
||||
portForwardAddress: localhost
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
k9s:
|
||||
cluster: cl-1
|
||||
skin: in_the_navy
|
||||
readOnly: true
|
||||
namespace:
|
||||
active: default
|
||||
lockFavorites: false
|
||||
favorites:
|
||||
- default
|
||||
view:
|
||||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
k9s:
|
||||
cluster: cl-2
|
||||
skin: skin-2
|
||||
readOnly: true
|
||||
namespace:
|
||||
active: ns-2
|
||||
lockFavorites: true
|
||||
favorites:
|
||||
- ns-1
|
||||
- ns-2
|
||||
view:
|
||||
active: svc
|
||||
featureGates:
|
||||
nodeShell: true
|
||||
portForwardAddress: fred
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultDirMod default unix perms for k9s directory.
|
||||
DefaultDirMod os.FileMode = 0744
|
||||
|
||||
// DefaultFileMod default unix perms for k9s files.
|
||||
DefaultFileMod os.FileMode = 0600
|
||||
|
||||
// MainConfigFile track main configuration file..
|
||||
MainConfigFile = "config.yaml"
|
||||
)
|
||||
|
||||
// KubeSettings exposes kubeconfig context information.
|
||||
type KubeSettings interface {
|
||||
// CurrentContextName returns the name of the current context.
|
||||
CurrentContextName() (string, error)
|
||||
|
||||
// CurrentClusterName returns the name of the current cluster.
|
||||
CurrentClusterName() (string, error)
|
||||
|
||||
// CurrentNamespace returns the name of the current namespace.
|
||||
CurrentNamespaceName() (string, error)
|
||||
|
||||
// ContextNames() returns all available context names.
|
||||
ContextNames() (map[string]struct{}, error)
|
||||
|
||||
// CurrentContext returns the current context configuration.
|
||||
CurrentContext() (*api.Context, error)
|
||||
|
||||
// GetContext returns a given context configuration or err if not found.
|
||||
GetContext(string) (*api.Context, error)
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
package data
|
||||
|
||||
const defaultView = "po"
|
||||
const DefaultView = "po"
|
||||
|
||||
// View tracks view configuration options.
|
||||
type View struct {
|
||||
|
|
@ -12,12 +12,12 @@ type View struct {
|
|||
|
||||
// NewView creates a new view configuration.
|
||||
func NewView() *View {
|
||||
return &View{Active: defaultView}
|
||||
return &View{Active: DefaultView}
|
||||
}
|
||||
|
||||
// Validate a view configuration.
|
||||
func (v *View) Validate() {
|
||||
if len(v.Active) == 0 {
|
||||
v.Active = defaultView
|
||||
v.Active = DefaultView
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestViewValidate(t *testing.T) {
|
||||
v := config.NewView()
|
||||
v := data.NewView()
|
||||
|
||||
v.Validate()
|
||||
assert.Equal(t, "po", v.Active)
|
||||
|
|
@ -22,7 +22,7 @@ func TestViewValidate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestViewValidateBlank(t *testing.T) {
|
||||
var v config.View
|
||||
var v data.View
|
||||
v.Validate()
|
||||
assert.Equal(t, "po", v.Active)
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
package config
|
||||
|
||||
// FeatureGates represents K9s opt-in features.
|
||||
type FeatureGates struct {
|
||||
NodeShell bool `yaml:"nodeShell"`
|
||||
}
|
||||
// // FeatureGates represents K9s opt-in features.
|
||||
// type FeatureGates struct {
|
||||
// NodeShell bool `yaml:"nodeShell"`
|
||||
// }
|
||||
|
||||
// NewFeatureGates returns a new feature gate.
|
||||
func NewFeatureGates() *FeatureGates {
|
||||
return &FeatureGates{}
|
||||
}
|
||||
// // NewFeatureGates returns a new feature gate.
|
||||
// func NewFeatureGates() *FeatureGates {
|
||||
// return &FeatureGates{}
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,297 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// K9sConfigDir represents k9s configuration dir env var.
|
||||
K9sConfigDir = "K9S_CONFIG_DIR"
|
||||
|
||||
// AppName tracks k9s app name.
|
||||
AppName = "k9s"
|
||||
|
||||
K9sLogsFile = "k9s.log"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed templates/benchmarks.yaml
|
||||
// benchmarkTpl tracks benchmark default config template
|
||||
benchmarkTpl []byte
|
||||
|
||||
//go:embed templates/aliases.yaml
|
||||
// aliasesTpl tracks aliases default config template
|
||||
aliasesTpl []byte
|
||||
|
||||
//go:embed templates/hotkeys.yaml
|
||||
// hotkeysTpl tracks hotkeys default config template
|
||||
hotkeysTpl []byte
|
||||
|
||||
//go:embed templates/stock-skin.yaml
|
||||
// stockSkinTpl tracks stock skin template
|
||||
stockSkinTpl []byte
|
||||
)
|
||||
|
||||
var (
|
||||
// AppConfigDir tracks main k9s config home directory.
|
||||
AppConfigDir string
|
||||
|
||||
// AppSkinsDir tracks skins data directory.
|
||||
AppSkinsDir string
|
||||
|
||||
// AppBenchmarksDir tracks benchmarks results directory.
|
||||
AppBenchmarksDir string
|
||||
|
||||
// AppDumpsDir tracks screen dumps data directory.
|
||||
AppDumpsDir string
|
||||
|
||||
// AppContextsDir tracks contexts data directory.
|
||||
AppContextsDir string
|
||||
|
||||
// AppConfigFile tracks k9s config file.
|
||||
AppConfigFile string
|
||||
|
||||
// AppLogFile tracks k9s logs file.
|
||||
AppLogFile string
|
||||
|
||||
// AppViewsFile tracks custom views config file.
|
||||
AppViewsFile string
|
||||
|
||||
// AppAliasesFile tracks aliases config file.
|
||||
AppAliasesFile string
|
||||
|
||||
// AppPluginsFile tracks plugins config file.
|
||||
AppPluginsFile string
|
||||
|
||||
// AppHotKeysFile tracks hotkeys config file.
|
||||
AppHotKeysFile string
|
||||
)
|
||||
|
||||
// InitLogsLoc initializes K9s logs location.
|
||||
func InitLogLoc() error {
|
||||
if hasK9sConfigEnv() {
|
||||
tmpDir, err := userTmpDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
AppLogFile = filepath.Join(tmpDir, K9sLogsFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
AppLogFile, err = xdg.StateFile(filepath.Join(AppName, K9sLogsFile))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// InitLocs initializes k9s artifacts locations.
|
||||
func InitLocs() error {
|
||||
if hasK9sConfigEnv() {
|
||||
return initK9sEnvLocs()
|
||||
}
|
||||
|
||||
return initXDGLocs()
|
||||
}
|
||||
|
||||
func initK9sEnvLocs() error {
|
||||
AppConfigDir = os.Getenv(K9sConfigDir)
|
||||
if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AppDumpsDir = filepath.Join(AppConfigDir, "screen-dumps")
|
||||
if err := data.EnsureFullPath(AppDumpsDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("Unable to create screen-dumps dir: %s", AppDumpsDir)
|
||||
}
|
||||
AppBenchmarksDir = filepath.Join(AppConfigDir, "benchmarks")
|
||||
if err := data.EnsureFullPath(AppBenchmarksDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("Unable to create benchmarks dir: %s", AppBenchmarksDir)
|
||||
}
|
||||
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
|
||||
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("Unable to create skins dir: %s", AppSkinsDir)
|
||||
}
|
||||
AppContextsDir = filepath.Join(AppConfigDir, "clusters")
|
||||
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("Unable to create clusters dir: %s", AppContextsDir)
|
||||
}
|
||||
|
||||
AppConfigFile = filepath.Join(AppConfigDir, data.MainConfigFile)
|
||||
AppHotKeysFile = filepath.Join(AppConfigDir, "hotkeys.yaml")
|
||||
AppAliasesFile = filepath.Join(AppConfigDir, "aliases.yaml")
|
||||
AppPluginsFile = filepath.Join(AppConfigDir, "plugins.yaml")
|
||||
AppViewsFile = filepath.Join(AppConfigDir, "views.yaml")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initXDGLocs() error {
|
||||
var err error
|
||||
|
||||
AppConfigDir, err = xdg.ConfigFile(AppName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AppConfigFile, err = xdg.ConfigFile(filepath.Join(AppName, data.MainConfigFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AppHotKeysFile = filepath.Join(AppConfigDir, "hotkeys.yaml")
|
||||
AppAliasesFile = filepath.Join(AppConfigDir, "aliases.yaml")
|
||||
AppPluginsFile = filepath.Join(AppConfigDir, "plugins.yaml")
|
||||
AppViewsFile = filepath.Join(AppConfigDir, "views.yaml")
|
||||
|
||||
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
|
||||
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("No skins dir detected")
|
||||
}
|
||||
|
||||
AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks"))
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No benchmarks dir detected")
|
||||
}
|
||||
|
||||
dataDir, err := xdg.DataFile(AppName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
AppContextsDir = filepath.Join(dataDir, "clusters")
|
||||
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil {
|
||||
log.Warn().Err(err).Msgf("No context dir detected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
|
||||
|
||||
// SanitizeFileName ensure file spec is valid.
|
||||
func SanitizeFileName(name string) string {
|
||||
return invalidPathCharsRX.ReplaceAllString(name, "-")
|
||||
}
|
||||
|
||||
// AppContextDir generates a valid context config dir.
|
||||
func AppContextDir(cluster, context string) string {
|
||||
return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context))
|
||||
}
|
||||
|
||||
// AppContextAliasesFile generates a valid context specific aliases file path.
|
||||
func AppContextAliasesFile(cluster, context string) string {
|
||||
return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "aliases.yaml")
|
||||
}
|
||||
|
||||
// AppContextPluginsFile generates a valid context specific plugins file path.
|
||||
func AppContextPluginsFile(cluster, context string) string {
|
||||
return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "plugins.yaml")
|
||||
}
|
||||
|
||||
// AppContextHotkeysFile generates a valid context specific hotkeys file path.
|
||||
func AppContextHotkeysFile(cluster, context string) string {
|
||||
return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "hotkeys.yaml")
|
||||
}
|
||||
|
||||
// AppContextConfig generates a valid context config file path.
|
||||
func AppContextConfig(cluster, context string) string {
|
||||
return filepath.Join(AppContextDir(cluster, context), data.MainConfigFile)
|
||||
}
|
||||
|
||||
// DumpsDir generates a valid context dump directory.
|
||||
func DumpsDir(cluster, context string) (string, error) {
|
||||
dir := filepath.Join(AppDumpsDir, sanContextSubpath(cluster, context))
|
||||
|
||||
return dir, data.EnsureDirPath(dir, data.DefaultDirMod)
|
||||
}
|
||||
|
||||
// EnsureBenchmarksDir generates a valid benchmark results directory.
|
||||
func EnsureBenchmarksDir(cluster, context string) (string, error) {
|
||||
dir := filepath.Join(AppBenchmarksDir, sanContextSubpath(cluster, context))
|
||||
|
||||
return dir, data.EnsureDirPath(dir, data.DefaultDirMod)
|
||||
}
|
||||
|
||||
// EnsureBenchmarksCfgFile generates a valid benchmark file.
|
||||
func EnsureBenchmarksCfgFile(cluster, context string) (string, error) {
|
||||
f := filepath.Join(AppContextDir(cluster, context), "benchmarks.yaml")
|
||||
if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(f); os.IsNotExist(err) {
|
||||
return f, os.WriteFile(f, benchmarkTpl, data.DefaultFileMod)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// EnsureAliasesCfgFile generates a valid aliases file.
|
||||
func EnsureAliasesCfgFile() (string, error) {
|
||||
f := filepath.Join(AppConfigDir, "aliases.yaml")
|
||||
if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(f); os.IsNotExist(err) {
|
||||
return f, os.WriteFile(f, aliasesTpl, data.DefaultFileMod)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// EnsureHotkeysCfgFile generates a valid hotkeys file.
|
||||
func EnsureHotkeysCfgFile() (string, error) {
|
||||
f := filepath.Join(AppConfigDir, "hotkeys.yaml")
|
||||
if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(f); os.IsNotExist(err) {
|
||||
return f, os.WriteFile(f, hotkeysTpl, data.DefaultFileMod)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// SkinFileFromName generate skin file path from spec.
|
||||
func SkinFileFromName(n string) string {
|
||||
return filepath.Join(AppSkinsDir, n+".yaml")
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func sanContextSubpath(cluster, context string) string {
|
||||
return filepath.Join(SanitizeFileName(cluster), SanitizeFileName(context))
|
||||
}
|
||||
|
||||
func hasK9sConfigEnv() bool {
|
||||
return os.Getenv(K9sConfigDir) != ""
|
||||
}
|
||||
|
||||
func userTmpDir() (string, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir := filepath.Join(os.TempDir(), AppName, u.Username)
|
||||
if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnsureBenchmarkCfg(t *testing.T) {
|
||||
os.Setenv(config.K9sConfigDir, "/tmp/test-config")
|
||||
assert.NoError(t, config.InitLocs())
|
||||
defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir))
|
||||
|
||||
assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod))
|
||||
assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
|
||||
|
||||
uu := map[string]struct {
|
||||
cluster, context string
|
||||
f, e string
|
||||
}{
|
||||
"not-exist": {
|
||||
cluster: "cl-1",
|
||||
context: "ct-1",
|
||||
f: "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml",
|
||||
e: "benchmarks:\n defaults:\n concurrency: 2\n requests: 200",
|
||||
},
|
||||
"exist": {
|
||||
cluster: "cl-1",
|
||||
context: "ct-2",
|
||||
f: "/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
f, err := config.EnsureBenchmarksCfgFile(u.cluster, u.context)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, u.f, f)
|
||||
bb, err := os.ReadFile(f)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, u.e, string(bb))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,6 @@
|
|||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultRefreshRate represents the refresh interval.
|
||||
DefaultRefreshRate = 2 // secs
|
||||
|
|
@ -21,7 +15,7 @@ const (
|
|||
)
|
||||
|
||||
// DefaultLogFile represents the default K9s log file.
|
||||
var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser()))
|
||||
// var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser()))
|
||||
|
||||
// Flags represents K9s configuration flags.
|
||||
type Flags struct {
|
||||
|
|
@ -43,7 +37,7 @@ func NewFlags() *Flags {
|
|||
return &Flags{
|
||||
RefreshRate: intPtr(DefaultRefreshRate),
|
||||
LogLevel: strPtr(DefaultLogLevel),
|
||||
LogFile: strPtr(DefaultLogFile),
|
||||
LogFile: strPtr(AppLogFile),
|
||||
Headless: boolPtr(false),
|
||||
Logoless: boolPtr(false),
|
||||
Command: strPtr(DefaultCommand),
|
||||
|
|
@ -51,7 +45,7 @@ func NewFlags() *Flags {
|
|||
ReadOnly: boolPtr(false),
|
||||
Write: boolPtr(false),
|
||||
Crumbsless: boolPtr(false),
|
||||
ScreenDumpDir: strPtr(K9sDefaultScreenDumpDir),
|
||||
ScreenDumpDir: strPtr(AppDumpsDir),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,39 +4,18 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultDirMod default unix perms for k9s directory.
|
||||
DefaultDirMod os.FileMode = 0755
|
||||
// DefaultFileMod default unix perms for k9s files.
|
||||
DefaultFileMod os.FileMode = 0600
|
||||
)
|
||||
|
||||
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
|
||||
|
||||
// SanitizeFilename sanitizes the dump filename.
|
||||
func SanitizeFilename(name string) string {
|
||||
return invalidPathCharsRX.ReplaceAllString(name, "-")
|
||||
}
|
||||
|
||||
// InList check if string is in a collection of strings.
|
||||
func InList(ll []string, n string) bool {
|
||||
for _, l := range ll {
|
||||
if l == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InNSList check if ns is in an ns collection.
|
||||
func InNSList(nn []interface{}, ns string) bool {
|
||||
ss := make([]string, len(nn))
|
||||
|
|
@ -45,7 +24,7 @@ func InNSList(nn []interface{}, ns string) bool {
|
|||
ss[i] = nsp.Name
|
||||
}
|
||||
}
|
||||
return InList(ss, ns)
|
||||
return data.InList(ss, ns)
|
||||
}
|
||||
|
||||
// MustK9sUser establishes current user identity or fail.
|
||||
|
|
@ -57,22 +36,6 @@ func MustK9sUser() string {
|
|||
return usr.Username
|
||||
}
|
||||
|
||||
// EnsureDirPath ensures a directory exist from the given path.
|
||||
func EnsureDirPath(path string, mod os.FileMode) error {
|
||||
return EnsureFullPath(filepath.Dir(path), mod)
|
||||
}
|
||||
|
||||
// EnsureFullPath ensures a directory exist from the given path.
|
||||
func EnsureFullPath(path string, mod os.FileMode) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path, mod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBoolSet checks if a bool prt is set.
|
||||
func IsBoolSet(b *bool) bool {
|
||||
return b != nil && *b
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
|
|
@ -14,24 +12,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestHelperInList(t *testing.T) {
|
||||
uu := []struct {
|
||||
item string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"a", []string{}, false},
|
||||
{"", []string{}, false},
|
||||
{"", []string{""}, true},
|
||||
{"a", []string{"a", "b", "c", "d"}, true},
|
||||
{"z", []string{"a", "b", "c", "d"}, false},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.expected, config.InList(u.list, u.item))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperInNSList(t *testing.T) {
|
||||
uu := []struct {
|
||||
item string
|
||||
|
|
@ -58,30 +38,3 @@ func TestHelperInNSList(t *testing.T) {
|
|||
assert.Equal(t, u.expected, config.InNSList(u.list, u.item))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureDirPathNone(t *testing.T) {
|
||||
var mod os.FileMode = 0744
|
||||
dir := filepath.Join("/tmp", "fred")
|
||||
os.Remove(dir)
|
||||
|
||||
path := filepath.Join(dir, "duh.yml")
|
||||
assert.NoError(t, config.EnsureDirPath(path, mod))
|
||||
|
||||
p, err := os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||
}
|
||||
|
||||
func TestEnsureDirPathNoOpt(t *testing.T) {
|
||||
var mod os.FileMode = 0744
|
||||
dir := filepath.Join("/tmp", "blee")
|
||||
os.Remove(dir)
|
||||
assert.NoError(t, os.Mkdir(dir, mod))
|
||||
|
||||
path := filepath.Join(dir, "duh.yml")
|
||||
assert.NoError(t, config.EnsureDirPath(path, mod))
|
||||
|
||||
p, err := os.Stat(dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "drwxr--r--", p.Mode().String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,13 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sHotKeys manages K9s hotKeys.
|
||||
var K9sHotKeys = YamlExtension(filepath.Join(K9sHome(), "hotkey.yml"))
|
||||
|
||||
// HotKeys represents a collection of plugins.
|
||||
type HotKeys struct {
|
||||
HotKey map[string]HotKey `yaml:"hotKey"`
|
||||
HotKey map[string]HotKey `yaml:"hotKeys"`
|
||||
}
|
||||
|
||||
// HotKey describes a K9s hotkey.
|
||||
|
|
@ -34,7 +30,7 @@ func NewHotKeys() HotKeys {
|
|||
|
||||
// Load K9s plugins.
|
||||
func (h HotKeys) Load() error {
|
||||
return h.LoadHotKeys(K9sHotKeys)
|
||||
return h.LoadHotKeys(AppHotKeysFile)
|
||||
}
|
||||
|
||||
// LoadHotKeys loads plugins from a given file.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
func TestHotKeyLoad(t *testing.T) {
|
||||
h := config.NewHotKeys()
|
||||
assert.Nil(t, h.LoadHotKeys("testdata/hot_key.yml"))
|
||||
assert.Nil(t, h.LoadHotKeys("testdata/hotkeys.yaml"))
|
||||
|
||||
assert.Equal(t, 1, len(h.HotKey))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,37 +4,28 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
)
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
const (
|
||||
defaultRefreshRate = 2
|
||||
defaultMaxConnRetry = 5
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
)
|
||||
|
||||
// K9s tracks K9s configuration options.
|
||||
type K9s struct {
|
||||
LiveViewAutoRefresh bool `yaml:"liveViewAutoRefresh"`
|
||||
RefreshRate int `yaml:"refreshRate"`
|
||||
MaxConnRetry int `yaml:"maxConnRetry"`
|
||||
EnableMouse bool `yaml:"enableMouse"`
|
||||
EnableImageScan bool `yaml:"enableImageScan"`
|
||||
Headless bool `yaml:"headless"`
|
||||
Logoless bool `yaml:"logoless"`
|
||||
Crumbsless bool `yaml:"crumbsless"`
|
||||
ReadOnly bool `yaml:"readOnly"`
|
||||
NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"`
|
||||
NoIcons bool `yaml:"noIcons"`
|
||||
ShellPod *ShellPod `yaml:"shellPod"`
|
||||
SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"`
|
||||
Logger *Logger `yaml:"logger"`
|
||||
CurrentContext string `yaml:"currentContext"`
|
||||
CurrentCluster string `yaml:"currentCluster"`
|
||||
KeepMissingClusters bool `yaml:"keepMissingClusters"`
|
||||
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
||||
Thresholds Threshold `yaml:"thresholds"`
|
||||
ScreenDumpDir string `yaml:"screenDumpDir"`
|
||||
DisablePodCounting bool `yaml:"disablePodCounting"`
|
||||
LiveViewAutoRefresh bool `yaml:"liveViewAutoRefresh"`
|
||||
ScreenDumpDir string `yaml:"screenDumpDir,omitempty"`
|
||||
RefreshRate int `yaml:"refreshRate"`
|
||||
MaxConnRetry int `yaml:"maxConnRetry"`
|
||||
ReadOnly bool `yaml:"readOnly"`
|
||||
NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"`
|
||||
UI UI `yaml:"ui"`
|
||||
SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"`
|
||||
DisablePodCounting bool `yaml:"disablePodCounting"`
|
||||
ShellPod *ShellPod `yaml:"shellPod"`
|
||||
ImageScans *ImageScans `yaml:"imageScans"`
|
||||
Logger *Logger `yaml:"logger"`
|
||||
Thresholds Threshold `yaml:"thresholds"`
|
||||
manualRefreshRate int
|
||||
manualHeadless *bool
|
||||
manualLogoless *bool
|
||||
|
|
@ -42,36 +33,151 @@ type K9s struct {
|
|||
manualReadOnly *bool
|
||||
manualCommand *string
|
||||
manualScreenDumpDir *string
|
||||
dir *data.Dir
|
||||
activeContextName string
|
||||
activeConfig *data.Config
|
||||
conn client.Connection
|
||||
ks data.KubeSettings
|
||||
}
|
||||
|
||||
// NewK9s create a new K9s configuration.
|
||||
func NewK9s() *K9s {
|
||||
func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
|
||||
return &K9s{
|
||||
RefreshRate: defaultRefreshRate,
|
||||
MaxConnRetry: defaultMaxConnRetry,
|
||||
ScreenDumpDir: AppDumpsDir,
|
||||
Logger: NewLogger(),
|
||||
Clusters: make(map[string]*Cluster),
|
||||
Thresholds: NewThreshold(),
|
||||
ScreenDumpDir: K9sDefaultScreenDumpDir,
|
||||
ShellPod: NewShellPod(),
|
||||
ImageScans: NewImageScans(),
|
||||
dir: data.NewDir(AppContextsDir, conn, ks),
|
||||
conn: conn,
|
||||
ks: ks,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *K9s) CurrentContextDir() string {
|
||||
return SanitizeFilename(k.CurrentContext)
|
||||
func (k *K9s) Save() error {
|
||||
if k.activeConfig != nil {
|
||||
path := filepath.Join(
|
||||
AppContextsDir,
|
||||
k.activeConfig.Context.ClusterName,
|
||||
k.activeContextName,
|
||||
data.MainConfigFile,
|
||||
)
|
||||
return k.activeConfig.Save(path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActivateCluster initializes the active cluster is not present.
|
||||
func (k *K9s) ActivateCluster(ns string) {
|
||||
if k.Clusters == nil {
|
||||
k.Clusters = map[string]*Cluster{}
|
||||
func (k *K9s) Refine(k1 *K9s) {
|
||||
k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh
|
||||
k.ScreenDumpDir = k1.ScreenDumpDir
|
||||
k.RefreshRate = k1.RefreshRate
|
||||
k.MaxConnRetry = k1.MaxConnRetry
|
||||
k.ReadOnly = k1.ReadOnly
|
||||
k.NoExitOnCtrlC = k1.NoExitOnCtrlC
|
||||
k.UI = k1.UI
|
||||
k.SkipLatestRevCheck = k1.SkipLatestRevCheck
|
||||
k.DisablePodCounting = k1.DisablePodCounting
|
||||
k.ShellPod = k1.ShellPod
|
||||
k.ImageScans = k1.ImageScans
|
||||
k.Logger = k1.Logger
|
||||
k.Thresholds = k1.Thresholds
|
||||
}
|
||||
|
||||
func (k *K9s) Generate(k9sFlags *Flags) {
|
||||
if *k9sFlags.RefreshRate != DefaultRefreshRate {
|
||||
k.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
||||
}
|
||||
if _, ok := k.Clusters[k.CurrentCluster]; ok {
|
||||
return
|
||||
|
||||
k.OverrideHeadless(*k9sFlags.Headless)
|
||||
k.OverrideLogoless(*k9sFlags.Logoless)
|
||||
k.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
||||
k.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||
k.OverrideWrite(*k9sFlags.Write)
|
||||
k.OverrideCommand(*k9sFlags.Command)
|
||||
k.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir)
|
||||
}
|
||||
|
||||
// OverrideScreenDumpDir set the screen dump dir manually.
|
||||
func (k *K9s) OverrideScreenDumpDir(dir string) {
|
||||
k.manualScreenDumpDir = &dir
|
||||
}
|
||||
|
||||
func (k *K9s) GetScreenDumpDir() string {
|
||||
screenDumpDir := k.ScreenDumpDir
|
||||
if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
|
||||
screenDumpDir = *k.manualScreenDumpDir
|
||||
}
|
||||
cl := NewCluster()
|
||||
cl.Namespace.Active = ns
|
||||
k.Clusters[k.CurrentCluster] = cl
|
||||
if screenDumpDir == "" {
|
||||
screenDumpDir = AppDumpsDir
|
||||
}
|
||||
|
||||
return screenDumpDir
|
||||
}
|
||||
|
||||
func (k *K9s) Reset() {
|
||||
k.activeConfig, k.activeContextName = nil, ""
|
||||
}
|
||||
|
||||
func (k *K9s) ActiveContextDir() string {
|
||||
if k.activeConfig == nil {
|
||||
return "na"
|
||||
}
|
||||
|
||||
return filepath.Join(
|
||||
SanitizeFileName(k.activeConfig.Context.ClusterName),
|
||||
SanitizeFileName(k.ActiveContextName()),
|
||||
)
|
||||
}
|
||||
|
||||
func (k *K9s) ActiveContextNamespace() (string, error) {
|
||||
if k.activeConfig != nil {
|
||||
return k.activeConfig.Context.Namespace.Active, nil
|
||||
}
|
||||
|
||||
return "", errors.New("context config is not set")
|
||||
}
|
||||
|
||||
func (k *K9s) ActiveContextName() string {
|
||||
return k.activeContextName
|
||||
}
|
||||
|
||||
// ActiveContext returns the currently active context.
|
||||
func (k *K9s) ActiveContext() (*data.Context, error) {
|
||||
if k.activeConfig != nil {
|
||||
return k.activeConfig.Context, nil
|
||||
}
|
||||
|
||||
ct, err := k.ActivateContext(k.activeContextName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// ActivateContext initializes the active context is not present.
|
||||
func (k *K9s) ActivateContext(n string) (*data.Context, error) {
|
||||
k.activeContextName = n
|
||||
ct, err := k.ks.GetContext(k.activeContextName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := k.dir.Load(n, ct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.activeConfig = cfg
|
||||
// If the context specifies a default namespace, use it!
|
||||
if k.conn != nil {
|
||||
if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace {
|
||||
k.activeConfig.Context.Namespace.Active = ns
|
||||
}
|
||||
}
|
||||
|
||||
return cfg.Context, nil
|
||||
}
|
||||
|
||||
// OverrideRefreshRate set the refresh rate manually.
|
||||
|
|
@ -114,14 +220,9 @@ func (k *K9s) OverrideCommand(cmd string) {
|
|||
k.manualCommand = &cmd
|
||||
}
|
||||
|
||||
// OverrideScreenDumpDir set the screen dump dir manually.
|
||||
func (k *K9s) OverrideScreenDumpDir(dir string) {
|
||||
k.manualScreenDumpDir = &dir
|
||||
}
|
||||
|
||||
// IsHeadless returns headless setting.
|
||||
func (k *K9s) IsHeadless() bool {
|
||||
h := k.Headless
|
||||
h := k.UI.Headless
|
||||
if k.manualHeadless != nil && *k.manualHeadless {
|
||||
h = *k.manualHeadless
|
||||
}
|
||||
|
|
@ -131,7 +232,7 @@ func (k *K9s) IsHeadless() bool {
|
|||
|
||||
// IsLogoless returns logoless setting.
|
||||
func (k *K9s) IsLogoless() bool {
|
||||
h := k.Logoless
|
||||
h := k.UI.Logoless
|
||||
if k.manualLogoless != nil && *k.manualLogoless {
|
||||
h = *k.manualLogoless
|
||||
}
|
||||
|
|
@ -141,7 +242,7 @@ func (k *K9s) IsLogoless() bool {
|
|||
|
||||
// IsCrumbsless returns crumbsless setting.
|
||||
func (k *K9s) IsCrumbsless() bool {
|
||||
h := k.Crumbsless
|
||||
h := k.UI.Crumbsless
|
||||
if k.manualCrumbsless != nil && *k.manualCrumbsless {
|
||||
h = *k.manualCrumbsless
|
||||
}
|
||||
|
|
@ -165,36 +266,13 @@ func (k *K9s) IsReadOnly() bool {
|
|||
if k.manualReadOnly != nil {
|
||||
readOnly = *k.manualReadOnly
|
||||
}
|
||||
if k.activeConfig != nil && k.activeConfig.Context.ReadOnly {
|
||||
readOnly = true
|
||||
}
|
||||
|
||||
return readOnly
|
||||
}
|
||||
|
||||
// ActiveCluster returns the currently active cluster.
|
||||
func (k *K9s) ActiveCluster() *Cluster {
|
||||
if k.Clusters == nil {
|
||||
k.Clusters = map[string]*Cluster{}
|
||||
}
|
||||
if c, ok := k.Clusters[k.CurrentCluster]; ok {
|
||||
return c
|
||||
}
|
||||
k.Clusters[k.CurrentCluster] = NewCluster()
|
||||
|
||||
return k.Clusters[k.CurrentCluster]
|
||||
}
|
||||
|
||||
func (k *K9s) GetScreenDumpDir() string {
|
||||
screenDumpDir := k.ScreenDumpDir
|
||||
if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
|
||||
screenDumpDir = *k.manualScreenDumpDir
|
||||
}
|
||||
|
||||
if screenDumpDir == "" {
|
||||
return K9sDefaultScreenDumpDir
|
||||
}
|
||||
|
||||
return screenDumpDir
|
||||
}
|
||||
|
||||
func (k *K9s) validateDefaults() {
|
||||
if k.RefreshRate <= 0 {
|
||||
k.RefreshRate = defaultRefreshRate
|
||||
|
|
@ -202,44 +280,19 @@ func (k *K9s) validateDefaults() {
|
|||
if k.MaxConnRetry <= 0 {
|
||||
k.MaxConnRetry = defaultMaxConnRetry
|
||||
}
|
||||
if k.ScreenDumpDir == "" {
|
||||
k.ScreenDumpDir = K9sDefaultScreenDumpDir
|
||||
}
|
||||
}
|
||||
|
||||
func (k *K9s) validateClusters(c client.Connection, ks KubeSettings) {
|
||||
cc, err := ks.ClusterNames()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for key, cluster := range k.Clusters {
|
||||
cluster.Validate(c, ks)
|
||||
// if the cluster is defined in the $KUBECONFIG file, keep it in the k9s config file
|
||||
if _, ok := cc[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we asked to keep the clusters in the config file
|
||||
if k.KeepMissingClusters {
|
||||
continue
|
||||
}
|
||||
|
||||
// else remove it from the k9s config file
|
||||
if k.CurrentCluster == key {
|
||||
k.CurrentCluster = ""
|
||||
}
|
||||
delete(k.Clusters, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the current configuration.
|
||||
func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
|
||||
func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) {
|
||||
k.validateDefaults()
|
||||
if k.Clusters == nil {
|
||||
k.Clusters = map[string]*Cluster{}
|
||||
if k.activeConfig == nil {
|
||||
if n, err := ks.CurrentContextName(); err == nil {
|
||||
_, _ = k.ActivateContext(n)
|
||||
}
|
||||
}
|
||||
if k.ImageScans == nil {
|
||||
k.ImageScans = NewImageScans()
|
||||
}
|
||||
k.validateClusters(c, ks)
|
||||
|
||||
if k.ShellPod == nil {
|
||||
k.ShellPod = NewShellPod()
|
||||
}
|
||||
|
|
@ -254,18 +307,4 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
|
|||
k.Thresholds = NewThreshold()
|
||||
}
|
||||
k.Thresholds.Validate(c, ks)
|
||||
|
||||
if context, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 {
|
||||
k.CurrentContext = context
|
||||
k.CurrentCluster = ""
|
||||
}
|
||||
|
||||
if cl, err := ks.CurrentClusterName(); err == nil && len(k.CurrentCluster) == 0 {
|
||||
k.CurrentCluster = cl
|
||||
}
|
||||
|
||||
if _, ok := k.Clusters[k.CurrentCluster]; !ok {
|
||||
k.Clusters[k.CurrentCluster] = NewCluster()
|
||||
}
|
||||
k.Clusters[k.CurrentCluster].Validate(c, ks)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,165 +7,37 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/derailed/k9s/internal/config/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsReadOnly(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
config string
|
||||
read, write bool
|
||||
readOnly bool
|
||||
}{
|
||||
"writable": {
|
||||
config: "k9s.yml",
|
||||
},
|
||||
"writable_read_override": {
|
||||
config: "k9s.yml",
|
||||
read: true,
|
||||
readOnly: true,
|
||||
},
|
||||
"writable_write_override": {
|
||||
config: "k9s.yml",
|
||||
write: true,
|
||||
},
|
||||
"readonly": {
|
||||
config: "k9s_readonly.yml",
|
||||
readOnly: true,
|
||||
},
|
||||
"readonly_read_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
read: true,
|
||||
readOnly: true,
|
||||
},
|
||||
"readonly_write_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
write: true,
|
||||
},
|
||||
"readonly_both_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
read: true,
|
||||
write: true,
|
||||
},
|
||||
}
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Nil(t, cfg.Load("testdata/"+u.config))
|
||||
cfg.K9s.OverrideReadOnly(u.read)
|
||||
cfg.K9s.OverrideWrite(u.write)
|
||||
assert.Equal(t, u.readOnly, cfg.K9s.IsReadOnly())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestK9sValidate(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
|
||||
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
|
||||
m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil)
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
|
||||
|
||||
c := config.NewK9s()
|
||||
c.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, 2, c.RefreshRate)
|
||||
assert.Equal(t, int64(100), c.Logger.TailCount)
|
||||
assert.Equal(t, 5000, c.Logger.BufferSize)
|
||||
assert.Equal(t, "ctx1", c.CurrentContext)
|
||||
assert.Equal(t, "c1", c.CurrentCluster)
|
||||
assert.Equal(t, 1, len(c.Clusters))
|
||||
assert.Equal(t, config.K9sDefaultScreenDumpDir, c.GetScreenDumpDir())
|
||||
_, ok := c.Clusters[c.CurrentCluster]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestK9sValidateBlank(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
|
||||
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
|
||||
m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil)
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
|
||||
|
||||
var c config.K9s
|
||||
c.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, 2, c.RefreshRate)
|
||||
assert.Equal(t, int64(100), c.Logger.TailCount)
|
||||
assert.Equal(t, 5000, c.Logger.BufferSize)
|
||||
assert.Equal(t, "ctx1", c.CurrentContext)
|
||||
assert.Equal(t, "c1", c.CurrentCluster)
|
||||
assert.Equal(t, 1, len(c.Clusters))
|
||||
_, ok := c.Clusters[c.CurrentCluster]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestK9sActiveClusterZero(t *testing.T) {
|
||||
c := config.NewK9s()
|
||||
c.CurrentCluster = "fred"
|
||||
cl := c.ActiveCluster()
|
||||
assert.NotNil(t, cl)
|
||||
assert.Equal(t, "default", cl.Namespace.Active)
|
||||
assert.Equal(t, 1, len(cl.Namespace.Favorites))
|
||||
}
|
||||
|
||||
func TestK9sActiveClusterBlank(t *testing.T) {
|
||||
var c config.K9s
|
||||
cl := c.ActiveCluster()
|
||||
assert.Equal(t, config.NewCluster(), cl)
|
||||
}
|
||||
|
||||
func TestK9sActiveCluster(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
|
||||
cl := cfg.K9s.ActiveCluster()
|
||||
assert.NotNil(t, cl)
|
||||
assert.Equal(t, "kube-system", cl.Namespace.Active)
|
||||
assert.Equal(t, 5, len(cl.Namespace.Favorites))
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDir(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirOverride(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("/override")
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("/override")
|
||||
assert.Equal(t, "/override", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirOverrideEmpty(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yaml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirEmpty(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s1.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
cfg := mock.NewMockConfig()
|
||||
|
||||
assert.Equal(t, config.K9sDefaultScreenDumpDir, cfg.K9s.GetScreenDumpDir())
|
||||
assert.Nil(t, cfg.Load("testdata/k9s1.yaml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
assert.Equal(t, config.AppDumpsDir, cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package config
|
|||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -38,7 +39,7 @@ func NewLogger() *Logger {
|
|||
}
|
||||
|
||||
// Validate checks thresholds and make sure we're cool. If not use defaults.
|
||||
func (l *Logger) Validate(_ client.Connection, _ KubeSettings) {
|
||||
func (l *Logger) Validate(_ client.Connection, _ data.KubeSettings) {
|
||||
if l.TailCount <= 0 {
|
||||
l.TailCount = DefaultLoggerTailCount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
version "k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
disk "k8s.io/client-go/discovery/cached/disk"
|
||||
dynamic "k8s.io/client-go/dynamic"
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
func EnsureDir(d string) error {
|
||||
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||
return os.MkdirAll(d, 0700)
|
||||
}
|
||||
if err := os.RemoveAll(d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.MkdirAll(d, 0700)
|
||||
}
|
||||
|
||||
func NewMockConfig() *config.Config {
|
||||
config.AppContextsDir = "/tmp/test"
|
||||
cl, ct := "cl-1", "ct-1"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
ClusterName: &cl,
|
||||
Context: &ct,
|
||||
}
|
||||
cfg := config.NewConfig(
|
||||
NewMockKubeSettings(&flags),
|
||||
)
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type mockKubeSettings struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
cts map[string]*api.Context
|
||||
}
|
||||
|
||||
func NewMockKubeSettings(f *genericclioptions.ConfigFlags) mockKubeSettings {
|
||||
_, idx, _ := strings.Cut(*f.ClusterName, "-")
|
||||
ctId := "ct-" + idx
|
||||
|
||||
return mockKubeSettings{
|
||||
flags: f,
|
||||
cts: map[string]*api.Context{
|
||||
ctId + "-1": {
|
||||
Cluster: *f.ClusterName,
|
||||
Namespace: "",
|
||||
},
|
||||
ctId + "-2": {
|
||||
Cluster: *f.ClusterName,
|
||||
Namespace: "ns-1",
|
||||
},
|
||||
ctId + "-3": {
|
||||
Cluster: *f.ClusterName,
|
||||
Namespace: client.DefaultNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
func (m mockKubeSettings) CurrentContextName() (string, error) {
|
||||
return *m.flags.Context, nil
|
||||
}
|
||||
func (m mockKubeSettings) CurrentClusterName() (string, error) {
|
||||
return *m.flags.ClusterName, nil
|
||||
}
|
||||
func (m mockKubeSettings) CurrentNamespaceName() (string, error) {
|
||||
return "default", nil
|
||||
}
|
||||
func (m mockKubeSettings) GetContext(s string) (*api.Context, error) {
|
||||
ct, ok := m.cts[s]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no context found for: %q", s)
|
||||
}
|
||||
return ct, nil
|
||||
}
|
||||
func (m mockKubeSettings) CurrentContext() (*api.Context, error) {
|
||||
return m.GetContext(*m.flags.Context)
|
||||
}
|
||||
func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) {
|
||||
mm := make(map[string]struct{}, len(m.cts))
|
||||
for k := range m.cts {
|
||||
mm[k] = struct{}{}
|
||||
}
|
||||
|
||||
return mm, nil
|
||||
}
|
||||
|
||||
type mockConnection struct{}
|
||||
|
||||
func NewMockConnection() mockConnection {
|
||||
return mockConnection{}
|
||||
}
|
||||
func (m mockConnection) CanI(ns, gvr string, verbs []string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
func (m mockConnection) Config() *client.Config {
|
||||
return nil
|
||||
}
|
||||
func (m mockConnection) ConnectionOK() bool {
|
||||
return false
|
||||
}
|
||||
func (m mockConnection) Dial() (kubernetes.Interface, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) DialLogs() (kubernetes.Interface, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) SwitchContext(ctx string) error {
|
||||
return nil
|
||||
}
|
||||
func (m mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) RestConfig() (*restclient.Config, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) MXDial() (*versioned.Clientset, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) DynDial() (dynamic.Interface, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) HasMetrics() bool {
|
||||
return false
|
||||
}
|
||||
func (m mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) IsValidNamespace(string) bool {
|
||||
return true
|
||||
}
|
||||
func (m mockConnection) ServerVersion() (*version.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m mockConnection) CheckConnectivity() bool {
|
||||
return false
|
||||
}
|
||||
func (m mockConnection) ActiveContext() string {
|
||||
return ""
|
||||
}
|
||||
func (m mockConnection) ActiveNamespace() string {
|
||||
return ""
|
||||
}
|
||||
func (m mockConnection) IsActiveNamespace(string) bool {
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,674 +0,0 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/derailed/k9s/internal/client (interfaces: Connection)
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
client "github.com/derailed/k9s/internal/client"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
version "k8s.io/apimachinery/pkg/version"
|
||||
disk "k8s.io/client-go/discovery/cached/disk"
|
||||
dynamic "k8s.io/client-go/dynamic"
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
rest "k8s.io/client-go/rest"
|
||||
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockConnection struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockConnection(options ...pegomock.Option) *MockConnection {
|
||||
mock := &MockConnection{}
|
||||
for _, option := range options {
|
||||
option.Apply(mock)
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockConnection) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockConnection) ActiveCluster() string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveCluster", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
|
||||
var ret0 string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ActiveNamespace() string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveNamespace", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
|
||||
var ret0 string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CachedDiscovery", params, []reflect.Type{reflect.TypeOf((**disk.CachedDiscoveryClient)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 *disk.CachedDiscoveryClient
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*disk.CachedDiscoveryClient)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) CanI(_param0 string, _param1 string, _param2 []string) (bool, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1, _param2}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CanI", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) CheckConnectivity() bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CheckConnectivity", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) Config() *client.Config {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**client.Config)(nil)).Elem()})
|
||||
var ret0 *client.Config
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*client.Config)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ConnectionOK() bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ConnectionOK", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) Dial() (kubernetes.Interface, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Dial", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 kubernetes.Interface
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(kubernetes.Interface)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) DialLogs() (kubernetes.Interface, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("DialLogs", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 kubernetes.Interface
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(kubernetes.Interface)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) DynDial() (dynamic.Interface, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("DynDial", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 dynamic.Interface
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(dynamic.Interface)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) HasMetrics() bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) IsActiveNamespace(_param0 string) bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("IsActiveNamespace", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) MXDial() (*versioned.Clientset, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 *versioned.Clientset
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*versioned.Clientset)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) RestConfig() (*rest.Config, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfig", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 *rest.Config
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*rest.Config)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ServerVersion() (*version.Info, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ServerVersion", params, []reflect.Type{reflect.TypeOf((**version.Info)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 *version.Info
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*version.Info)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SwitchContext(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 []v1.Namespace
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].([]v1.Namespace)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) VerifyWasCalledOnce() *VerifierMockConnection {
|
||||
return &VerifierMockConnection{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockConnection) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockConnection {
|
||||
return &VerifierMockConnection{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockConnection) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockConnection {
|
||||
return &VerifierMockConnection{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockConnection) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockConnection {
|
||||
return &VerifierMockConnection{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierMockConnection struct {
|
||||
mock *MockConnection
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) ActiveCluster() *MockConnection_ActiveCluster_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveCluster", params, verifier.timeout)
|
||||
return &MockConnection_ActiveCluster_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_ActiveCluster_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_ActiveCluster_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_ActiveCluster_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) ActiveNamespace() *MockConnection_ActiveNamespace_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveNamespace", params, verifier.timeout)
|
||||
return &MockConnection_ActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_ActiveNamespace_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_ActiveNamespace_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_ActiveNamespace_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) CachedDiscovery() *MockConnection_CachedDiscovery_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CachedDiscovery", params, verifier.timeout)
|
||||
return &MockConnection_CachedDiscovery_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_CachedDiscovery_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_CachedDiscovery_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_CachedDiscovery_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) CanI(_param0 string, _param1 string, _param2 []string) *MockConnection_CanI_OngoingVerification {
|
||||
params := []pegomock.Param{_param0, _param1, _param2}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanI", params, verifier.timeout)
|
||||
return &MockConnection_CanI_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_CanI_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_CanI_OngoingVerification) GetCapturedArguments() (string, string, []string) {
|
||||
_param0, _param1, _param2 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1]
|
||||
}
|
||||
|
||||
func (c *MockConnection_CanI_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
_param2 = make([][]string, len(c.methodInvocations))
|
||||
for u, param := range params[2] {
|
||||
_param2[u] = param.([]string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) CheckConnectivity() *MockConnection_CheckConnectivity_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CheckConnectivity", params, verifier.timeout)
|
||||
return &MockConnection_CheckConnectivity_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_CheckConnectivity_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_CheckConnectivity_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_CheckConnectivity_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) Config() *MockConnection_Config_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout)
|
||||
return &MockConnection_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_Config_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_Config_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_Config_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) ConnectionOK() *MockConnection_ConnectionOK_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ConnectionOK", params, verifier.timeout)
|
||||
return &MockConnection_ConnectionOK_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_ConnectionOK_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_ConnectionOK_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_ConnectionOK_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) Dial() *MockConnection_Dial_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Dial", params, verifier.timeout)
|
||||
return &MockConnection_Dial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_Dial_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_Dial_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_Dial_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) DynDial() *MockConnection_DynDial_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDial", params, verifier.timeout)
|
||||
return &MockConnection_DynDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_DynDial_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_DynDial_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_DynDial_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout)
|
||||
return &MockConnection_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_HasMetrics_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_HasMetrics_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_HasMetrics_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) IsActiveNamespace(_param0 string) *MockConnection_IsActiveNamespace_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "IsActiveNamespace", params, verifier.timeout)
|
||||
return &MockConnection_IsActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_IsActiveNamespace_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) MXDial() *MockConnection_MXDial_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout)
|
||||
return &MockConnection_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_MXDial_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_MXDial_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_MXDial_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) RestConfig() *MockConnection_RestConfig_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfig", params, verifier.timeout)
|
||||
return &MockConnection_RestConfig_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_RestConfig_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_RestConfig_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_RestConfig_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) ServerVersion() *MockConnection_ServerVersion_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ServerVersion", params, verifier.timeout)
|
||||
return &MockConnection_ServerVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_ServerVersion_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_ServerVersion_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_ServerVersion_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) SwitchContext(_param0 string) *MockConnection_SwitchContext_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContext", params, verifier.timeout)
|
||||
return &MockConnection_SwitchContext_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_SwitchContext_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_SwitchContext_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *MockConnection_SwitchContext_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockConnection) ValidNamespaces() *MockConnection_ValidNamespaces_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout)
|
||||
return &MockConnection_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockConnection_ValidNamespaces_OngoingVerification struct {
|
||||
mock *MockConnection
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockConnection_ValidNamespaces_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockConnection_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/derailed/k9s/internal/config (interfaces: KubeSettings)
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockKubeSettings struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockKubeSettings(options ...pegomock.Option) *MockKubeSettings {
|
||||
mock := &MockKubeSettings{}
|
||||
for _, option := range options {
|
||||
option.Apply(mock)
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockKubeSettings) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockKubeSettings) ClusterNames() (map[string]struct{}, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterNames", params, []reflect.Type{reflect.TypeOf((*map[string]struct{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 map[string]struct{}
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(map[string]struct{})
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) CurrentClusterName() (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentClusterName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) CurrentContextName() (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentContextName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) CurrentNamespaceName() (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentNamespaceName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) NamespaceNames(_param0 []v1.Namespace) []string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()})
|
||||
var ret0 []string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].([]string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) VerifyWasCalledOnce() *VerifierMockKubeSettings {
|
||||
return &VerifierMockKubeSettings{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockKubeSettings {
|
||||
return &VerifierMockKubeSettings{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockKubeSettings {
|
||||
return &VerifierMockKubeSettings{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockKubeSettings) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockKubeSettings {
|
||||
return &VerifierMockKubeSettings{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierMockKubeSettings struct {
|
||||
mock *MockKubeSettings
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockKubeSettings) ClusterNames() *MockKubeSettings_ClusterNames_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterNames", params, verifier.timeout)
|
||||
return &MockKubeSettings_ClusterNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockKubeSettings_ClusterNames_OngoingVerification struct {
|
||||
mock *MockKubeSettings
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_ClusterNames_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_ClusterNames_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockKubeSettings) CurrentClusterName() *MockKubeSettings_CurrentClusterName_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentClusterName", params, verifier.timeout)
|
||||
return &MockKubeSettings_CurrentClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockKubeSettings_CurrentClusterName_OngoingVerification struct {
|
||||
mock *MockKubeSettings
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentClusterName_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentClusterName_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockKubeSettings) CurrentContextName() *MockKubeSettings_CurrentContextName_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentContextName", params, verifier.timeout)
|
||||
return &MockKubeSettings_CurrentContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockKubeSettings_CurrentContextName_OngoingVerification struct {
|
||||
mock *MockKubeSettings
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentContextName_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentContextName_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockKubeSettings) CurrentNamespaceName() *MockKubeSettings_CurrentNamespaceName_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
|
||||
return &MockKubeSettings_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockKubeSettings_CurrentNamespaceName_OngoingVerification struct {
|
||||
mock *MockKubeSettings
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockKubeSettings) NamespaceNames(_param0 []v1.Namespace) *MockKubeSettings_NamespaceNames_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout)
|
||||
return &MockKubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockKubeSettings_NamespaceNames_OngoingVerification struct {
|
||||
mock *MockKubeSettings
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_NamespaceNames_OngoingVerification) GetCapturedArguments() []v1.Namespace {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *MockKubeSettings_NamespaceNames_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Namespace) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([][]v1.Namespace, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.([]v1.Namespace)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNSValidate(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"})
|
||||
|
||||
ns := config.NewNamespace()
|
||||
ns.Validate(mc, mk)
|
||||
|
||||
mk.VerifyWasCalledOnce()
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSValidateMissing(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
mk := NewMockKubeSettings()
|
||||
|
||||
ns := config.NewNamespace()
|
||||
ns.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSValidateNoNS(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), fmt.Errorf("Crap!"))
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2"})
|
||||
|
||||
ns := config.NewNamespace()
|
||||
ns.Validate(mc, mk)
|
||||
|
||||
mk.VerifyWasCalledOnce()
|
||||
assert.Equal(t, "default", ns.Active)
|
||||
assert.Equal(t, []string{"default"}, ns.Favorites)
|
||||
}
|
||||
|
||||
func TestNSSetActive(t *testing.T) {
|
||||
allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"}
|
||||
uu := []struct {
|
||||
ns string
|
||||
fav []string
|
||||
}{
|
||||
{"all", []string{"all", "default"}},
|
||||
{"ns1", []string{"ns1", "all", "default"}},
|
||||
{"ns2", []string{"ns2", "ns1", "all", "default"}},
|
||||
{"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}},
|
||||
{"ns4", allNS},
|
||||
}
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS)
|
||||
|
||||
ns := config.NewNamespace()
|
||||
for _, u := range uu {
|
||||
err := ns.SetActive(u.ns, mk)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, u.ns, ns.Active)
|
||||
assert.Equal(t, u.fav, ns.Favorites)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSValidateRmFavs(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
mk := NewMockKubeSettings()
|
||||
|
||||
ns := config.NewNamespace()
|
||||
ns.Favorites = []string{"default", "fred", "blee"}
|
||||
ns.Validate(mc, mk)
|
||||
|
||||
assert.Equal(t, []string{"default", "fred"}, ns.Favorites)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -13,13 +14,11 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sPluginsFilePath manages K9s plugins.
|
||||
var K9sPluginsFilePath = YamlExtension(filepath.Join(K9sHome(), "plugin.yml"))
|
||||
var K9sPluginDirectory = filepath.Join("k9s", "plugins")
|
||||
const k9sPluginsDir = "k9s/plugins"
|
||||
|
||||
// Plugins represents a collection of plugins.
|
||||
type Plugins struct {
|
||||
Plugin map[string]Plugin `yaml:"plugin"`
|
||||
Plugins map[string]Plugin `yaml:"plugins"`
|
||||
}
|
||||
|
||||
// Plugin describes a K9s plugin.
|
||||
|
|
@ -41,22 +40,58 @@ func (p Plugin) String() string {
|
|||
// NewPlugins returns a new plugin.
|
||||
func NewPlugins() Plugins {
|
||||
return Plugins{
|
||||
Plugin: make(map[string]Plugin),
|
||||
Plugins: make(map[string]Plugin),
|
||||
}
|
||||
}
|
||||
|
||||
// Load K9s plugins.
|
||||
func (p Plugins) Load() error {
|
||||
pluginDirs := make([]string, 0, len(xdg.DataDirs))
|
||||
for _, dataDir := range xdg.DataDirs {
|
||||
pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory))
|
||||
func (p Plugins) Load(path string) error {
|
||||
var errs error
|
||||
if err := p.load(AppPluginsFile); err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
if err := p.load(path); err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
return p.LoadPlugins(K9sPluginsFilePath, pluginDirs)
|
||||
for _, dataDir := range xdg.DataDirs {
|
||||
if err := p.loadPluginDir(filepath.Join(dataDir, k9sPluginsDir)); err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// LoadPlugins loads plugins from a given file and a set of plugin directories.
|
||||
func (p Plugins) LoadPlugins(path string, pluginDirs []string) error {
|
||||
func (p Plugins) loadPluginDir(dir string) error {
|
||||
pluginFiles, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
for _, file := range pluginFiles {
|
||||
if file.IsDir() || !isYamlFile(file.Name()) {
|
||||
continue
|
||||
}
|
||||
pluginFile, err := os.ReadFile(filepath.Join(dir, file.Name()))
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
var plugin Plugin
|
||||
if err = yaml.Unmarshal(pluginFile, &plugin); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Plugins[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (p *Plugins) load(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
f, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -66,29 +101,8 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error {
|
|||
if err := yaml.Unmarshal(f, &pp); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range pp.Plugin {
|
||||
p.Plugin[k] = v
|
||||
}
|
||||
|
||||
for _, pluginDir := range pluginDirs {
|
||||
pluginFiles, err := os.ReadDir(pluginDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, file := range pluginFiles {
|
||||
if file.IsDir() || !isYamlFile(file.Name()) {
|
||||
continue
|
||||
}
|
||||
pluginFile, err := os.ReadFile(filepath.Join(pluginDir, file.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var plugin Plugin
|
||||
if err = yaml.Unmarshal(pluginFile, &plugin); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Plugin[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin
|
||||
}
|
||||
for k, v := range pp.Plugins {
|
||||
p.Plugins[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config_test
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var pluginYmlTestData = config.Plugin{
|
||||
var pluginYmlTestData = Plugin{
|
||||
Scopes: []string{"po", "dp"},
|
||||
Args: []string{"-n", "$NAMESPACE", "-boolean"},
|
||||
ShortCut: "shift-s",
|
||||
|
|
@ -20,7 +19,7 @@ var pluginYmlTestData = config.Plugin{
|
|||
Background: false,
|
||||
}
|
||||
|
||||
var test1YmlTestData = config.Plugin{
|
||||
var test1YmlTestData = Plugin{
|
||||
Scopes: []string{"po", "dp"},
|
||||
Args: []string{"-n", "$NAMESPACE", "-boolean"},
|
||||
ShortCut: "shift-s",
|
||||
|
|
@ -30,7 +29,7 @@ var test1YmlTestData = config.Plugin{
|
|||
Background: false,
|
||||
}
|
||||
|
||||
var test2YmlTestData = config.Plugin{
|
||||
var test2YmlTestData = Plugin{
|
||||
Scopes: []string{"svc", "ing"},
|
||||
Args: []string{"-n", "$NAMESPACE", "-oyaml"},
|
||||
ShortCut: "shift-r",
|
||||
|
|
@ -41,29 +40,31 @@ var test2YmlTestData = config.Plugin{
|
|||
}
|
||||
|
||||
func TestSinglePluginFileLoad(t *testing.T) {
|
||||
p := config.NewPlugins()
|
||||
assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"/random/dir/not/exist"}))
|
||||
p := NewPlugins()
|
||||
assert.Nil(t, p.load("testdata/plugins.yaml"))
|
||||
assert.Nil(t, p.loadPluginDir("/random/dir/not/exist"))
|
||||
|
||||
assert.Equal(t, 1, len(p.Plugin))
|
||||
k, ok := p.Plugin["blah"]
|
||||
assert.Equal(t, 1, len(p.Plugins))
|
||||
k, ok := p.Plugins["blah"]
|
||||
assert.True(t, ok)
|
||||
|
||||
assert.ObjectsAreEqual(pluginYmlTestData, k)
|
||||
}
|
||||
|
||||
func TestMultiplePluginFilesLoad(t *testing.T) {
|
||||
p := config.NewPlugins()
|
||||
assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"testdata/plugins"}))
|
||||
p := NewPlugins()
|
||||
assert.Nil(t, p.load("testdata/plugins.yaml"))
|
||||
assert.Nil(t, p.loadPluginDir("testdata/plugins"))
|
||||
|
||||
testPlugins := map[string]config.Plugin{
|
||||
testPlugins := map[string]Plugin{
|
||||
"blah": pluginYmlTestData,
|
||||
"test1": test1YmlTestData,
|
||||
"test2": test2YmlTestData,
|
||||
}
|
||||
|
||||
assert.Equal(t, len(testPlugins), len(p.Plugin))
|
||||
assert.Equal(t, len(testPlugins), len(p.Plugins))
|
||||
for name, expectedPlugin := range testPlugins {
|
||||
k, ok := p.Plugin[name]
|
||||
k, ok := p.Plugins[name]
|
||||
assert.True(t, ok)
|
||||
assert.ObjectsAreEqual(expectedPlugin, k)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
|
||||
// Labels tracks a collection of labels.
|
||||
type Labels map[string][]string
|
||||
|
||||
func (l Labels) exclude(k, val string) bool {
|
||||
vv, ok := l[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range vv {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Blacklist tracks vul scan exclusions.
|
||||
type BlackList struct {
|
||||
Namespaces []string `yaml:"namespaces"`
|
||||
Labels Labels `yaml:"labels"`
|
||||
}
|
||||
|
||||
func newBlackList() BlackList {
|
||||
return BlackList{
|
||||
Labels: make(Labels),
|
||||
}
|
||||
}
|
||||
|
||||
func (b BlackList) exclude(ns string, ll map[string]string) bool {
|
||||
for _, nss := range b.Namespaces {
|
||||
if nss == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for k, v := range ll {
|
||||
if b.Labels.exclude(k, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ImageScans tracks vul scans options.
|
||||
type ImageScans struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
BlackList BlackList `yaml:"blackList"`
|
||||
}
|
||||
|
||||
// NewImageScans returns a new instance.
|
||||
func NewImageScans() *ImageScans {
|
||||
return &ImageScans{
|
||||
BlackList: newBlackList(),
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldExclude checks if scan should be excluder given ns/labels
|
||||
func (i *ImageScans) ShouldExclude(ns string, ll map[string]string) bool {
|
||||
if !i.Enable {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.BlackList.exclude(ns, ll)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ package config
|
|||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ func NewShellPod() *ShellPod {
|
|||
}
|
||||
|
||||
// Validate validates the configuration.
|
||||
func (s *ShellPod) Validate(client.Connection, KubeSettings) {
|
||||
func (s *ShellPod) Validate(client.Connection, data.KubeSettings) {
|
||||
if s.Image == "" {
|
||||
s.Image = defaultDockerShellImage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,12 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sStylesFile represents K9s skins file location.
|
||||
var K9sStylesFile = YamlExtension(filepath.Join(K9sHome(), "skin.yml"))
|
||||
|
||||
// StyleListener represents a skin's listener.
|
||||
type StyleListener interface {
|
||||
// StylesChanged notifies listener the skin changed.
|
||||
|
|
@ -434,6 +430,11 @@ func newMenu() Menu {
|
|||
|
||||
// NewStyles creates a new default config.
|
||||
func NewStyles() *Styles {
|
||||
var s Styles
|
||||
if err := yaml.Unmarshal(stockSkinTpl, &s); err == nil {
|
||||
return &s
|
||||
}
|
||||
|
||||
return &Styles{
|
||||
K9s: newStyle(),
|
||||
}
|
||||
|
|
@ -446,7 +447,6 @@ func (s *Styles) Reset() {
|
|||
|
||||
// DefaultSkin loads the default skin.
|
||||
func (s *Styles) DefaultSkin() {
|
||||
s.K9s = newStyle()
|
||||
}
|
||||
|
||||
// FgColor returns the foreground color.
|
||||
|
|
@ -545,7 +545,6 @@ func (s *Styles) Load(path string) error {
|
|||
if err := yaml.Unmarshal(f, s); err != nil {
|
||||
return err
|
||||
}
|
||||
// s.fireStylesChanged()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func TestColor(t *testing.T) {
|
|||
|
||||
func TestSkinNone(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.Nil(t, s.Load("testdata/empty_skin.yml"))
|
||||
assert.Nil(t, s.Load("testdata/empty_skin.yaml"))
|
||||
s.Update()
|
||||
|
||||
assert.Equal(t, "#5f9ea0", s.Body().FgColor.String())
|
||||
|
|
@ -43,7 +43,7 @@ func TestSkinNone(t *testing.T) {
|
|||
|
||||
func TestSkin(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.Nil(t, s.Load("testdata/black_and_wtf.yml"))
|
||||
assert.Nil(t, s.Load("testdata/black_and_wtf.yaml"))
|
||||
s.Update()
|
||||
|
||||
assert.Equal(t, "#ffffff", s.Body().FgColor.String())
|
||||
|
|
@ -56,10 +56,10 @@ func TestSkin(t *testing.T) {
|
|||
|
||||
func TestSkinNotExits(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.NotNil(t, s.Load("testdata/blee.yml"))
|
||||
assert.NotNil(t, s.Load("testdata/blee.yaml"))
|
||||
}
|
||||
|
||||
func TestSkinBoarked(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.NotNil(t, s.Load("testdata/skin_boarked.yml"))
|
||||
assert.NotNil(t, s.Load("testdata/skin_boarked.yaml"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
aliases:
|
||||
dp: apps/v1/deployments
|
||||
sec: v1/secrets
|
||||
jo: batch/v1/jobs
|
||||
cr: rbac.authorization.k8s.io/v1/clusterroles
|
||||
crb: rbac.authorization.k8s.io/v1/clusterrolebindings
|
||||
ro: rbac.authorization.k8s.io/v1/roles
|
||||
rb: rbac.authorization.k8s.io/v1/rolebindings
|
||||
np: networking.k8s.io/v1/networkpolicies
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
benchmarks:
|
||||
defaults:
|
||||
concurrency: 2
|
||||
requests: 200
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
hotKeys:
|
||||
# Examples...
|
||||
# shift-0:
|
||||
# shortCut: Shift-0
|
||||
# description: View Workloads
|
||||
# command: wk k8s-app=cilium
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Stock skin
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Skin...
|
||||
k9s:
|
||||
body:
|
||||
fgColor: cadetblue
|
||||
bgColor: black
|
||||
logoColor: orange
|
||||
logoColorMsg: white
|
||||
logoColorInfo: green
|
||||
logoColorWarn: mediumvioletred
|
||||
logoColorError: red
|
||||
prompt:
|
||||
fgColor: cadetblue
|
||||
bgColor: black
|
||||
suggestColor: dodgerblue
|
||||
border:
|
||||
default: seagreen
|
||||
command: aqua
|
||||
info:
|
||||
fgColor: orange
|
||||
sectionColor: white
|
||||
dialog:
|
||||
fgColor: dodgerblue
|
||||
bgColor: black
|
||||
buttonFgColor: black
|
||||
buttonBgColor: dodgerblue
|
||||
buttonFocusFgColor: white
|
||||
buttonFocusBgColor: fuchsia
|
||||
labelFgColor: fuchsia
|
||||
fieldFgColor: dodgerblue
|
||||
frame:
|
||||
border:
|
||||
fgColor: dodgerblue
|
||||
focusColor: aqua
|
||||
menu:
|
||||
fgColor: white
|
||||
keyColor: dodgerblue
|
||||
numKeyColor: fuchsia
|
||||
crumbs:
|
||||
fgColor: black
|
||||
bgColor: aqua
|
||||
activeColor: orange
|
||||
status:
|
||||
newColor: lightskyblue
|
||||
modifyColor: greenyellow
|
||||
addColor: white
|
||||
errorColor: orangered
|
||||
pendingColor: darkorange
|
||||
highlightColor: aqua
|
||||
killColor: mediumpurple
|
||||
completedColor: gray
|
||||
title:
|
||||
fgColor: aqua
|
||||
highlightColor: fuchsia
|
||||
counterColor: papayawhip
|
||||
filterColor: steelblue
|
||||
views:
|
||||
# Charts skins...
|
||||
charts:
|
||||
bgColor: black
|
||||
defaultDialColors:
|
||||
- linegreen
|
||||
- orangered
|
||||
defaultChartColors:
|
||||
- linegreen
|
||||
- orangered
|
||||
table:
|
||||
fgColor: aqua
|
||||
bgColor: black
|
||||
cursorFgColor: white
|
||||
cursorBgColor: black
|
||||
markColor: darkgoldenrod
|
||||
header:
|
||||
fgColor: lightGray
|
||||
bgColor: black
|
||||
sorterColor: orange
|
||||
xray:
|
||||
fgColor: blue
|
||||
bgColor: black
|
||||
cursorColor: aqua
|
||||
graphicColor: darkgoldenrod
|
||||
showIcons: false
|
||||
yaml:
|
||||
keyColor: steelblue
|
||||
colonColor: white
|
||||
valueColor: papayawhip
|
||||
logs:
|
||||
fgColor: white
|
||||
bgColor: black
|
||||
indicator:
|
||||
fgColor: dodgerblue
|
||||
bgColor: black
|
||||
toggleOnColor: limegreen
|
||||
toggleOffColor: steelblue
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
alias:
|
||||
aliases:
|
||||
dp: "apps.v1.deployments"
|
||||
pe: ".v1.pods"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
hotKey:
|
||||
hotKeys:
|
||||
pods:
|
||||
shortCut: shift-0
|
||||
description: Launch pod view
|
||||
|
|
@ -6,9 +6,9 @@ k9s:
|
|||
tail: 200
|
||||
buffer: 2000
|
||||
currentContext: minikube
|
||||
currentCluster: minikube
|
||||
clusters:
|
||||
contexts:
|
||||
minikube:
|
||||
cluster: minikube
|
||||
namespace:
|
||||
active: kube-system
|
||||
favorites:
|
||||
|
|
@ -20,6 +20,7 @@ k9s:
|
|||
view:
|
||||
active: ctx
|
||||
fred:
|
||||
cluster: fred
|
||||
namespace:
|
||||
active: default
|
||||
favorites:
|
||||
|
|
@ -5,8 +5,7 @@ k9s:
|
|||
tail: 200
|
||||
buffer: 2000
|
||||
currentContext: minikube
|
||||
currentCluster: minikube
|
||||
clusters:
|
||||
contexts:
|
||||
minikube:
|
||||
namespace:
|
||||
active: kube-system
|
||||
|
|
@ -2,8 +2,7 @@ k9s:
|
|||
refreshRate: 2
|
||||
logBufferSize: 200
|
||||
currentContext: minikube
|
||||
currentCluster: minikube
|
||||
clusters:
|
||||
contexts:
|
||||
minikube:
|
||||
namespace:
|
||||
active: kube-system
|
||||
|
|
@ -4,19 +4,19 @@ clusters:
|
|||
- cluster:
|
||||
certificate-authority: /Users/test/ca.crt
|
||||
server: https://1.2.3.4:8443
|
||||
name: testCluster
|
||||
name: cl-1
|
||||
contexts:
|
||||
- context:
|
||||
cluster: cluster1
|
||||
cluster: cl-1
|
||||
user: user1
|
||||
namespace: ns1
|
||||
name: test1
|
||||
namespace: ns-1
|
||||
name: ct-1
|
||||
- context:
|
||||
cluster: cluster2
|
||||
cluster: cl-1
|
||||
user: user2
|
||||
namespace: ns2
|
||||
name: test2
|
||||
current-context: test1
|
||||
namespace: ns-2
|
||||
name: ct-2
|
||||
current-context: ct-1
|
||||
preferences: {}
|
||||
users:
|
||||
- name: user1
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
plugin:
|
||||
plugins:
|
||||
blah:
|
||||
shortCut: shift-s
|
||||
confirm: true
|
||||
|
|
@ -5,6 +5,7 @@ package config
|
|||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -65,7 +66,7 @@ func NewThreshold() Threshold {
|
|||
}
|
||||
|
||||
// Validate a namespace is setup correctly.
|
||||
func (t Threshold) Validate(c client.Connection, ks KubeSettings) {
|
||||
func (t Threshold) Validate(c client.Connection, ks data.KubeSettings) {
|
||||
for _, k := range []string{"cpu", "memory"} {
|
||||
v, ok := t[k]
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
|
||||
const (
|
||||
defaultRefreshRate = 2
|
||||
defaultMaxConnRetry = 5
|
||||
)
|
||||
|
||||
// UI tracks ui specific configs.
|
||||
type UI struct {
|
||||
// EnableMouse toggles mouse support.
|
||||
EnableMouse bool `yaml:"enableMouse"`
|
||||
|
||||
// Headless toggles top header display.
|
||||
Headless bool `yaml:"headless"`
|
||||
|
||||
// LogoLess toggles k9s logo.
|
||||
Logoless bool `yaml:"logoless"`
|
||||
|
||||
// Crumbsless toggles nav crumb display.
|
||||
Crumbsless bool `yaml:"crumbsless"`
|
||||
|
||||
// NoIcons toggles icons display.
|
||||
NoIcons bool `yaml:"noIcons"`
|
||||
|
||||
// Skin reference the general k9s skin name.
|
||||
// Can be overridden per context.
|
||||
Skin string `yaml:"skin,omitempty"`
|
||||
}
|
||||
|
|
@ -5,17 +5,13 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sViewConfigFile represents the location for the views configuration.
|
||||
var K9sViewConfigFile = YamlExtension(filepath.Join(K9sHome(), "views.yml"))
|
||||
|
||||
// ViewConfigListener represents a view config listener.
|
||||
type ViewConfigListener interface {
|
||||
// ConfigChanged notifies listener the view configuration changed.
|
||||
// ViewSettingsChanged notifies listener the view configuration changed.
|
||||
ViewSettingsChanged(ViewSetting)
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +86,8 @@ func (v *CustomView) fireConfigChanged() {
|
|||
for gvr, list := range v.listeners {
|
||||
if v, ok := v.K9s.Views[gvr]; ok {
|
||||
list.ViewSettingsChanged(v)
|
||||
} else {
|
||||
list.ViewSettingsChanged(ViewSetting{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
func TestViewSettingsLoad(t *testing.T) {
|
||||
cfg := config.NewCustomView()
|
||||
|
||||
assert.Nil(t, cfg.Load("testdata/view_settings.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/view_settings.yaml"))
|
||||
assert.Equal(t, 1, len(cfg.K9s.Views))
|
||||
assert.Equal(t, 4, len(cfg.K9s.Views["v1/pods"].Columns))
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue