## K9s - Kubernetes CLI To Manage Your Clusters In Style!
K9s provides a terminal UI to interact with your Kubernetes clusters.
The aim of this project is to make it easier to navigate, observe and manage
your applications in the wild. K9s continually watches Kubernetes
for changes and offers subsequent commands to interact with your observed resources.
---
## Note...
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!**
---
[](https://goreportcard.com/report/github.com/derailed/k9s)
[](https://golangci.com/r/github.com/derailed/k9s)
[](https://hub.docker.com/r/derailed/k9s/)
[](https://github.com/derailed/k9s/releases)
[](https://github.com/mum4k/termdash/blob/master/LICENSE)
[](https://github.com/derailed/k9s/releases)
---
## Screenshots
1. Pods
2. Logs
3. Deployments
---
## Demo Videos/Recordings
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
* [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.
---
## Slack Channel
Wanna discuss K9s features with your fellow `K9sers` or simply show your support for this tool?
* Channel: [K9sersSlack](https://k9sers.slack.com/)
* Invite: [K9slackers Invite](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
---
## Installation
K9s is available on Linux, macOS and Windows platforms.
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
```shell
brew install derailed/k9s/k9s
```
* Via [MacPorts](https://www.macports.org)
```shell
sudo port install k9s
```
* Via [snap](https://snapcraft.io/k9s) for Linux
```shell
snap install k9s --devmode
```
* On Arch Linux
```shell
pacman -S k9s
```
* On OpenSUSE Linux distribution
```shell
zypper install k9s
```
* On FreeBSD
```shell
pkg install k9s
```
* On Ubuntu
```shell
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_amd64.deb && sudo apt install ./k9s_linux_amd64.deb && rm k9s_linux_amd64.deb
```
* On Fedora (42+)
```shell
dnf install k9s
```
* Via [Winget](https://github.com/microsoft/winget-cli) for Windows
```shell
winget install k9s
```
* Via [Scoop](https://scoop.sh) for Windows
```shell
scoop install k9s
```
* Via [Chocolatey](https://chocolatey.org/packages/k9s) for Windows
```shell
choco install k9s
```
* Via a GO install
```shell
# NOTE: The dev version will be in effect!
go install github.com/derailed/k9s@latest
```
* Via [Webi](https://webinstall.dev) for Linux and macOS
```shell
curl -sS https://webinstall.dev/k9s | bash
```
* Via [pkgx](https://pkgx.dev/pkgs/k9scli.io/) for Linux and macOS
```shell
pkgx k9s
```
* Via [gah](https://github.com/marverix/gah) for Linux and macOS
```shell
gah install k9s
```
* Via [Webi](https://webinstall.dev) for Windows
```shell
curl.exe -A MS https://webinstall.dev/k9s | powershell
```
* As a [Docker Desktop Extension](https://docs.docker.com/desktop/extensions/) (for the Docker Desktop built in Kubernetes Server)
```shell
docker extension install spurin/k9s-dd-extension:latest
```
---
## Building From Source
K9s is currently using GO v1.23.X or above.
In order to build K9s from source you must:
1. Clone the repo
2. Build and run the executable
```shell
make build && ./execs/k9s
```
---
## Running with Docker
### Running the official Docker image
You can run k9s as a Docker container by mounting your `KUBECONFIG`:
```shell
docker run --rm -it -v $KUBECONFIG:/root/.kube/config derailed/k9s
```
For default path it would be:
```shell
docker run --rm -it -v ~/.kube/config:/root/.kube/config derailed/k9s
```
### Building your own Docker image
You can build your own Docker image of k9s from the [Dockerfile](Dockerfile) with the following:
```shell
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.
You can use the `--build-arg` option to pass any valid `kubectl` version (like `v1.18.0` or `v1.19.1`).
```shell
KUBECTL_VERSION=$(make kubectl-stable-version 2>/dev/null)
docker build --build-arg KUBECTL_VERSION=${KUBECTL_VERSION} -t k9s-docker:0.1 .
```
Run your container:
```shell
docker run --rm -it -v ~/.kube/config:/root/.kube/config k9s-docker:0.1
```
---
## PreFlight Checks
* K9s uses 256 colors terminal mode. On `Nix system make sure TERM is set accordingly.
```shell
export TERM=xterm-256color
```
* In order to issue resource edit commands make sure your EDITOR and KUBE_EDITOR env vars are set.
```shell
# Kubectl edit command will use this env var.
export KUBE_EDITOR=my_fav_editor
```
* K9s prefers recent kubernetes versions ie 1.28+
---
## K8S Compatibility Matrix
| k9s | k8s client |
| ------------------ | ---------- |
| >= 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 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 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:
```shell
# Find out where the logs are stored
k9s info
```
```text
____ __.________
| |/ _/ __ \______
| < \____ / ___/
| | \ / /\___ \
|____|__ \ /____//____ >
\/ \/
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
```
### Customize logs destination
You can override the default log file destination either with the `--logFile` argument:
```shell
k9s --logFile /tmp/k9s.log
less /tmp/k9s.log
```
Or through the `K9S_LOGS_DIR` environment variable:
```shell
K9S_LOGS_DIR=/var/log k9s
less /var/log/k9s.log
```
## Key Bindings
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 | `:quit`, `:q`, `ctrl-c` | |
| To go up/back to the previous view | `esc` | If you have crumbs on, this will go to the previous one |
| 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 | `
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 the skin by setting `K9S_SKIN` in the environment, e.g. `export K9S_SKIN="dracula"`.
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
# $XDG_DATA_HOME/k9s/clusters/clusterX/contextY/config.yaml
k9s:
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
# $XDG_CONFIG_HOME/k9s/config.yaml
k9s:
liveViewAutoRefresh: false
screenDumpDir: /tmp/dumps
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
noIcons: false
invert: false
# Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false.
reactive: false
# By default all contexts will use the dracula skin unless explicitly overridden in the context config file.
skin: dracula # => assumes the file skins/dracula.yaml is present in the $XDG_DATA_HOME/k9s/skins directory
defaultsToFullScreen: false
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 100
buffer: 5000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
```
```yaml
# $XDG_DATA_HOME/k9s/skins/in-the-navy.yaml
# Skin InTheNavy!
k9s:
# General K9s styles
body:
fgColor: dodgerblue
bgColor: '#ffffff'
logoColor: '#0000ff'
# ClusterInfoView styles.
info:
fgColor: lightskyblue
sectionColor: steelblue
# Help panel styles
help:
fgColor: white
bgColor: black
keyColor: cyan
numKeyColor: blue
sectionColor: gray
frame:
# Borders styles.
border:
fgColor: dodgerblue
focusColor: aliceblue
# MenuView attributes and styles.
menu:
fgColor: darkblue
# Style of menu text. Supported options are "dim" (default), "normal", and "bold"
fgStyle: dim
keyColor: cornflowerblue
# Used for favorite namespaces
numKeyColor: cadetblue
# CrumbView attributes for history navigation.
crumbs:
fgColor: white
bgColor: steelblue
activeColor: skyblue
# Resource status and update styles
status:
newColor: '#00ff00'
modifyColor: powderblue
addColor: lightskyblue
errorColor: indianred
highlightcolor: royalblue
killColor: slategray
completedColor: gray
# Border title styles.
title:
fgColor: aqua
bgColor: white
highlightColor: skyblue
counterColor: slateblue
filterColor: slategray
views:
# TableView attributes.
table:
fgColor: blue
bgColor: darkblue
cursorColor: aqua
# Header row styles.
header:
fgColor: white
bgColor: darkblue
sorterColor: orange
selectedSortColumnColor: lightskyblue
# YAML info styles.
yaml:
keyColor: steelblue
colonColor: blue
valueColor: royalblue
# Logs styles.
logs:
fgColor: lightskyblue
bgColor: black
indicator:
fgColor: dodgerblue
bgColor: black
toggleOnColor: limegreen
toggleOffColor: gray
```
---
## Contributors
Without the contributions from these fine folks, this project would be a total dud!
# Release v0.10.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
First off, Happy 2020 to you and yours!! Best wishes for good health and good fortune!
This release represents a major overall of K9s core. It's been a long time coming and indeed a long day in the saddle. There has been many code changes and hopefully improvements from previous releases. I think some of it is better but I've probably borked a bunch of functionality in the process ;( I look to you to help me flesh out issues and bugs, so we can move on to bigger and exciting features in 2020! Please do thread lightly on this one and make sure to keep a previous release handy just in case... This was a boatload of work to make this happen, my hope is you'll enjoy some of the improvements... In any case, and as always, if you feel they're better ways or imperfections by all means please pipe in!
I would also like to take this opportunity to thank all of you for your kind PRs and issues and for your support and patience with K9s. I understand this release might be a bit torked, but I will work hard to make sure we reach stability quickly in the next few drops. Thank you for your understanding!!
## VatDoesDisDo?
Most of the refactors are around K8s resource fetching and viewing as well as navigation changes. Based on our observations this release might load resources a bit slower than usual but should make navigation much faster once the cache is primed. We've made some improvements to be more consistent with navigation, menus and shortcuts management. We've got ride off the breadcrumbs navigation ie no more `p` to nav back. Crumbs are now just tracking a natural resource navigation ie pod -> containers -> logs and no longer commands history. Each new command will now load a brand new breadcrumb. You can press `
# Release v0.10.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
First casualties... As my pappy used to say `can't cook without making a big mess`. Also reminds me of his very last words `A Truck?`...
---
## Resolved Bugs/Features
* [Issue #450](https://github.com/derailed/k9s/issues/450) Skins work messed up the UI. Thank you [Julio H Morimoto](https://github.com/juliohm1978)!!
---
# Release v0.10.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #451](https://github.com/derailed/k9s/issues/451)
* [Issue #415](https://github.com/derailed/k9s/issues/415)
---
# Release v0.10.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
Thank you all for kicking the tires on these new drops and in making sure we get back to nominal quickly. You guys ROCK!!
---
## Resolved Bugs/Features
* [Issue #455](https://github.com/derailed/k9s/issues/455)
* [Issue #454](https://github.com/derailed/k9s/issues/454)
* [Issue #453](https://github.com/derailed/k9s/issues/453)
---
# Release v0.10.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #456](https://github.com/derailed/k9s/issues/456)
* [Issue #452](https://github.com/derailed/k9s/issues/452)
---
# Release v0.10.5
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
Starting the new year with a ... Bug!
Hopefully moving the needle a bit more in the right direction??
---
## Resolved Bugs/Features
* [Issue #457](https://github.com/derailed/k9s/issues/457)
---
# Release v0.10.6
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #452](https://github.com/derailed/k9s/issues/452)
---
# Release v0.10.7
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #458](https://github.com/derailed/k9s/issues/458)
---
# Release v0.10.8
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #460](https://github.com/derailed/k9s/issues/460)
---
# Release v0.10.9
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release!
---
## Resolved Bugs/Features
* [Issue #461](https://github.com/derailed/k9s/issues/461)
* [Issue #462](https://github.com/derailed/k9s/issues/462)
---
# Release v0.11.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Anyone At The Helm?
K9s now offers preliminary support for Helm 3 charts! It's been a long time coming and I know a few early users had mentioned the need, but I wanted to see where Helm3 was going first. This is a preview release to see how we fair in Helm land. Besides managing your installed charts, you will be able to perform the following operations:
* Uninstall a chart
* View chart release notes
* View deployed manifests
#### How to use?
Simply enter `:charts` K9s alias command to view the deployed Helm3 charts on your cluster.
If you're using Helm3 in your current clusters, please give it a rip and also pipe in for potential features/enhancements. Mind the gap here as the paint on this feature is totally fresh...
### Bring Out Your Deads...
There are also a few bugs fixes from the refactor aftermath that made this drop. I know this was a bit of a brutal transition, so thank you all for your patience and for filing issues! I am hopeful that K9s will stabilize quickly so we can move on to bigger things.
---
## Resolved Bugs/Features
---
# Release v0.11.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
Maintenance Release!
---
## Resolved Bugs/Features
* [Issue #466](https://github.com/derailed/k9s/issues/466)
---
# Release v0.11.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
Maintenance Release!
---
## Resolved Bugs/Features
* [Issue #469](https://github.com/derailed/k9s/issues/469)
* [Issue #468](https://github.com/derailed/k9s/issues/468)
---
# Release v0.11.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
Maintenance Release!
### Speedy Gonzales?
In this drop, we took a bit of a perf pass in light of recent issues and thanks to [Chris Werner Rau](https://github.com/cwrau) pushing me and keeping me up to speed, I've digged a bit deeper and found that there might be some seemingly innocent calls that sucked a bit of cycles during K9s refreshes. Long story short, I think this drop will improve perf by a factor of ~10x in some instances. Typically the initial load will be slower but subsequent loads should be much faster. Famous last words right? Anyhow, can't really take credit for this one as the awesome [Gustavo Silva Paiva](https://github.com/paivagustavo) suggested doing this a while back, but since I was already in flight with the refactor decided to punt until back online. And here we are now...
Hopefully these findings will coalesce with yours?? If not, please forward bulk Prozac patches at the address below ;)
Thanks Chris! Was up all night trying to figure out and what was the deal with K9s and your specific clusters. Hopefully this time for sure??
---
## Resolved Bugs/Features
* [Issue #475](https://github.com/derailed/k9s/issues/475)
* [Issue #473](https://github.com/derailed/k9s/issues/473)
---
# Release v0.12.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
### Searchable Logs
There has been quite a few demands for this feature. It should now be generally available in this drop. It works the same as the resource view ie `/fred`, you can also specify a fuzzy filter using `/-f blee-duh`. The paint is still fresh on that deal and not super confident that it will work nominally as I had to rework the logs to enable. So totally possible I've hosed something in the process.
### APIServer Dud
At times, it could be you've lost your api server connection while K9s was up which resulted in the `K9s screen of death` or in other words a hosed terminal session ;(. K9s should now detect this condition and close out. Once again no super sure about this implementation on that deal. So if you see K9s close out under normal condition, that means I would need to go back to the drawing board.
### FullScreen Logs
I've been told having a flag to set fullScreen mode preference while viewing the logs would be `awesome`. Thanks [Fardin Khanjani](https://github.com/fardin01)!
So there is now a new K9s config flag available to set your fullscreen logs `drathers` in your .k9s/config.yml. This flag defaults to false if not set.
Here is a snippet:
```yaml
# .k9s/config.yml
k9s:
refreshRate: 2
headless: false
currentContext: crashandburn666
currentCluster: slowassnot
fullScreenLogs: true
...
```
### K9s Slackers
I've enabled a [K9s slack channel](https://join.slack.com/t/k9sers/shared_invite/enQtOTAzNTczMDYwNjc5LWJlZjRkNzE2MzgzYWM0MzRiYjZhYTE3NDc1YjNhYmM2NTk2MjUxMWNkZGMzNjJiYzEyZmJiODBmZDYzOGQ5NWM) dedicated to all K9sers. This would be a place for us to meet and discuss ideas and use cases. I'll be honest here I am not a big slack aficionado as I don't do very well with interrupt drive workflows. But I think it would be a great resource for us all.
NOTE: Please be kind to each others and threat everyone with respect as I would like this to be a safe and fun place for folks to hangout. Thank you for you support and understanding!!
NOTE: I'll admit my slackFU is pretty low, so if you're a slack master, feel free to advise me for best practices around setup and management, etc... Thank you!
---
## Resolved Bugs/Features
* [Issue #484](https://github.com/derailed/k9s/issues/484)
* [Issue #481](https://github.com/derailed/k9s/issues/481)
* [Issue #480](https://github.com/derailed/k9s/issues/480)
* [Issue #479](https://github.com/derailed/k9s/issues/479)
* [Issue #477](https://github.com/derailed/k9s/issues/477)
* [Issue #476](https://github.com/derailed/k9s/issues/476)
* [Issue #468](https://github.com/derailed/k9s/issues/468)
---
` to navigate back.
+ Added configuration `logBufferSize` to limit the size of the log view while viewing chatty or big logs.
---
## Resolved Bugs
+ [Issue #116](https://github.com/derailed/k9s/issues/116)
+ [Issue #113](https://github.com/derailed/k9s/issues/113)
+ [Issue #111](https://github.com/derailed/k9s/issues/111)
+ [Issue #110](https://github.com/derailed/k9s/issues/110)
================================================
FILE: change_logs/release_0.2.6.md
================================================
# Release v0.2.6
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
1. Preliminary drop on sorting by resource columns
2. Add sort by namespace, name and age for all views
3. Add invert sort functionality on all sortable views
4. Add sort on pod views for metrics and most other columns
5. For all other views we will add custom sort on a per request basis
---
## Resolved Bugs
+ [Issue #117](https://github.com/derailed/k9s/issues/117)
Was filtering out inactive ns which need to be there for all to see anyway!
+ [Issue #59](https://github.com/derailed/k9s/issues/59)
================================================
FILE: change_logs/release_0.3.0.md
================================================
# Release v0.3.0
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
1. [Feature #127](https://github.com/derailed/k9s/issues/127)
Added PodDisruptionBudgets initial support.
---
## Resolved Bugs
+ [Issue #126](https://github.com/derailed/k9s/issues/126)
+ [Issue #125](https://github.com/derailed/k9s/issues/125)
+ [Issue #122](https://github.com/derailed/k9s/issues/122) With feeling!
================================================
FILE: change_logs/release_0.3.1.md
================================================
# Release v0.3.1
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
1. Refactored a lot of code! So please watch for disturbance in the force!
1. Changed cronjob and job aliases names to `cj` and `jo` respectively
1. *JobView*: Added new columns
1. Completions
2. Containers
3. Images
1. *NodeView* Added the following columns:
1. Available CPU/Mem
2. Capacity CPU/Mem
1. *NodeView* Added sort fields for cpu and mem
---
## Resolved Bugs
+ [Issue #133](https://github.com/derailed/k9s/issues/133)
+ [Issue #132](https://github.com/derailed/k9s/issues/132)
+ [Issue #129](https://github.com/derailed/k9s/issues/129) The easiest bug fix to date ;)
================================================
FILE: change_logs/release_0.3.2.md
================================================
# Release v0.3.2
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
1. [Feature #124](https://github.com/derailed/k9s/issues/124)
1. *NodeView* Add current cpu/memory percentages to track current load on nodes.
2. *NodeView* Add requested cpu/memory percentages to track how many container
resources are requested on the cluster.
3. *NodeView* Add requested cpu/memory raw metrics
4. *NodeView* Add corresponding column sorters
---
## Resolved Bugs
+ None
================================================
FILE: change_logs/release_0.3.3.md
================================================
# Release v0.3.3
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
1. [Feature #131](https://github.com/derailed/k9s/issues/131)
Preliminary support for snapcraft builds ie read trying this out...
2. [Feature #118](https://github.com/derailed/k9s/issues/118) Add arm 32/64 bit builds.
NOTE: will need help vetting this out as my RPi cluster is currently down.
---
## Resolved Bugs
+ [Feature #132](https://github.com/derailed/k9s/issues/132). With feelings!
================================================
FILE: change_logs/release_0.4.0.md
================================================
# Release v0.4.0
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
---
## Change Logs
> NOTE! Lots of changes here, please report any disturbances in the force. Thank you!
1. [Feature #82](https://github.com/derailed/k9s/issues/82)
1. Added ability to view RBAC policies while in clusterrole or role view.
2. The RBAC view will auto-refresh just like any K9s views hence showing live RBAC updates
3. RBAC view supports standard K8s verbs ie get,list,deletecollection,watch,create,patch,update,delete.
4. Any verbs not in this standard K8s verb list, will end up in the EXTRAS column.
5. For non resource URLS, we map standard REST verbs to K8s verbs ie post=create patch=update, etc.
6. Added initial sorts by name and group while in RBAC view.
7. Usage: To activate, enter command mode via `:cr` or `:ro` for clusterrole(cr)/role(ro), select a row and press ` (with confirmation) Promote rollout
# (with confirmation) Promote rollout
#
# Release v0.4.1
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
---
## Change Logs
### o Subject View
You can now view users/groups that are bound by RBAC rules without having to type to full subject name.
To activate use the following command mode
```text
# For users
:usr
# For groups
:grp
```
These commands will pull all the available cluster and role bindings associated with these subject types.
Use select + `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.2.md
================================================
# Release v0.4.2
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### o Decode Secrets On Demand
Secrets can now be base64 decoded to view their actual content.
In the secret view you can use `ctrl-x` to decode a selected secret. [Feature #123](https://github.com/derailed/k9s/issues/123)
### o YAML Highlighter
Describe and YAML commands will now yield syntax highlighted view.
[Feature #142](https://github.com/derailed/k9s/issues/142)
---
## Resolved Bugs
+ Sort by age busted [Issue #145](https://github.com/derailed/k9s/issues/145)
+ Logs not escaped correctly [Issue #137](https://github.com/derailed/k9s/issues/137)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.3.md
================================================
# Release v0.4.3
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ Sort by age busted (with feeling edition!) [Issue #145](https://github.com/derailed/k9s/issues/145)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.4.md
================================================
# Release v0.4.4
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Exiting K9s
There are a few debates about drathers on K9s key bindings. I have caved in
and decided to give up my beloved 'q' for quit which will no longer be bound. As of this release quitting K9s must be done via `:q` or `ctrl-c`.
### Container Logs
[Feature #147](https://github.com/derailed/k9s/issues/147). The default behavior was to pick the first available container. Which meant if the pod has an init container, the log view would choose that.
The view will now choose the first non init container. Most likely it
would be the wrong choice in pod's sidecar scenarios, but for the time
being showing log on one of the init containers just did not make much sense. You can still pick other containers via the menu options. We will implement a better solution for this soon...
### Delete Dialog
[Feature #146](https://github.com/derailed/k9s/issues/146) Tx @dperique!
Pressing `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.5.md
================================================
# Release v0.4.5
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Multi containers
There was an [issue](https://github.com/derailed/k9s/issues/135) where we ran into limitations with the container
selection keyboard shortcuts only allowing up to 10 containers. In this release, we've changed to a pick list vs the menu
to select containers for both shell and logs access. This gives K9s the ability to select up to 26 containers now. This
is not in any way an *encouragement* to have so many containers per pods!!
### Alias View ShortCut
The change above entailed having to move the alias shortcut to `A` vs `a` as the pick list shortcuts conflicted with
the alias view keyboard activation.
---
## Resolved Bugs
+ [Issue #152](https://github.com/derailed/k9s/issues/152)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.6.md
================================================
# Release v0.4.6
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ Node overview column ordering [Issue #153](https://github.com/derailed/k9s/issues/153)
+ Changed foreground color on container pick list [Issue #132](https://github.com/derailed/k9s/issues/132)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.7.md
================================================
# Release v0.4.7
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Popeye Support
Managing and operating a cluster is the wild is hard and getting harder.
I've created [Popeye](https://github.com/derailed/popeye) to help with cluster sanitation and best practices. Since K9s folks are so awesome, you're getting a sneak peek! I figured why not integrate it with K9s? No need to install yet another CLI right? Provided I did not mess this up too much, you should now be able to use command mod `:popeye` to access Popeye sanitizer reports within
K9s and scan your clusters. You can read more about it [here](https://medium.com/@fernand.galiana/k8s-clusters-oh-biff-em-popeye-637e9312963)
and if you like so give it a clap or two ;)
NOTE: In a K9s environment, if you'd like to specify a spinach config file, you must set it in your $HOME/.k9s/spinach.yml.
NOTE: There is a bit more that need to be done on this integration to be complete. Please file an issue if something does not work as expected.
NOTE: Popeye will run its own course and K9s is just a viewer for it, so if you'd like additional sanitation or find Popeye related issues, please tune to the corresponding repo!
Let us know if you dig it? And share your before/after clusters scores!
---
## Resolved Bugs
+ Great find! Thank you @swe-covis! Moved alias view to `Ctrl-A` [Issue #156](https://github.com/derailed/k9s/issues/156)
+ Added toggle autoscroll via `s` key [Issue #155](https://github.com/derailed/k9s/issues/155)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.4.8.md
================================================
# Release v0.4.8
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #159](https://github.com/derailed/k9s/issues/159)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.5.0.md
================================================
# Release v0.5.0
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
I am super excited about this drop of K9s. Lots of cool improvements based on K9s friends excellent feedback!
### Popeye
Turns out [Popeye](https://github.com/derailed/popeye) is in too much flux at present, thus I've decided to remove it from K9s for the time being.
### ContainerView
Added a container view to list all the containers available on a given pod. On a selected pod, you can now press `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.5.1.md
================================================
# Release v0.5.1
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Minor code cleanup and some display bug fixes.
---
## Resolved Bugs
+ [Issue #168](https://github.com/derailed/k9s/issues/168)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.5.2.md
================================================
# Release v0.5.2
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
+ [Issue #173](https://github.com/derailed/k9s/issues/173)
+ [Issue #174](https://github.com/derailed/k9s/issues/174)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.0.md
================================================
# Release v0.6.0
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### K9s Got Skins
You can now skin K9s based on your own sense of style. Skinning, is currently limited to color variations and is still very much experimental. More details on how to achieve this is provided in the README and skins sample directory on this repo. This could be a prime opportunity for someone to contribute to this project and help us come up with some cooler looks and share with all K9s folks. Any cool skins contributed will be added and featured in this repo 🐶!
### Possible instability
Just spent my birthday weekend tracking down a weird synchronization issue ;( I might have introduced some instability in the process. So please thread lightly and
please report any weirdness. Thank you!!
---
## Resolved Bugs
+ [Issue #169](https://github.com/derailed/k9s/issues/169)
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
+ [Issue #172](https://github.com/derailed/k9s/issues/172)
+ [Issue #175](https://github.com/derailed/k9s/issues/175)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.1.md
================================================
# Release v0.6.1
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #171](https://github.com/derailed/k9s/issues/171) With feelings...
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.2.md
================================================
# Release v0.6.2
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Performance
In our attempt to remediate screens lock outs, it looks like K9s performance on certain clusters took a major dive. In this drop we've taken a peek at improving some of the perf issues tho much more investigating does remain. Big ATTA Boys! in effect this week to @eldada and @despairblue for kind support in helping me track down some of these issues. We're not done yet but hopefully this drop will be a bit of an improvement in the 0.6.x lineage. Thank you all for your patience and support!!
---
## Resolved Bugs
+ [Issue #176](https://github.com/derailed/k9s/issues/171) Fingers crossed it's a better drop 🙏🐭?
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.3.md
================================================
# Release v0.6.3
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Performance... With feelings!
Ran thru another perf pass and hope I've pushed the needle in the right direction? K9s is now leveraging informers which I think came out of CRDs work. Our initial assessments shows numbers to μsecond updates, down from milliseconds 🎉. Hopefully the outputs are still correct as I have the tendency to make things much faster with incorrect results ;( We hope to hear back from you with a report from your clusters and assessments and brace for good news? This was a deep cycle thru K9s core and more perf will be gained, once we get a chance to vet this new strategy. I'd like to take this opportunity to thank you all for your patience and incredible kindness and support. We certainly hope this drop won't turn out to be a dud as I am fresh out of prozac patches 😩
---
## Resolved Bugs
+ [Issue #176](https://github.com/derailed/k9s/issues/171) Fingers crossed it's a better drop 🙏🐭?
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.4.md
================================================
# Release v0.6.4
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Aftermath...
Various bug fixes and cleanup items.
---
## Resolved Bugs
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.5.md
================================================
# Release v0.6.5
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #178](https://github.com/derailed/k9s/issues/178)
+ [Issue #179](https://github.com/derailed/k9s/issues/179)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.6.md
================================================
# Release v0.6.6
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #180](https://github.com/derailed/k9s/issues/180)
+ [Issue #181](https://github.com/derailed/k9s/issues/181)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.6.7.md
================================================
# Release v0.6.7
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
This is a maintenance release to mainly resolve outstanding issues and bugs.
### Tracking Percentages
Added two new columns to track cpu/memory utilization on metrics-server enabled clusters. These apply to pod,container and node view.
---
## Resolved Bugs
+ [Issue #192](https://github.com/derailed/k9s/issues/192)
+ [Issue #190](https://github.com/derailed/k9s/issues/190)
+ [Issue #189](https://github.com/derailed/k9s/issues/189)
+ [Issue #185](https://github.com/derailed/k9s/issues/185)
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
+ [Issue #155](https://github.com/derailed/k9s/issues/155)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.0.md
================================================
# Release v0.7.0
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Labor Day Weekend?
I always seem to get this wrong... Does Labor Day weekend mean you get to work on your OSS projects all weekend?
I am very excited about this drop and hopefully won't be unanimous (?) on this? 🐭
For the impatient watch this! [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8)
### Service Traversals
Provided your K8s services are head(Full), you can now navigate to the pods that match the service selector. So you will be able to traverse Pods/Containers directly from a service just like other resources like deployment, cron, sts...
### Moving Forward!
In this drop, we've added support for port-forwarding that allows you to exercise your container from your local machine. To setup a port-forward, from the Pod view drill down to a selected Pod's containers, select the container that exposes the port of interest and hit `Ctrl-F`. A dialog will popup allowing you to configure a localhost port to forward to. Once set up, K9s will take you to the new PortForward view aka `pf`. Pending your terminal feature and container setup, you should be able to pop the forwarded URL directly into your browse. On iTerm2 me think `command+click` does the trick?
Big thanks and ATTABOY! in full effect all week to [Brent](https://github.com/brentco) for filing this initial issue. Please keep in mind, these port-forward babies are a bit expensive to run, so make sure you choose wisely and delete any superfluous PFs!!
This feature is still work in progress. It does require a new config file to help assist with URL configurations. As it stands, your PortForwards are in effect for the current K9s session and will be terminated on exit. Please thread lightly and checkout the README under the Benchmarking section. Your feedback on this as always, is welcome and encouraged!
### Hey now!
This is one feature that I think is, pardon my french.., totally `Bitch'n`! K9s now bundles [Hey](https://github.com/rakyll/hey) CLI tool from the extremely talented Jaana Dogan of Google fame. Hey allows you to benchmark HTTP service endpoints similar to apache bench.
Benchmarking is enabled via keyboard shortcuts `Ctrl-B` and `Alt-B` to activate/cancel a benchmark while in PortForward and Service view. Benchmarking a service assumes the HTTP service is exposed either as NodePort or LoadBalancer. To view your benchmarks, navigate to the new Benchmark view aka `:be
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.1.md
================================================
# Release v0.7.1
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### AfterMath
Looks like I've broken some stuff in the excitement of 0.7.0! As I ran thru the video this am, I noticed the last minute screen dumps might not be a viable feature. As [Norbert](https://github.com/ncsibra) correctly points out, in issue #187 (Thanks Norbert!!), retrieving screen dumps was a pain. So I've put together a quick ScreenDump view alias `sd` to view the screen snapshots and allows to pop your editor of choice upon selection to view the screen dump file content.
NOTE: You will need to use an EDITOR env var to tell K9s which editor you want to use.
```shell
# For example...
export EDITOR=vim
```
This is a quick turn around, hopefully I did not hose anything else in the process ;(
---
## Resolved Bugs/Features
+ [Issue #200](https://github.com/derailed/k9s/issues/200)
+ [Issue #187](https://github.com/derailed/k9s/issues/187)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.10.md
================================================
# Release v0.7.10
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Meow! Looks like v0.7.9 hosed the logger ;) Sorry!!
---
## Resolved Bugs/Features
+ [Issue #245](https://github.com/derailed/k9s/issues/245)
+ [Issue #231](https://github.com/derailed/k9s/issues/231)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.11.md
================================================
# Release v0.7.11
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release. Just code clean up and small bug fixes.
---
## Resolved Bugs/Features
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.12.md
================================================
# Release v0.7.12
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release. Just code clean up and bug fixes.
---
## Resolved Bugs/Features
+ [Issue #259](https://github.com/derailed/k9s/issues/259)
+ [Issue #258](https://github.com/derailed/k9s/issues/258)
+ [Issue #256](https://github.com/derailed/k9s/issues/256)
+ [Issue #255](https://github.com/derailed/k9s/issues/255)
+ [Issue #252](https://github.com/derailed/k9s/issues/252)
+ [Issue #250](https://github.com/derailed/k9s/issues/250)
+ [Issue #246](https://github.com/derailed/k9s/issues/246)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.13.md
================================================
# Release v0.7.13
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release bug fixes
---
## Resolved Bugs/Features
+ [Issue #266](https://github.com/derailed/k9s/issues/266)
+ [Issue #262](https://github.com/derailed/k9s/issues/262)
+ [Issue #246](https://github.com/derailed/k9s/issues/246) Thank you @mcristina422!
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.2.md
================================================
# Release v0.7.2
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Bug Fix Drop
Removed requirement that enforces node access. In the case RBAC rules are in effect and user does not have enough RBAC-Fu to list/watch cluster nodes.
---
## Resolved Bugs/Features
+ [Issue #202](https://github.com/derailed/k9s/issues/202)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.3.md
================================================
# Release v0.7.3
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs/Features
+ [Issue #210](https://github.com/derailed/k9s/issues/210)
+ [Issue #209](https://github.com/derailed/k9s/issues/209)
+ [Issue #206](https://github.com/derailed/k9s/issues/206) Thank you @carlowouters!!
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.4.md
================================================
# Release v0.7.4
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release.
---
## Resolved Bugs/Features
+ [Issue #211](https://github.com/derailed/k9s/issues/210)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.5.md
================================================
# Release v0.7.5
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Rats, looks like 0.7.4 is a dud! Sorry my fault, feeling burned out ;(
Please upgrade to 0.7.5. Thank you for your patience and support!
---
## Resolved Bugs/Features
+ [Issue #211](https://github.com/derailed/k9s/issues/210)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.6.md
================================================
# Release v0.7.6
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### MultiLogs Initial Support
This is an experimental enhancement to allow to view logs for associated resources ie view logs for all containers in a pod or view container logs for pods fronted by a service, deployment, etc... directly in K9s. We've contemplated integrating the excellent `stern` CLI for this which is more full featured than the current implementation, but decided that shelling out for logs was at this juncture not ideal. Based on your feedback, we might revisit in future releases should this feature be a total dud...
### Delete Dialog
The resource delete dialog was updated to provide affordance for force and cascade deletes. This should now provide an on par behavior with the `kubectl` CLI. Cascade and force options are checkboxes, please use `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.7.md
================================================
# Release v0.7.7
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Labels Filters
K9s now provides an affordance to filter Kubernetes resources by label (Feature #233. Thank you [Chad Hanley](https://github.com/cchanley2003)). In order to enable filtering by labels, enter the filter mode via `/` on any resource table and enter your label filter via `-l app=fred,env=prod` + `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.8.md
================================================
# Release v0.7.8
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
This is mainly a maintenance release a few bugs were fixed.
### Breaking Change!
We've changed the benchmarks and skins file formats in this release. Please take a peek at the README and sample skin files for the deltas.
### RBAC Checks
There was a few issues regarding running K9s on RBAC enabled clusters. It turns out some of the permission checks were faulty. In this release, we hope these are now fixed. Please send us issues if that is not the case.
---
## Resolved Bugs/Features
+ [Issue #242](https://github.com/derailed/k9s/issues/242)
+ [Issue #241](https://github.com/derailed/k9s/issues/241)
+ [Issue #201](https://github.com/derailed/k9s/issues/201)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.7.9.md
================================================
# Release v0.7.9
## Notes
Thank you to all that contributed with flushing out issues with 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 is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release a few bugs and code cleanup items.
---
## Resolved Bugs/Features
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.8.0.md
================================================
# Release v0.8.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Pretty excited about this drop! I am as ever humbled by all the cool comments and suggestions you guys are coming up with.
There are a few features that were requested that are simply excellent! Thank you all for your support, feedback and observations 👏
Now that said, some features might be more or less baked, so there might be some disturbance in the force with this drop since much code churned. So please file issues or PRs 🥰 if you notice anything that no longer works as expected.
### Client Update
In the mist of the next Kubernetes 1.16 drop, deprecating some old apis, we've decided to update K9s to support 1.15.1 client. We don't forsee any issues here but please make sure all is cool with this K9s drop on your clusters. If not please let us know so we can address. Thank you!!
### Scaling Pods
This was feature #12 filed by [Tyler Lewis](https://github.com/alairock) many moons ago. So big thanks to Tyler!! To be honest I was on the fence with this feature as I am not a big fan of one offs when it comes to cluster management. However I think it's a great way to validate adequate HPA settings while putting your cluster under load and use K9s to figure out what reasonable number of pods might be. Now this feature was not my own implementation so all kudos on this one goes to [Nathan Piper](https://github.com/nathanpiper) for spending the time to make this a reality for all of us. So many thanks to you Nathan!!
By Nathan's implementation you can now leverage the `s` shortcut for scale deployments, replication controllers and statefulsets. Very cool!
### FuzzBuzz!
Another enhancement request came this time from [Arthur Koziel](https://github.com/arthurk) and I think you guys will dig this one. So big thanks to Arthur for this report!! K9s now leverages a fuzzy finder to be able to search for resources. Previous implementation just used regex to locate matches. For example with this addition you can now type `promse` while in search mode `/` to locate all prometheus-server-5d5f6db7cc-XXX pods. That's so cool! Once this implementation is vetted, we will enable fuzzy searching on other views as well.
### ClipBoarding
This feature comes out of [Raman Gupta](https://github.com/rocketraman) report. Thank you Raman!! This allows a K9s operator to now just hit `c` while on a resource table view to copy the currently selected resource name to the clipboard. This allows you to navigate between K9s and other tools to search, grep/etc.. thru the currently selected resource. We may want to improve on this some but the basic implementation is now available.
### OldiesButGoodies?
So the initial few releases of K9s did not have any failsafe counter measures while deleting resources. So we've beefed the deletion logic to make sure you did not inadvertently blow something away by leveraging
dialogs. This was totally a reasonable thing to do! However in case of managed pods, one may want to quickly cycle on or more pod perhaps to pickup a new image or configuration. For this purpose we've introduced an alternate deletion mechanism to delete pod under `alt-k` for kill. Thanks to my fellow frenchma [ftorto](https://github.com/ftorto) for this one ;)
### HairPlugs!
This one is cool! I think this thought came about from (Markus)[https://github.com/Makusi75]. Thank you Markus!! This feature allows K9s users to now customize K9s with their own plugin commands. You will be able to add a new menu shortcut to the K9s menu and fire off a custom command on a selected resource. Some of you might be leveraging kubectl plugins and now you will be able to fire these off directly from K9s along with many other shell commands.
In order to specify a custom plugin command, you will need to modify your .k9s/config.yml file. For example here is a sample extension to list out all the pods in the `fred` namespace while in a pod or deployment view. When this plugin is available a new command `
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.8.1.md
================================================
# Release v0.8.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### FuzzBuster!
So it looks like going all fuzzy was a mistake as we've lost some nice searchability feature with the regex counterpart. No worries tho Fuzzy is still around! The logic for searching will default to regex like all prior K9s version. To enable fuzzy logic, I figured we will use the same idea as we did with label filters using `/-lapp=bobo` but instead using `/-fpromset`
### Location, Location, Location!
There was a few issues related to screen `real estate` with K9s or more specifically the lack of it! Some folks flat out decided not to use K9s just because of the ASCII Logo ;( WTF! In this drop, I'd like to introduce a new presentation mode aka `Headless`.
Using the following command you can now run K9s headless:
```shell
k9s --headless # => Launch K9s without the header rows
```
NOTE! If you forgot your K9s shortcuts already, fear not! I've also updated the help menu so `?` will remind you of all the available options.
Lastly if you really dig the headless mode, you can sneak an extra `headless: true` in your ./k9s/config.yml like so:
```yaml
k9s:
refreshRate: 2
headless: false
...
```
### Menu Shortcuts
Some folks correctly pointed out the issue with the `Alt-XXX`. Totally my bad as my external mac keyboard unlike my MBP keyboard shows `option` and `alt` as the same key. So I've added a check to make sure the correct mnemonic is displayed based on you OS. Big Thanks for the call out to Ming, Eldad, Raman and Andrew!! Hopefully it did not hose the menu options in the process... 🙏
---
## Resolved Bugs/Features
+ [Issue #286](https://github.com/derailed/k9s/issues/286)
+ [Issue #285](https://github.com/derailed/k9s/issues/285)
+ [Issue #270](https://github.com/derailed/k9s/issues/270)
+ [Issue #223](https://github.com/derailed/k9s/issues/223)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.8.2.md
================================================
# Release v0.8.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release.
In this quick drop, we've opted to nuke any menu shortcut using the infamous `Alt` key. This includes the new pod kill command that is now `Ctrl-K` and for the most part the column sorting shortcuts for CPU% and MEMORY%. My apologizes to all on this fiasco as it turns out I had remapped opt->alt on my local dev machine and space it while trying to offer different key mappings. Will revisit this in the future when things simmer down a bit. Thank you to all that reported on this!
---
## Resolved Bugs/Features
+ Nuked Alt-XXX menu mnemonic [Issue #285](https://github.com/derailed/k9s/issues/285)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.8.3.md
================================================
# Release v0.8.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### NetworkPolicy
NetworkPolicy resource is now available under the command `np` while in command mode. It behaves like other K9s resource view. You will get a bit more information in K9s vs `kubectl` as it includes information about ingress and egress rules.
### Arrrggg! New CLI Argument
There is a new K9s command option available on the CLI that affords for launching K9s with a given resource. For example using `k9s -c svc` will launch K9s with a preloaded service view. You can use the same aliases as you would while in K9s to navigate a resources. For all supports resource aliases please view the `Alias View` using `Ctrl-A`.
### CRDS!
We've beefed up CRD support to allow users to navigate to the CRD instances view more readily. So you can now navigate between CRD schema and CRD instances by just hitting `ENTER` while in the `crd` view.
---
## Resolved Bugs/Features
+ CRD Navigation [Issue #295](https://github.com/derailed/k9s/issues/295)
+ Terminal colors [Issue #294](https://github.com/derailed/k9s/issues/294)
+ Help menu typo [Issue #291](https://github.com/derailed/k9s/issues/291)
+ NetworkPolicy Support [Issue #289](https://github.com/derailed/k9s/issues/289)
+ Scaling replicas start count [Issue #288](https://github.com/derailed/k9s/issues/288)
+ CLI command arg support [Issue #283](https://github.com/derailed/k9s/issues/283)
+ YAML screen dump support [Issue #275](https://github.com/derailed/k9s/issues/275)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.8.4.md
================================================
# Release v0.8.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release.
---
## Resolved Bugs/Features
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.9.0.md
================================================
# Release v0.9.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
A lots of changes here in 0.9.0!! Please watch out for potential disturbance in the force as much code changed on this drop...
Figured, I'll put a quick video out for you to checkout the latest [K9s V0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I)
### Support K8s 1.16
As you might have heard K8s had a big drop with 1.16 so we've added client/server support this new kubernetes release.
### Alias Alas!
K9s now supports standard kubernetes short name. Major shoutout to [Gustavo](https://github.com/paivagustavo) for making this painful change happen!
With this change is place you can now use all standard K8s short names along with defining your own. You can now define a new alias file aka `alias.yml` in your k9s home directory `$HOME/.k9s`. An alias is made up of a command and a group/version/resource aka GVR specification as follows:
```yaml
alias:
fred: apps/v1/deployments # Typing fred while in command mode will list out deployments
pp: v1/pods # Typing pp while in command mode will list out pods
```
### Plug For Plugins
As of this release and based on some users feedback we've moved the plugin section that used to live in the main K9s configuration file out to its own file. So as of this release we've added a new file in K9s home dir called `plugin.yml`. This is where you will define/share your K9s plugins and define your own commands and menu mnemonics. Here is an example for defining a custom command to show logs.
```yaml
# plugin.yml
plugin:
fred:
shortCut: Ctrl-L
description: "Pod logs"
scopes:
- po
command: /usr/local/bin/kubectl
background: false
args:
- logs
- -f
- $NAME
- -n
- $NAMESPACE
- --context
- $CONTEXT
```
Special K9s env vars you will have access to are currently for your commands or shell scripts are as follows:
* NAMESPACE
* NAME
* CLUSTER
* CONTEXT
* USER
* GROUPS
* COL[0-9+]
I will setup a plugin/alias repo so we can share these with all K9sers. Please ping me if interested in contributing/sharing your commands. Thank you!!
### Aye Aye Capt'ain!!
Hopefully improved overall navigation...
#### Real Estate
This release allows you to maximize screen real estate via 2 combos. First, the command/filter prompt is now hidden. To enter commands or filters you can type `:` or `/` to type your commands. Second, you can toggle the header using `CTRL-H`.
#### Bett'a ShortCuts
You can now use commands like `svc fred` while in command mode to directly navigate to a resource in a given namespace. Likewise to switch contexts you can now enter `ctx blee` to switch out clusters.
#### Sticky Filters
You can now keep filters sticky allowing you to filter a view bases on regex, fuzzy or labels and keep the filter live while switching resources. This provides for a horizontal navigation to view the various resources for a given application. Thank you so much [Nobert](https://github.com/ncsibra) for your continuous awesome feedback!!
### New Resources
Added support for StorageClass, you can now view this resource and describe it directly in K9s. Major shoutout to [Oscar F](https://github.com/fridokus), zero go chops and yet managed to push this PR thru with minimal support. You Sir, blew me away. Thank you!!
---
## Resolved Bugs/Features
* [Issue #318](https://github.com/derailed/k9s/issues/318)
* [Issue #303](https://github.com/derailed/k9s/issues/303)
* [Issue #301](https://github.com/derailed/k9s/issues/301)
* [Issue #300](https://github.com/derailed/k9s/issues/300)
* [Issue #276](https://github.com/derailed/k9s/issues/276)
* [Issue #268](https://github.com/derailed/k9s/issues/268)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.9.1.md
================================================
# Release v0.9.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release
---
## Resolved Bugs/Features
* [Issue #325](https://github.com/derailed/k9s/issues/325)
* [Issue #326](https://github.com/derailed/k9s/issues/326)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.9.2.md
================================================
# Release v0.9.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
I am absolutely blown away by your support and excitement about K9s! As I can recall, this is the first drop since we've launched K9s
back in January 2019 that I've seen some many external contributions and PRs. Thank you!! This is both super exciting and humbling.
### Core +1
As you may have noticed, there is a new voice on the project. [Gustavo Silva Paiva](https://github.com/paivagustavo) kindly accepted to become a K9s core member. Gustavo has been following and contributing to K9s for a while now and have patiently plowed thru my code ;( Raising issues, fixing them, improving code and test coverage, he has demonstrated a genuine interest on making sure K9s is better for all of us.
Actually, I can say enough about Gustavo since I don't know him that well yet ;) But I can tell from my interactions with him that he is a great human being, smart, kind and consensus and hence an awesome K9s addition. Please help me in welcoming him to the K9s pac!
### Breaking Bad
There was an issue with the header toggle mnemonic `Ctrl-H` and it has been changed in this release to just `h`. Thank you for the heads up [Swe Covis](https://github.com/swe-covis)!!
## Merged PRs
* [PR #365](https://github.com/derailed/k9s/pull/365) Fix Alias columns sorting.
* [PR #363](https://github.com/derailed/k9s/issues/363) Change Terminated to Terminating
* [PR #360](https://github.com/derailed/k9s/pull/360) Header toggle while typing commands
* [PR #359](https://github.com/derailed/k9s/pull/359) Add support for CRD v1beta1
* [PR #356](https://github.com/derailed/k9s/pull/356) Remove Object field from CRD yaml
* [PR #347](https://github.com/derailed/k9s/pull/347) Sort node roles
* [PR #346](https://github.com/derailed/k9s/pull/346) Optimize configmap and secret rendering
* [PR #342](https://github.com/derailed/k9s/pull/342) Add copy YAML to clipboard
* [PR #338](https://github.com/derailed/k9s/pull/338) Escape describe text
* [PR #330](https://github.com/derailed/k9s/pull/330) Don't override standard K8s short names
* [PR #324](https://github.com/derailed/k9s/pull/324) Leverage cached client to speed up K9s
---
## Resolved Bugs/Features
* [Issue #361](https://github.com/derailed/k9s/issues/361)
* [Issue #341](https://github.com/derailed/k9s/issues/341)
* [Issue #335](https://github.com/derailed/k9s/issues/335)
* [Issue #331](https://github.com/derailed/k9s/issues/331)
* [Issue #323](https://github.com/derailed/k9s/issues/323)
* [Issue #280](https://github.com/derailed/k9s/issues/280)
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_0.9.3.md
================================================
# Release v0.9.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release
## Merged PRs
* [PR #385](https://github.com/derailed/k9s/pull/385) Remove debugging calls from HPA
* [PR #384](https://github.com/derailed/k9s/issues/384) Invalidate cache when switching context
* [PR #372](https://github.com/derailed/k9s/pull/372) Fix race when switching context
---
## Resolved Bugs/Features
* [Issue #392](https://github.com/derailed/k9s/issues/392)
* [Issue #389](https://github.com/derailed/k9s/issues/389)
* [Issue #386](https://github.com/derailed/k9s/issues/386)
* [Issue #383](https://github.com/derailed/k9s/issues/383)
* [Issue #382](https://github.com/derailed/k9s/issues/382)
* [Issue #336](https://github.com/derailed/k9s/issues/336) NOTE: Sticky filters have been removed for now until we have a better plan.
---
© 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.0.md
================================================
# Release v0.13.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
### GitHub Sponsors
I'd like to personally thank the following folks for their support and efforts with this project as I know some of you have been around since it's inception almost a year ago!
* [Norbert Csibra](https://github.com/ncsibra)
* [Andrew Roth](https://github.com/RothAndrew)
* [James Smith](https://github.com/sedders123)
* [Daniel Koopmans](https://github.com/fsdaniel)
Big thanks in full effect to you all, I am so humbled and honored by your kind actions!
### Dracula Skin
Since we're in the thank you phase, might as well lasso in [Josh Symonds](https://github.com/Veraticus) for contributing the `Dracula` K9s skin that is now available in this repo under the skins directory. Here is a sneak peek of what K9s looks like under that skin. I am hopeful that like minded `graphically` inclined K9sers will contribute cool skins for this project for us to share/use in our Kubernetes clusters.
### XRay Vision!
Since we've launched K9s, we've longed for a view that would display the relationships among resources. For instance, pods may reference configmaps/secrets directly via volumes or indirectly with containers referencing configmaps/secrets via say env vars. Having the ability to know which pods/deployments use a given configmap may involve some serious `kubectl` wizardry. K9s now has xray vision which allows one to view and traverse these relationships/associations as well as check for referential integrity.
For this, we are introducing a new command aka `xray`. Xray initially supports the following resources (more to come later...)
1. Deployments
2. Services
3. StatefulSets
4. DaemonSets
To enable cluster xray vision for deployments simply type `:xray deploy`. You can also enter the resource aliases/shortnames or use the alias `x` for `xray`. Some of the commands available in table view mode are available here ie describe, view, shell, logs, delete, etc...
Xray not only will tell you when a resource is considered `TOAST` ie the resource is in a bad state, but also will tell you if a dependency is actually broken via `TOAST_REF` status. For example a pod referencing a configmap that has been deleted from the cluster.
Xray view also supports for filtering the resources by leveraging regex, labels or fuzzy filters. This affords for getting more of an application `cross-cut` among several resources.
As it stands Xray will check for following resource dependencies:
* pods
* containers
* configmaps
* secrets
* serviceaccounts
* persistentvolumeclaims
Keep in mind these can be expensive traversals and the view is eventually consistent as dependent resources will be lazy loaded.
We hope you'll find this feature useful? Keep in mind this is an initial drop and more will be coming in this area in subsequent releases. As always, your comments/suggestions are encouraged and welcomed.
### Breaking Change Header Toggle
It turns out the 'h' to toggle header was a bad move as it is use by the view navigation. So we changed that shortcut to `Ctrl-h` to toggle the header expansion/collapse.
---
## Resolved Bugs/Features
* [Issue #494](https://github.com/derailed/k9s/issues/494)
* [Issue #490](https://github.com/derailed/k9s/issues/490)
* [Issue #488](https://github.com/derailed/k9s/issues/488)
* [Issue #486](https://github.com/derailed/k9s/issues/486)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.1.md
================================================
# Release v0.13.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
### XRay Reloaded?
In the last release excitement, forgot to link the video update. Check it out! [K9s Xray](https://www.youtube.com/watch?v=qaeR2iK7U0o). Yup, got a cold... The joy of transports on the flying Petri dishes we call airplanes ;(
Based on some reported issues, decided to axe the xray icons in this drop for portability sake and also added support for replicasets. So here is the official list of supported Xray resources.
1. Deployments
2. Services
3. StatefulSets
4. DaemonSets
5. ReplicaSets (New!)
Still work in progress... so please proceed with caution!
---
## Resolved Bugs/Features
* [Issue #498](https://github.com/derailed/k9s/issues/498)
* [Issue #497](https://github.com/derailed/k9s/issues/497)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.2.md
================================================
# Release v0.13.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
### XRay Reloaded. Part Duh!
Found a waffle thin issue in the Beryllium(Be) core causing K9s xray vision to only operate on one eye ;)
Should be all betta' now...
The `xray` command now takes an **optional** third argument for the target namespace ie `:xray dp fred` will show the Xray view for deployments in the `fred` namespace.
Supported resources:
* Pods
* Deployments
* Services
* StatefulSets
* DaemonSets
* ReplicaSets
Still watch out for that overbite!! hence please proceed with caution...
---
## Resolved Bugs/Features
* [Issue #500](https://github.com/derailed/k9s/issues/500)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.3.md
================================================
# Release v0.13.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
### XRay Now With Lipstick?
Call me old school, but Xray without icons made me a bit sad ;( Just like any engineer would, I do fancy eye candy once in a while...
So I've decided to revive the xray `icon` mode for the some of us that are not stuck with what I'd like to call `Jurassic` terminals.
To date, there was no way to skin the Xray view, so I've added a new xray skin config section that `currently` looks like this:
```yaml
# $HOME/.k9s/skin.yml
k9s:
body:
fgColor: dodgerblue
bgColor: black
logoColor: orange
...
xray:
fgColor: blue
bgColor: black
cursorColor: aqua
graphicColor: darkgoldenrod
# NOTE! Show xray in icon mode. Defaults to false!!
showIcons: true
```
So if your terminal does not support emoji's we're still cool...
---
## Resolved Bugs/Features
* [Issue #505](https://github.com/derailed/k9s/issues/505)
* [Issue #504](https://github.com/derailed/k9s/issues/504)
* [Issue #503](https://github.com/derailed/k9s/issues/503)
* [Issue #501](https://github.com/derailed/k9s/issues/501)
* [Issue #499](https://github.com/derailed/k9s/issues/499)
* [Issue #493](https://github.com/derailed/k9s/issues/493)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.4.md
================================================
# Release v0.13.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
Maintenance Release!
## GH Sponsors
A Big Thank You to the following folks that I've decided to dig in and give back!! 👏🙏🎊
Thank you for your gesture of kindness and for supporting K9s!! (not to mention for replenishing my liquids during oh-dark-thirty hours 🍺🍹🍸)
* [w11d](https://github.com/w11d)
* [vglen](https://github.com/vglen)
## CPU/MEM Metrics
A small change here based on [Benjamin](https://github.com/binarycoded) excellent PR! We've added 2 new columns for pod/container views to indicate percentages of resources request/limits if set on the containers. The columns have been renamed to represent the resources requests/limits as follows:
| Name | Description | Sort Keys |
|--------|--------------------------------|-----------|
| %CPU/R | Percentage of requested cpu | shift-x |
| %MEM/R | Percentage of requested memory | shift-z |
| %CPU/L | Percentage of limited cpu | ctrl-x |
| %MEM/L | Percentage of limited memory | ctrl-z |
---
## Resolved Bugs/Features
* [Issue #507](https://github.com/derailed/k9s/issues/507) ??May be??
* [PR #489](https://github.com/derailed/k9s/issues/489) ATTA Boy! [Benjamin](https://github.com/binarycoded)
* [PR #491](https://github.com/derailed/k9s/issues/491) Big Thanks! [Bjoern](https://github.com/bjoernmichaelsen)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.5.md
================================================
# Release v0.13.5
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
Maintenance Release!
---
## Resolved Bugs/Features
* [Issue #507](https://github.com/derailed/k9s/issues/507)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.6.md
================================================
# Release v0.13.6
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
### GH Sponsorships
WOOT!! Big Thank you in this release to [shiv3](https://github.com/shiv3) for your contributions and support for K9s!
Duly noted and so much appreciated!!
---
### Bow Or Stern?
Some of you had voiced wanting to enable the multi pod logger [Stern](https://github.com/wercker/stern) from the good folks at [Wercker](https://github.com/wercker). Well now you can!
To make this work the awesome [Tuomo Syvänperä](https://github.com/syvanpera) contributed a PR to enable to plug this in with K9s. Thank you Tuomo!!
By default the filter will be set to the currently selected pod. If you need to change the filter, simply filter the pod view to using your own regex and that's the filter K9s will use. Here is a sample plugin that defines a new K9s shortcut to launch Stern provided of course it is installed on your box...
```yaml
# K9s plugin.yml
plugin:
stern:
shortCut: Ctrl-L
description: "Logs (Stern)"
scopes:
- pods
command: /usr/local/bin/stern # NOTE! Look for the command at this location.
background: false
args:
- --tail
- 50
- $FILTER # NOTE! Pulls the filter out of the pod view.
- -n
- $NAMESPACE
- --context
- $CONTEXT
```
---
## Resolved Bugs/Features/PRs
* [Issue #507](https://github.com/derailed/k9s/issues/507)
* [PR #510](https://github.com/derailed/k9s/pull/510) Thank you!! [Vimal Kumar](https://github.com/vimalk78)
* [PR #340](https://github.com/derailed/k9s/pull/340) ATTA Boy! [Tuomo Syvänperä](https://github.com/syvanpera)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.7.md
================================================
# Release v0.13.7
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
### GH Sponsorships
WOOT!! Big Thank you in this release to [Matthew Davis](https://github.com/mateothegreat) for your contributions and support for K9s!
---
## Resolved Bugs/Features/PRs
* [Issue #520](https://github.com/derailed/k9s/issues/520)
* [Issue #518](https://github.com/derailed/k9s/issues/518)
* [Issue #517](https://github.com/derailed/k9s/issues/517)
* [Issue #516](https://github.com/derailed/k9s/issues/516)
* [Issue #506](https://github.com/derailed/k9s/issues/506)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.13.8.md
================================================
# Release v0.13.8
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
### GH Sponsorships
WOOT!! Big Thank you to [Mark Baumann](https://github.com/mtreeman) for your contributions and support for K9s!
---
## Resolved Bugs/Features/PRs
* [Issue #523](https://github.com/derailed/k9s/issues/523)
* [Issue #522](https://github.com/derailed/k9s/issues/522)
* [Issue #521](https://github.com/derailed/k9s/issues/521)
* [PR #524](https://github.com/derailed/k9s/pull/524) Big Thanks!! [Joscha](https://github.com/joscha-alisch)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.14.0.md
================================================
# Release v0.14.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## Happy Birthday K9s!!
🎉🥳🎊 Doh! Almost missed it... 🎉🥳🎊
Yes sir, it's been a year (already...) since K9s was first launched 🎉. I can't tell you what a year this has been 🙀. Difficult? sure. However, you guys are making this project a total gas, by your candor, kindness and for giving back via your creative issues, prs, sponsorships, slack channel help to name a few... I do think, you've all been all too quiet tho 🐭... So if K9s helps make your K8s life bett'a on a day to day basis, please reach out for your shoe-phones and dial up [@kitesurfer](https://twitter.com/kitesurfer) or write an article/blog and share it! Lastly I am so humbled by this... but we're closing on 5k stars/136k downloads in this repo, so please invite 28 of your closest friends soon...
Major Thanks to all of you for you patience and for making this project a reality to all our K8s friends! You're all redefining awesomeness!!
Also I'd like to take this opportunity to recognize and thank a few folks that have willingly volunteered their own time to track down issues and help improve K9s for all of us!!
* [Gustavo Silva Paiva](https://github.com/paivagustavo)
* [Joscha Alisch](https://github.com/joscha-alisch)
* [Michael Christina](https://github.com/mcristina422)
* [Bruno Meneguello](https://github.com/bkmeneguello)
* [Tuomo Syvänperä](https://github.com/syvanpera)
* [Oskar F](https://github.com/fridokus)
* [Bruno Ohms](https://github.com/brunohms)
* [IgorRamalho](https://github.com/IgorRamalho)
* [Benjamin](https://github.com/binarycoded)
* [Norbert Csibra](https://github.com/ncsibra)
* [Andrew Roth](https://github.com/RothAndrew)
* [Sgandon](https://github.com/sgandon)
* [Chris Werner Rau](https://github.com/cwrau)
* [Eldad Assis](https://github.com/eldada)
* [Tobias](https://github.com/mycrEEpy)
* [Helge Sychla](https://github.com/hsychla)
* [Makusi75](https://github.com/Makusi75)
* [Swe-Covis](https://github.com/swe-covis)
* [Evgeniy Shubin](https://github.com/com30n)
## Search Enabled For Describe/YAML views
In this drop we made the Describe/YAML views searchable. So you no longer need to plow thru your resource configurations and get directly to the gist of it by using the search command ie `/elvis` + `enter`. You can use the familiar keys `n` and `N` to nav back and forth to the next occurrence in a circular buffer fashion once you've reached the BOF/EOF. It's the little things in life...
## And On Another Note...
More bugz...😿
## Resolved Bugs/Features/PRs
* [Issue #536](https://github.com/derailed/k9s/issues/536)
* [Issue #526](https://github.com/derailed/k9s/issues/526)
* [Issue #464](https://github.com/derailed/k9s/issues/464)
* [PR #532](https://github.com/derailed/k9s/pull/532) Thank you!! [Joscha Alisch](https://github.com/joscha-alisch)
* [PR #525](https://github.com/derailed/k9s/pull/525) Big Thanks!! [darklore](https://github.com/darklore)
* [PR #524](https://github.com/derailed/k9s/pull/524) Thank you (Again)!! [Joscha Alisch](https://github.com/joscha-alisch)
* [PR #514](https://github.com/derailed/k9s/pull/514) ATTA Boy!! [Alexander F. Rødseth](https://github.com/xyproto)
* [PR #483](https://github.com/derailed/k9s/pull/483) Thank you!! [Paul Varache](https://github.com/paulvarache)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.14.1.md
================================================
# Release v0.14.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## Term Color Part Duh!
Some folks had reported issues with skins and wanting to preserve their terminal background colors while in K9s. In this drop, we're introducing a new skin setting called `default` that should enable the skin to keep the original terminal background color. Here is a sample skin snippet that should achieve just that:
```yaml
# .k9s/pale_rider.yml
# Styles...
fg: &fg "#ff00ff"
bg: &bg "default" # default keeps your current terminal window background color.
# Skin...
k9s:
body:
fgColor: *fg
bgColor: "default"
#...
```
## Resolved Bugs/Features/PRs
* [Issue #539](https://github.com/derailed/k9s/issues/539)
* [Issue #538](https://github.com/derailed/k9s/issues/538) Fingers crossed!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.15.0.md
================================================
# Release v0.15.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## Seen This Fez Before?
The awesome and ever so smart and creative [Alex Ellis](https://github.com/alexellis) of [OpenFaas Fame](https://www.openfaas.com) fame, had pinged me when I had launched K9s to add support for OpenFaas functions. It's been a long time coming indeed, but we now have a very (VERY!) primitive integration with this very cool framework.
The current approach is to enable a few environment variables to tell K9s that you have an OpenFaas cluster available namely:
```shell
OPENFAAS_GATEWAY=http://YOUR_CLUSTER_IP:31112
OPENFAAS_TLS_INSECURE=false
OPENFAAS_JWT_TOKEN=YOUR_TOKEN
```
These will tell K9s that an OpenFaas gateway is available and exposed on a given nodeport.
Next you can navigate to your OpenFaas function view by entering command mode `:openfaas` or using aliases `:ofaas` or `ofa`
If functions are present in the given namespace they will be displayed here just like any other K8s resources.
The following operations are currently supported:
* Describe and YAML to view function definitions (Note: currently yields same results!)
* Enter to view all pods instances associated with the selected function
* Delete a function
* Editing, shelling, logs, etc... are all supported by navigating to the underlying pods
Keep in mind, the paint is way fresh here and this feature could be a complete dud, but figure will give it a rinse on this drop and Alex can pipe in and helps us ironing this out.
> NOTE! It's been a while since I've played with OpenFaas so if some of you are more versed in this space by all means please do land a hand so we can make this feature more awesome!
## Moving Forward!
A few folks had mentioned the eagerness to port-forward directly from a pod or a service. Well now you can! Port Forwarding is now available on both the pod view and services view. Note! at the end of the day, you are still port-forwarding to a container! So the port-forward dialog is a bit different for these views as there might be several container ports available now when looking at this from a pod perspective. So the first field in the dialog is a combo-box that allows one to pick their desired ports. The rest of the dialog works the same as the container port-forward dialog.
## Resolved Bugs/Features/PRs
* [Issue #546](https://github.com/derailed/k9s/issues/546) BREAKING NEWS! Use `t` vs `ctrl-h` now to toggle the K9s header
* [Issue #541](https://github.com/derailed/k9s/issues/541)
* [Issue #227](https://github.com/derailed/k9s/issues/227)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.15.1.md
================================================
# Release v0.15.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## OpenFeZ Reloaded?
🙀With feelings and one less bugZ!
The awesome and ever so smart and creative [Alex Ellis](https://github.com/alexellis) of [OpenFaas Fame](https://www.openfaas.com) fame, had pinged me when I had launched K9s to add support for OpenFaas functions. It's been a long time coming indeed, but we now have a very (VERY!) primitive integration with this very cool framework.
The current approach is to enable a few environment variables to tell K9s that you have an OpenFaas cluster available namely:
```shell
OPENFAAS_GATEWAY=http://YOUR_CLUSTER_IP:31112
OPENFAAS_TLS_INSECURE=false
OPENFAAS_JWT_TOKEN=YOUR_TOKEN
```
These will tell K9s that an OpenFaas gateway is available and exposed on a given nodeport.
Next you can navigate to your OpenFaas function view by entering command mode `:openfaas` or using aliases `:ofaas` or `ofa`
If functions are present in the given namespace they will be displayed here just like any other K8s resources.
The following operations are currently supported:
* Describe and YAML to view function definitions (Note: currently yields same results!)
* Enter to view all pods instances associated with the selected function
* Delete a function
* Editing, shelling, logs, etc... are all supported by navigating to the underlying pods
Keep in mind, the paint is way fresh here and this feature could be a complete dud, but figure will give it a rinse on this drop and Alex can pipe in and helps us ironing this out.
> NOTE! It's been a while since I've played with OpenFaas so if some of you are more versed in this space by all means please do land a hand so we can make this feature more awesome!
## Moving Forward!
A few folks had mentioned the eagerness to port-forward directly from a pod or a service. Well now you can! Port Forwarding is now available on both the pod view and services view. Note! at the end of the day, you are still port-forwarding to a container! So the port-forward dialog is a bit different for these views as there might be several container ports available now when looking at this from a pod perspective. So the first field in the dialog is a combo-box that allows one to pick their desired ports. The rest of the dialog works the same as the container port-forward dialog.
## Resolved Bugs/Features/PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.15.2.md
================================================
# Release v0.15.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## Mo PortForwards...
While putting together the [OpenFeeZ video](https://youtu.be/7Fx4XQ2ftpM), I've noticed a few issues with port-forwards and benchmarks. While I was doing surgery on that carp, figured why not go pull a full monty on port-forwards and enable for other controller like resources such as deployments, statefulsets and daemonsets. So now you can set up port-forwards on any of these using `shift-f`. This exhibits the same mechanics as service based port-forwards ie pick a container port from pods matching the controller selector.
## Resolved Bugs/Features/PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.16.0.md
================================================
# Release v0.16.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
This is one of these drops that may make you wonder if you'll go from zero to hero or likely the reverse?? Will see how this goes... Please proceed with caution on this one as there could very well be much disturbances in the force...
Lots of code churns so could have totally hose some stuff, but like my GranPappy used to say `can't cook without making a mess!`
## Going Wide?
In this drop, we've enabled a new shortcut namely `wide` as `Ctrl-w`. On table views, you will be able to see more information about the resources such as labels or others depending on the viewed resource. This mnemonic works as a toggle so you can `narrow` the view by hitting it again.
## Zoom, Zoom, Zoom!
While viewing some resources that may contain errors, sorting on columns may not achieve the results you're seeking ie `show me all resources in an error state`. We've added a new option to achieve just that aka `zoom errors` as `ctrl-z`. This works as a toggle and will unveil resources that are need of some TLC on your part ;)
## Does Your Cluster Have A Pulse 💓?
In this drop, we're introducing a brand new view aka `K9s Pulses` 💓. This is a summary view listing the most salient resources in your clusters and their current states. This view tracks two main metrics ie Ok and Toast on a 5sec beat. This view affords cluster activity and failure rates. BTW this is the zero to hero deal 🙀 Hopefully you'll dig it as this was much work to put together and I personally think it's the `ducks nuts`... If you like, please give me some luving on social or via GH sponsors as batteries are running low...
To active, enter command mode by typing in `:pulse` aliases are `pu`, `pulses` or `hz`
To navigate thru the various pulses, you can use `tab`/`backtab` or use the menu index (just like namespaces selectors). Once on a pulse view, you can press `enter` to see the associated resource table view. Pressing `esc` will nav you back.
As I've may have mentioned before, my front-end/UX FU is weak, so I've also added a way for you to skin the charts via skins yaml to your own liking. Please see the skin section below for an example on how to skin the pulses dials. BONUS you should be able to skin K9s live! How cool is that 😻?
NOTE: Pulses are very much experimental and could totally bomb on your clusters! So please thread carefully and please do report (kindly!) back.
## BReaking Bad!
In this drop I've broken a few things (that I know of...), here is the list as I can recall...
1. Toggle header aka `my red headed step child`. Key moved (again!) now `Ctrl-e`
2. Skin yaml layout CHANGED! Moved table and xray sections under views and added charts section.
## Skins Updates!
The skin file format CHANGE! If you are running skins with K9s, please make sure to update your skin file. If not K9s could bomb coming up!
NOTE: I don't think I'll get around to update all the contributed skins in this repo `skins` dir. If you're looking for a way to help out and are UI inclined, please take a peek and make them cool!
```yaml
# my_cluster_skin.yml
# Styles...
foreground: &foreground "#f8f8f2"
background: &background "#282a36"
current_line: ¤t_line "#44475a"
selection: &selection "#44475a"
comment: &comment "#6272a4"
cyan: &cyan "#8be9fd"
green: &green "#50fa7b"
orange: &orange "#ffb86c"
pink: &pink "#ff79c6"
purple: &purple "#bd93f9"
red: &red "#ff5555"
yellow: &yellow "#f1fa8c"
# Skin...
k9s:
# General K9s styles
body:
fgColor: *foreground
bgColor: *background
logoColor: *purple
# ClusterInfoView styles.
info:
fgColor: *pink
sectionColor: *foreground
frame:
# Borders styles.
border:
fgColor: *selection
focusColor: *current_line
menu:
fgColor: *foreground
keyColor: *pink
# Used for favorite namespaces
numKeyColor: *purple
# CrumbView attributes for history navigation.
crumbs:
fgColor: *foreground
bgColor: *current_line
activeColor: *current_line
# Resource status and update styles
status:
newColor: *cyan
modifyColor: *purple
addColor: *green
errorColor: *red
highlightcolor: *orange
killColor: *comment
completedColor: *comment
# Border title styles.
title:
fgColor: *foreground
bgColor: *current_line
highlightColor: *orange
counterColor: *purple
filterColor: *pink
views:
charts:
bgColor: *background
dialBgColor: "#0A2239"
chartBgColor: "#0A2239"
defaultDialColors:
- "#1E3888"
- "#820101"
defaultChartColors:
- "#1E3888"
- "#820101"
resourceColors:
batch/v1/jobs:
- "#5D737E"
- "#820101"
v1/persistentvolumes:
- "#3E554A"
- "#820101"
cpu:
- "#6EA4BF"
- "#820101"
mem:
- "#17505B"
- "#820101"
v1/events:
- "#073B3A"
- "#820101"
v1/pods:
- "#487FFF"
- "#820101"
# TableView attributes.
table:
fgColor: *foreground
bgColor: *background
cursorColor: *current_line
# Header row styles.
header:
fgColor: *foreground
bgColor: *background
sorterColor: *cyan
# Xray view attributes.
xray:
fgColor: *foreground
bgColor: *background
cursorColor: *current_line
graphicColor: *purple
showIcons: true
# YAML info styles.
yaml:
keyColor: *pink
colonColor: *purple
valueColor: *foreground
# Logs styles.
logs:
fgColor: *foreground
bgColor: *background
```
## Resolved Bugs/Features/PRs
- [Issue #557](https://github.com/derailed/k9s/issues/557)
- [Issue #555](https://github.com/derailed/k9s/issues/555)
- [Issue #554](https://github.com/derailed/k9s/issues/554)
- [Issue #553](https://github.com/derailed/k9s/issues/553)
- [Issue #552](https://github.com/derailed/k9s/issues/552)
- [Issue #551](https://github.com/derailed/k9s/issues/551)
- [Issue #549](https://github.com/derailed/k9s/issues/549) A start with pulses...
- [Issue #540](https://github.com/derailed/k9s/issues/540)
- [Issue #421](https://github.com/derailed/k9s/issues/421)
- [Issue #351](https://github.com/derailed/k9s/issues/351) Solved by Pulses?
- [Issue #25](https://github.com/derailed/k9s/issues/25) Pulses? Oldie but goodie!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.16.1.md
================================================
# Release v0.16.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
- [Issue #561](https://github.com/derailed/k9s/issues/561)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.0.md
================================================
# Release v0.17.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
## Custom Columns? Yes Please!!
[SneakCast v0.17.0 on The Beach! - Yup! sound is sucking but what a setting!](https://youtu.be/7S33CNLAofk)
In this drop I've reworked the rendering engine to provide for custom columns support. Now, you should be able to not only tell K9s which columns you would like to display but also which order they should be in.
To surface this feature, you will need to create a new configuration file, namely `$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!
> 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
# $HOME/.k9s/views.yml
k9s:
views:
v1/pods:
columns:
- AGE
- NAMESPACE
- NAME
- IP
- NODE
- STATUS
- READY
v1/services:
columns:
- AGE
- NAMESPACE
- NAME
- TYPE
- CLUSTER-IP
```
## Resolved Bugs/Features/PRs
- [Issue #581](https://github.com/derailed/k9s/issues/581)
- [Issue #576](https://github.com/derailed/k9s/issues/576)
- [Issue #574](https://github.com/derailed/k9s/issues/574)
- [Issue #573](https://github.com/derailed/k9s/issues/573)
- [Issue #571](https://github.com/derailed/k9s/issues/571)
- [Issue #566](https://github.com/derailed/k9s/issues/566)
- [Issue #563](https://github.com/derailed/k9s/issues/563)
- [Issue #562](https://github.com/derailed/k9s/issues/562)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.1.md
================================================
# Release v0.17.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
- [Issue #584](https://github.com/derailed/k9s/issues/584)
- [Issue #583](https://github.com/derailed/k9s/issues/583)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.2.md
================================================
# Release v0.17.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
- [Issue #592](https://github.com/derailed/k9s/issues/592)
- [Issue #591](https://github.com/derailed/k9s/issues/591)
- [Issue #590](https://github.com/derailed/k9s/issues/590)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.3.md
================================================
# Release v0.17.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please 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)
---
Maintenance Release!
- Reworked Pulses view counters and layout.
- Switching context now will take you to that context last view if available vs the pod view.
- Reworked info/version layout.
## Resolved Bugs/Features/PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.4.md
================================================
# Release v0.17.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## Pulses Part Duh!
In this drop, we've updated pulses to now show used/allocatable resources for cpu and mem as recommended by the awesome and kind [Eldad Assis](https://github.com/eldada)! We've also added the concept of threshold to alert you when things in your clusters are going south. These currently come in the shape of cpu and mem thresholds. They are set at the cluster level. K9s will now let you know when these limits are reached or surpassed. As it stands, the k9s logo will change color and a flash message will appear to let you know which resource threshold was exceeded. Once the load subsumes the logo/flash will return to their original states.
In order to override the default thresholds (cpu/mem: 80% ), you will need to modify your `$HOME/.k9s/config.yml` using the new config section named `thresholds` as follows:
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
headless: false
...
# Specify resources thresholds percentages
thresholds:
cpu: 80 # default is 80
memory: 55 # default is 80
...
```
## Resolved Bugs/Features/PRs
- [Issue #596](https://github.com/derailed/k9s/issues/596)
- [Issue #593](https://github.com/derailed/k9s/issues/593)
- [Issue #560](https://github.com/derailed/k9s/issues/560)
- NOTE!! All credits here goes to [Bruno Meneguello](https://github.com/bkmeneguello) and [Michael Cristina](https://github.com/mcristina422) for making this possible in K9s!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.5.md
================================================
# Release v0.17.5
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## Thresholds Reloaded!
In the previous k9s release, we've introduced the notion of thresholds to provide with an alert mechanism when either the cpu or memory goes high on your clusters. Looking at the current solution, we felt we needed a bit more granularity in the severity levels thanks to [Eldad Assis](https://github.com/eldada) feedback on this one! So here is the new configuration for cluster thresholds. Please keep in mind this feature is still in flux!
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
headless: false
...
# Specify resources thresholds in percent - defaults: critical=90, warn=70
thresholds:
cpu:
critical: 85
warn: 75
memory:
critical: 80
warn: 70
...
```
## Resolved Bugs/Features/PRs
- [Issue #604](https://github.com/derailed/k9s/issues/604)
- [Issue #601](https://github.com/derailed/k9s/issues/601) Thank you [Christian Vent](https://github.com/christian-vent)
- [Issue #598](https://github.com/derailed/k9s/issues/598) `Ctrl-l` will now trigger the benchmarking toggle!
- [Issue #593](https://github.com/derailed/k9s/issues/593)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.6.md
================================================
# Release v0.17.6
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## Get A Rope!
This was in the backlogs for a while, so I've decided to give it a bit of TLC. Thank you [Mitchell Maler](https://github.com/mitchellmaler) for this issue!
🏝Feeling your clusters could use a bit of spring cleaning 🧽🧼?
As of this drop, you can now perform direct cluster nodes maintenance by leveraging cordon `c`, uncordon `u` and drain `d` while in node view! Each operation comes with a dialog to either configure the options or confirm the operation. You dig?
## Resolved Bugs/Features/PRs
- [Issue #612](https://github.com/derailed/k9s/issues/612)
- [Issue #608](https://github.com/derailed/k9s/issues/608)
- [Issue #606](https://github.com/derailed/k9s/issues/606)
- [Issue #237](https://github.com/derailed/k9s/issues/237)
- [PR #607](https://github.com/derailed/k9s/pull/607) ATTA Boy! [Jeff Widman](https://github.com/jeffwidman)
- [PR #570](https://github.com/derailed/k9s/pull/570) Thank you [Ludovico Rosso](https://github.com/ludusrusso)!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.17.7.md
================================================
# Release v0.17.7
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## 🙀(PLUGIN-19)
[Br]eaking [Ba]d on K9s plugins! In previous releases, we used the COL
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.18.0.md
================================================
# Release v0.18.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## GH Sponsors
Big `ThankYou` to the following folks that I've decided to dig in and give back!! 👏🙏🎊
Thank you for your gesture of kindness and for supporting K9s!!
* [Bob Johnson](https://github.com/bbobjohnson)
* [Poundex](https://github.com/Poundex)
* [thllxb](https://github.com/thllxb)
If you've contributed $25 or more please reach out to me on slack with your earth coordinates so I can send you your K9s swags!
NOTE: I am one not to pressure folks into giving. However, it does make me sad to see postings out there with clear indications that K9s is being used and yet zero mentions of the web site nor this repo. K9s marketing budget relies entirely on word of mouth and is not pimped out by big corps. So if you publish your work and leverage K9s, please give us a shoutout or at the very least reference this repo or website!
---
## AutoSuggestions
K9s command mode now provides for auto complete. Suggestions are pulled from available kubernetes resources and custom aliases. The command mode supports the following keyboard triggers:
| Key | Description |
|---------------------|------------------------------------------|
| ⬆️ ⬇️ | Navigate up or down thru the suggestions |
| `Ctrl-w`, `Ctrl-u` | Clear out the command |
| `Tab`, `Ctrl-f`, ➡️ | Accept the suggestion |
## Logs Revisited
Breaking Change! This drop changes how logs are viewed and configured. The log view now support for pulling logs based on the log timeline current settings are: all, 1m, 5m, 15m and 1h. The following log configuration is in effect as of this drop:
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
readOnly: false
# NOTE: New logger configuration!
logger:
tail: 200 # Tail the last 100 lines. Default 100
buffer: 5000 # Max number of lines displayed in the view. Default 1000
sinceSeconds: 900 # Displays the last x seconds from the logs timeline. Default 5m
...
```
## Resolved Bugs/Features/PRs
* [Issue #628](https://github.com/derailed/k9s/issues/628)
* [Issue #623](https://github.com/derailed/k9s/issues/623)
* [Issue #622](https://github.com/derailed/k9s/issues/622)
* [Issue #565](https://github.com/derailed/k9s/issues/565)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.18.1.md
================================================
# Release v0.18.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #632](https://github.com/derailed/k9s/issues/632)
* [Issue #631](https://github.com/derailed/k9s/issues/631)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.0.md
================================================
# Release v0.19.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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## A Word From Our Sponsors...
It makes me always very happy to hear folks are digging this effort and using K9s daily! If you feel this way please tell us and consider joining our [sponsorship](https://github.com/sponsors/derailed) program.
Big Thank You! to [hornbech](https://github.com/hornbech) for joining our sponsors!
## K8s v1.18.0 Support
As you might have heard, the good Kubernetes folks just dropped some big features in this new release. ATTA Girls/Boys!! We've (painfully) updated K9s to now link with the latest and greatest apis. Likely more work will need to take place here as I am still trying to catch up with the latest enhancements. This is great to see and excellent for all our Kubernetes friends!
## Oh Biffs'em And Buffs'em Popeye!
As you may know, I am the author of [Popeye](https://popeyecli.io) a Kubernetes cluster linter/sanitizer. Popeye scans your clusters live and reports potential issues, things like: referential integrity, misconfiguration, resource usage, etc...
In this drop, we've integrated K9s and Popeye to produce what I believe is a killer combo. Not only can you manage/observe your cluster resources in the wild, but you can now assert that your resources are indeed cool and potentially get rid of dead weights that might add up to your monthly cloud service bills. How cool is that?
In order to run your sanitization and produce reports, you can enter a new command `:popeye`. Once your cluster sanitization is complete, you can use familiar keyboard shortcuts to sort columns and view the sanitization reports by pressing `enter` on a given resource linter. Popeye also supports a configuration file namely `spinach.yml`, this file provides for customizing what resources get scanned as well as setting different severity levels to your own company policies. Please read the Popeye docs on how to customize your reports. The spinach.yml file will be read from K9s home directory `$HOME/.k9s/MY_CLUSTER_CONTEXT_NAME_spinach.yml`
NOTE! This is very much still experimental, so you may experience some disturbances in the force! And remember PRs are always open ;)
## Command History Support
K9s now supports for command history. Entering command mode via `:` you can now up/down arrow to navigate thru your command history. Pressing `tab` or `ctrl-e` or `->` will activate the selected command upon `enter`.
## K9s Icons
Some terminals often don't offer icon support. In this release there is a new option `noIcons` available to enable/disable K9s icons. By default this option is set `false`. You can now set your icon preference in the K9s config file as follows:
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
headless: false
readOnly: false
noIcons: true # Enable/Disable K9s icons display.
```
## Videos!
* [video v0.19.0](https://www.youtube.com/watch?v=kj-WverKZ24)
* [video v0.18.0](https://www.youtube.com/watch?v=zMnD5e53yRw)
## Resolved Bugs/Features/PRs
* [Issue #647](https://github.com/derailed/k9s/issues/647)
* [Issue #645](https://github.com/derailed/k9s/issues/645)
* [Issue #640](https://github.com/derailed/k9s/issues/640)
* [Issue #639](https://github.com/derailed/k9s/issues/639)
* [Issue #635](https://github.com/derailed/k9s/issues/635)
* [Issue #634](https://github.com/derailed/k9s/issues/634) Thank you!! [David Němec](https://github.com/davidnemec)
* [Issue #626](https://github.com/derailed/k9s/issues/626)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.1.md
================================================
# Release v0.19.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## A Word From Our Sponsors...
It makes me always very happy to hear folks are digging this effort and using K9s daily! If you feel this way please tell us and consider joining our [sponsorship](https://github.com/sponsors/derailed) program.
Big Thank You! to [Azar](https://github.com/azarudeena) for joining our sponsors!
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #649](https://github.com/derailed/k9s/issues/649)
* [PR #638](https://github.com/derailed/k9s/pull/638) Thank you! [Shang Yuanchun](https://github.com/ideal)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.2.md
================================================
# Release v0.19.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us 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)
---
## A Word From Our Sponsors...
It makes me always very happy to hear folks are digging this effort and using K9s daily! If you feel this way please tell us and consider joining our [sponsorship](https://github.com/sponsors/derailed) program.
Big Thank You! to the following folks for joining our program:
* [Nick Hobart](https://github.com/nwhobart)
* [Shopeonarope](https://github.com/shopeonarope)
Maintenance Release!
NOTE! During K9s update to support the latest version of Kubernetes (v1.18), K9s Helm charts support took one for the team ;( At this time Helm as yet to be released k8s v1.18 support. We will track for updates and enable this feature once HelmV3 releases it.
## Resolved Bugs/Features/PRs
* [Issue #665](https://github.com/derailed/k9s/issues/665)
* [Issue #662](https://github.com/derailed/k9s/issues/662)
* [PR #660](https://github.com/derailed/k9s/pull/660) Thank you! [Tomáš Pospíšek](https://github.com/tpo)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.3.md
================================================
# Release v0.19.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## Look Who Is Back?
Thanks to the good Helm folks, we're now back on par with the Helm charts support feature. As you may recall, when we've updated to K8s v1.18, the Helm feature took one for the team ;( as they had yet to upgrade to the latest k8s rev. So K9s Helm chart feature is back in this drop! On that note, we've added new aliases to allow you to view your currently installed Helm charts aka `helm` | `hm` | `chart` | `charts`.
## Boh-Bye Windows 386!
As of this drop, I've decided to axe Windows 386 support. Our good friend [Guy Barrette](https://github.com/guybarrette) reported K9s Windows-386 binary is tripping his virus scanner. After double checking my installed SHAs/binaries/dependencies/etc... and performing vulnerability scans on various win-i386 K9s binaries, I just could not figure out which dependencies are causing the exec to bomb on the scans??
Note: This does not necessary entails that there is a deliberate or malicious intent with the software, but likely a false positive thrown by the Windows virus scanner. This has been [reported](https://golang.org/doc/faq#virus) with other GO binaries on windows as well ;(
That said, I've repeatedly scanned the K9s Windows-x64 and ended up with a clean bill of health on every single scans. So I've decided to drop the 386 windows support for the time being. If that causes you some grief, please land a hand as I am fresh out of ideas...
## And Now For Something A Bit More... Controversial?
There has been a lot of requests for K9s to support shelling directly into cluster nodes. I was resisting the temptation to support this useful feature as depending on your cluster hosting solution, this involved less than ideal solutions. My clusters are provisioned in a multitude of platforms ranging from bare metal to cloud vendor self/managed hosting. I wanted the same experience shelling into an GKE/AWS node as a local KiND cluster node. To this end, we've opted to experimentally support shelling into nodes using the following approach:
1. While in the Node view, we are introducing a new `s` mnemonic to shell into nodes on your cluster.
2. K9s will spin up a `k9s-shell` pod in the `default` namespace with an official Busybox container running in `privileged` mode. This may require extra RBAC and PSPs (This will need Docs!)
3. Once shelled-in, you can poke around any of your nodes.
4. Upon exiting the node shell, K9s will automatically delete the `k9s-shell` pod for that node.
This feature is `OPT-IN` only ie you will need to manually enable the feature gate to make this functionality available to K9s on a specific cluster as follows:
```yaml
# $HOME/.k9s/config.yml
k9s:
...
clusters:
fred:
namespace:
active: "default"
favorites:
- default
view:
active: po
featureGates:
nodeShell: true # Defaults to false!
```
Please let us know if you dig this feature? This very much experimental and we're open to your suggestions. Thank you!
## New Sheriff In Town K9S_EDITOR
As you may know K9s currently uses your `EDITOR` env var to launch an editor while editing a k8s resource or viewing a screen dump or a performance benchmark. So folks voiced they are using some editors that require different CLI args when editing k8s resources vs files on disk. In this drop, we're introducing a new env var `K9S_EDITOR` to provide an affordance to deal with these discrepancies. If you are using emacs/vi/nano no action should be required. K9s will now check for `K9S_EDITOR` existence to view K9s artifacts such as screen_dumps. K9s still honors `KUBE_EDITOR` or `EDITOR` for K8s resource edits. K9s will fallback to the `EDITOR` env var if `K9S_EDITOR` is not set.
## Resolved Bugs/Features/PRs
* [Issue #669](https://github.com/derailed/k9s/issues/669)
* [Issue #677](https://github.com/derailed/k9s/issues/677)
* [Issue #673](https://github.com/derailed/k9s/issues/673)
* [Issue #671](https://github.com/derailed/k9s/issues/671)
* [Issue #670](https://github.com/derailed/k9s/issues/670)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.4.md
================================================
# Release v0.19.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## A Word From Out Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Jason Vance](https://github.com/jasonvance)
* [Jacob Gillespie](https://github.com/jacobwgillespie)
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #692](https://github.com/derailed/k9s/issues/692)
* [Issue #689](https://github.com/derailed/k9s/issues/689)
* [Issue #685](https://github.com/derailed/k9s/issues/685)
* [Issue #684](https://github.com/derailed/k9s/issues/684)
* [Issue #670](https://github.com/derailed/k9s/issues/670)
* [PR #688](https://github.com/derailed/k9s/pull/688) All credits goes to [David Němec](https://github.com/davidnemec)!!
* [PR #676](https://github.com/derailed/k9s/pull/676) Big Thanks to [Agrim Asthana](https://github.com/agrimrules)!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.5.md
================================================
# Release v0.19.5
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## A Word From Out Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Tommy Dejbjerg Pedersen](https://github.com/tpedersen123)
* [Matt Welke](https://github.com/mattwelke)
## Disruption In The Force
During this drop, I've gotten totally slammed by other forces ;( I've had so many disruptions that affected my `quasi` normal flow hence this drop might be a bit wonky ;( So please proceed with caution!!
As always please help me flush/report issues and I'll address them promptly! Thank you so much for your understanding and patience!! 🙏👨❤️👨😍
## Improved Node Shell Usability
In this drop we've changed the configuration of the node shell action that lets you shell into nodes. Big thanks to [Patrick Decat](https://github.com/pdecat) for helping us flesh out this beta feature! We've added configuration to not only customize the image but also the resources and namespace on how to run these K9s pods on your clusters. The new configuration is set at the cluster scope level.
Here is an example of the new pod shell config options:
```yaml
# $HOME/.k9s/config.yml
k9s:
clusters:
blee:
featureGates:
# You must enable the nodeShell feature gate to enable shelling into nodes
nodeShell: true
# NEW! You can now tune the pod specification: currently image, namespace and resources
shellPod:
image: cool_kid_admin:42
namespace: blee
limits:
cpu: 100m
memory: 100Mi
```
## Resolved Bugs/Features/PRs
* [Issue #714](https://github.com/derailed/k9s/issues/714)
* [Issue #713](https://github.com/derailed/k9s/issues/713)
* [Issue #708](https://github.com/derailed/k9s/issues/708)
* [Issue #707](https://github.com/derailed/k9s/issues/707)
* [Issue #705](https://github.com/derailed/k9s/issues/705)
* [Issue #704](https://github.com/derailed/k9s/issues/704)
* [Issue #702](https://github.com/derailed/k9s/issues/702)
* [Issue #700](https://github.com/derailed/k9s/issues/700) Fingers and toes crossed ;)
* [Issue #694](https://github.com/derailed/k9s/issues/694)
* [Issue #663](https://github.com/derailed/k9s/issues/663) Partially - should be better launching in a given namespace ie k9s -n fred??
* [Issue #702](https://github.com/derailed/k9s/issues/702)
* [PR #709](https://github.com/derailed/k9s/pull/709) All credits goes to [Namco](https://github.com/namco1992)!!
* [PR #706](https://github.com/derailed/k9s/pull/706) Big Thanks to [M. Tarık Yurt](https://github.com/mtyurt)!
* [PR #704](https://github.com/derailed/k9s/pull/704) Atta Boy!! [psvo](https://github.com/psvo)
* [PR #696](https://github.com/derailed/k9s/pull/696) Thank you! Credits to [Christian Köhn](https://github.com/ckoehn)
* [PR #691](https://github.com/derailed/k9s/pull/691) Mega Thanks To [Pavel Tumik](https://github.com/sagor999)!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.6.md
================================================
# Release v0.19.6
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [danysirota](https://github.com/danysirota)
* [lampapetrol](https://github.com/lampapetrol)
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #719](https://github.com/derailed/k9s/issues/719)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.19.7.md
================================================
# Release v0.19.7
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #726](https://github.com/derailed/k9s/issues/726)
* [Issue #724](https://github.com/derailed/k9s/issues/724)
* [Issue #722](https://github.com/derailed/k9s/issues/722)
* [Issue #721](https://github.com/derailed/k9s/issues/721)
* [Issue #720](https://github.com/derailed/k9s/issues/720)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.0.md
================================================
# Release v0.20.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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## ♫ The Sound Behind The Release ♭
And now for something a `beat` different?
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure right?
I've just discovered this Turkish band, that I dig and figured I'll share it with you while you read these release notes...
[Ruh - She Past Away](https://www.youtube.com/watch?v=B7f-opGKOyI)
NOTE! Mind you I grew up on the `The Cure`, so likely not for everyone here 🙀
## PortForward Revisited
While performing port-forwards, it could be convenient to specify a given IP address vs 'localhost'
for the forwarding host. For this reason, we are introducing a configuration setting that allows you to set the host IP address for the port-forward dialog on a per cluster basis. The IP address currently defaults to `localhost`.
Big Thanks and all credits goes to [Stowe4077](https://github.com/Stowe4077) (and that very cute dog!) for raising this issue in the first place!!
In order to change the configuration, edit your k9s config file as follows:
```yaml
k9s:
...
clusters:
blee:
namespace:
active: ""
favorites:
- fred
- default
view:
active: po
portForwardAddress: 1.2.3.4
```
## And We've Got A Floater!
I've been noodling on this feature for a while and thought it might be time to `float` this over to you guys... While operating on a cluster you may ask yourself: "Hum... wonder which resources use configmap `fred`?" Sure a quick grep through your manifests on disk will do fine, but what about the resources actually deployed on your cluster? Well my friends wonder no m'o, K9s knows!
While navigating to your ConfigMap View a new option will appear `UsedBy` pressing `u` will reveal any resources that are currently referencing that ConfigMap. As of this drop, this feature will be available for the usual suspects namely: ConfigMaps, Secrets and ServiceAccounts. K9s scans managing resources and locate references from Env vars, Volumes or ServiceAccounts.
NOTE: This feature is expensive to produce and might take a while to fully resolve on larger clusters! Also K9s referential scans might not be full proof and the paint is still fresh on this one so trade carefully! More resources refs checks will be enabled once we've rinse and repeat on this deal. We hope you'll find this feature useful, if so, please make some noise!
## Lastly...
There has been quick a bit of surgery going on with this drop, so this release could be a bit unstable. Please watch out for that carp overbite! As always, Thank You All for your understanding, support and patience!!
## Resolved Bugs/Features/PRs
- [Issue #734](https://github.com/derailed/k9s/issues/734)
- [Issue #733](https://github.com/derailed/k9s/issues/733)
- [Issue #716](https://github.com/derailed/k9s/issues/716)
- [Issue #693](https://github.com/derailed/k9s/issues/693)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.1.md
================================================
# Release v0.20.1
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## ♫ The Sound Behind The Release ♭
And now for something a `beat` different?
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure right?
I've just discovered this Turkish band, that I dig and figured I'll share it with you while you read these release notes...
[Ruh - She Past Away](https://www.youtube.com/watch?v=B7f-opGKOyI)
NOTE! Mind you I grew up on the `The Cure`, so likely not for everyone here 🙀
## PortForward Revisited
While performing port-forwards, it could be convenient to specify a given IP address vs 'localhost'
for the forwarding host. For this reason, we are introducing a configuration setting that allows you to set the host IP address for the port-forward dialog on a per cluster basis. The IP address currently defaults to `localhost`.
Big Thanks and all credits goes to [Stowe4077](https://github.com/Stowe4077) (and that very cute dog!) for raising this issue in the first place!!
In order to change the configuration, edit your k9s config file as follows:
```yaml
k9s:
...
clusters:
blee:
namespace:
active: ""
favorites:
- fred
- default
view:
active: po
portForwardAddress: 1.2.3.4
```
## And We've Got A Floater!
I've been noodling on this feature for a while and thought it might be time to `float` this over to you guys... While operating on a cluster you may ask yourself: "Hum... wonder which resources use configmap `fred`?" Sure a quick grep through your manifests on disk will do fine, but what about the resources actually deployed on your cluster? Well my friends wonder no m'o, K9s knows!
While navigating to your ConfigMap View a new option will appear `UsedBy` pressing `u` will reveal any resources that are currently referencing that ConfigMap. As of this drop, this feature will be available for the usual suspects namely: ConfigMaps, Secrets and ServiceAccounts. K9s scans managing resources and locate references from Env vars, Volumes or ServiceAccounts.
NOTE: This feature is expensive to produce and might take a while to fully resolve on larger clusters! Also K9s referential scans might not be full proof and the paint is still fresh on this one so trade carefully! More resources refs checks will be enabled once we've rinse and repeat on this deal. We hope you'll find this feature useful, if so, please make some noise!
## Lastly...
There has been quick a bit of surgery going on with this drop, so this release could be a bit unstable. Please watch out for that carp overbite! As always, Thank You All for your understanding, support and patience!!
## Resolved Bugs/Features/PRs
- [Issue #734](https://github.com/derailed/k9s/issues/734)
- [Issue #733](https://github.com/derailed/k9s/issues/733)
- [Issue #716](https://github.com/derailed/k9s/issues/716)
- [Issue #693](https://github.com/derailed/k9s/issues/693)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.2.md
================================================
# Release v0.20.2
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## Maintenance Release!
Fixing a few issues in the v0.20 aftermath ;(
Thank you all for reporting these issues and for your patience!
## Selection Marker
In this drop, we're adding the ability to set row mark ranges. There are situations where you've filtered a resource and need to delete part or all of the rows. In previous releases, you had to mark each rows one by one. Now you have the ability to select the beginning and end range and all rows in between will now be marked! To mark a single row, you can use `space`. To select rows between your initial mark to the current selection use `Ctrl-space`. To nuke all marked rows use `Ctrl-\`. All credits and ATTA BOY! goes to [Ryan Richard](https://github.com/cfryanr) for suggesting this feature! Thank you Ryan!!
## Logs Got Some TLC!
Per [Raman Gupta](https://github.com/rocketraman) excellent suggestion, we've added a way to add a separator to your chatty logs to easily see the latest incoming logs. While in log view, you can now press `m` for mark to add the separator to the log stream. If you don't care about the log history and just want to see the latest incoming logs, pressing `c` will clear out the log viewer.
## Resolved Bugs/Features/PRs
- [Issue #741](https://github.com/derailed/k9s/issues/741)
- [Issue #740](https://github.com/derailed/k9s/issues/740)
- [Issue #739](https://github.com/derailed/k9s/issues/739)
- [Issue #727](https://github.com/derailed/k9s/issues/727)
- [Issue #723](https://github.com/derailed/k9s/issues/723)
- [PR #725](https://github.com/derailed/k9s/pull/725) Big Thanks To [Soupyt](https://github.com/soupyt)!!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.3.md
================================================
# Release v0.20.3
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
**Maintenance Release!**
## Resolved Bugs/Features/PRs
- [Issue #752](https://github.com/derailed/k9s/issues/752)
- [Issue #677](https://github.com/derailed/k9s/issues/677) Once again with feelings this time ;(
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.4.md
================================================
# Release v0.20.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## PersistentVolumeClaims Reference Tracking
In continuation with the resource usage check feature added in v0.20, we've added reference checks on the PVC view. If you ever wonder which resources on your cluster are referencing a given PVC, simply press `u` for `UsedBy` and k9s will tell you.
## New Config On The Block
Some folks voiced concerns with K9s config dir littering their home directory with yet another `.dir`. In this drop, we're introducing a new env variable `K9SCONFIG` that tells K9s where to look for its configurations. If `K9SCONFIG` is not set K9s will look in the usual place aka `$HOME/.k9s`.
## Resolved Bugs/Features/PRs
- [Issue #754](https://github.com/derailed/k9s/issues/754)
- [Issue #753](https://github.com/derailed/k9s/issues/753)
- [Issue #743](https://github.com/derailed/k9s/issues/743)
- [Issue #728](https://github.com/derailed/k9s/issues/728)
- [Issue #718](https://github.com/derailed/k9s/issues/718)
- [Issue #643](https://github.com/derailed/k9s/issues/643)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.20.5.md
================================================
# Release v0.20.5
## 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 is as ever very much noticed and appreciated!
Also if you dig this tool, 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)
---
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [João Costa](https://github.com/JD557)
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #756](https://github.com/derailed/k9s/issues/756)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.0.md
================================================
# Release v0.21.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 is as ever very much noticed and appreciated!
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)
---
## First A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Remo Eichenberger](https://github.com/remoe)
* [Ken Ahrens](https://github.com/kenahrens)
## Moving Forward!
In this drop, we've added a port-forward indicator to visually see if a port-forward is active on a pod/container. You can also navigate directly to the port-forward view using the new shortcut `f` available in
pod and container view.
## Manifest That!
Ever wanted to manipulate your Kubernetes manifests directly in K9s? `Yes Please!!`
We are introducing a new view namely `directory` aka `dir`. Using this command you can list/traverse a given directory structure containing your Kubernetes manifests using a new `:dir /fred` command.
From there you can view/edit your manifests and also deploy or delete these resources for your cluster directly from K9s. Just like `kubectl` you can apply/delete an entire directory or a single manifest.
How cool is that?
## Resolved Bugs/Features/PRs
* [Issue #778](https://github.com/derailed/k9s/issues/778)
* [Issue #774](https://github.com/derailed/k9s/issues/774)
* [Issue #761](https://github.com/derailed/k9s/issues/761)
* [Issue #759](https://github.com/derailed/k9s/issues/759)
* [Issue #758](https://github.com/derailed/k9s/issues/758)
* [PR #746](https://github.com/derailed/k9s/pull/746) Big Thanks to [Groselt](https://github.com/groselt)!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.1.md
================================================
# Release v0.21.1
## 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 is as ever very much noticed and appreciated!
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)
---
## A Word From Our Sponsors...
I would like to send a `Big Thank You` to the following generous K9s friend for joining our sponsorship program and supporting this project!
* [Joao Azevedo](https://github.com/jcazevedo)
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #791](https://github.com/derailed/k9s/issues/791)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.10.md
================================================
# Release v0.21.10
## 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!
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)
---
Maintenance Release!
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Martin Kemp](https://github.com/MartiUK)
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a large team. K9s is complex and does demand a lot of my time. So if this tool is useful to you and part of your daily lifecycle, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
## I Should've known better
Seems like I've broken the golden rule ie never add a feature without providing an option to turn it off ;( It looks like enable mouse support for K9s had unexpected side effects. So in this drop, we're introducing a new configuration aka `enableMouse` that defaults to `false`. You can opt-in mouse support, by enabling it in the K9s config file. That said when mouse support is enabled, you can still use terminal selection using either `Shift/Option` for Windows/Mac.
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
enableMouse: true # Defaults to false if not set
headless: false
...
```
## Resolved Issues/Features
* [Issue #874](https://github.com/derailed/k9s/issues/874) Latest version broke selecting text by mouse
## Resolved PRs
* [PR #877](https://github.com/derailed/k9s/pull/877) Change character used for X in RBAC view. Thank you! [Torjus](https://github.com/torjue)
* [PR #876](https://github.com/derailed/k9s/pull/876) Migrate to new sortorder import path. Big thanks to [fbbommel](https://github.com/fvbommel)
* [PR #873](https://github.com/derailed/k9s/pull/873) Fix default logger config, same as README. Thank you! [darklore](https://github.com/darklore)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.2.md
================================================
# Release v0.21.2
## 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 is as ever very much noticed and appreciated!
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)
---
## First A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Remo Eichenberger](https://github.com/remoe)
* [Ken Ahrens](https://github.com/kenahrens)
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #790](https://github.com/derailed/k9s/issues/790) My bad! Must get mo' sleep ;(
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.3.md
================================================
# Release v0.21.3
## 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 is as ever very much noticed and appreciated!
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)
---
## ♫ The Sound Behind The Release ♭
And now for something a `beat` different?
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure right?
[Funkin' for Jamaica](https://www.youtube.com/watch?v=uuUy2ShGLyo) by the most awesome Tom Browne!
Maintenance Release!
Lots of bugs fix in this drop and perf improvements...
NOTE! You may experience some disturbance in the force in this drop, so please proceed with caution and do land a hand flushing out potential issues.
Thank you!!
## Video Tutorial
[Who Let The Pods Out (v0.21.3)](https://youtu.be/wG8KCwDAhnw)
## Resolved Bugs/Features/PRs
* [Issue #816](https://github.com/derailed/k9s/issues/816)
* [Issue #813](https://github.com/derailed/k9s/issues/813)
* [Issue #812](https://github.com/derailed/k9s/issues/812)
* [Issue #810](https://github.com/derailed/k9s/issues/810)
* [Issue #807](https://github.com/derailed/k9s/issues/807)
* [Issue #806](https://github.com/derailed/k9s/issues/806)
* [Issue #805](https://github.com/derailed/k9s/issues/805)
* [Issue #800](https://github.com/derailed/k9s/issues/800)
* [Issue #799](https://github.com/derailed/k9s/issues/799)
* [Issue #709](https://github.com/derailed/k9s/issues/709) Crossing fingers and toes ;)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.4.md
================================================
# Release v0.21.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
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)
---
Maintenance Release! The aftermath...
## Resolved Bugs/Features/PRs
* [Issue #819](https://github.com/derailed/k9s/issues/819)
* [Issue #818](https://github.com/derailed/k9s/issues/818)
* [Issue #797](https://github.com/derailed/k9s/issues/797)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.5.md
================================================
# Release v0.21.5
## 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!
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)
---
## First A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Drew](https://github.com/ScubaDrew)
* [Vladimir Rybas](https://github.com/vrybas)
Contrarily to popular belief, OSS is not free! We've now reached 8k stars and 270k downloads! As you all know, this project is not pimped out by a big company with deep pockets or a large team. This project is complex and does demand a lot of my time. So if k9s is useful to you and part of your daily lifecycle. Please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us!
Don't let OSS by individual contributors become an oxymoron...
## New Skins On The Block!
In this drop, big thanks are in effect for [Dan Mikita](https://github.com/danmikita) for contributing a new K9s [solarized theme](https://github.com/derailed/k9s/tree/master/skins)!
Also we've added a new skin configuration for table's cursor namely `cursorFgColor` and `cursorBgColor`:
```yaml
# skin.yml
...
views:
table:
fgColor: *foreground
bgColor: *background
cursorFgColor: *foreground
cursorBgColor: *current_line
header:
fgColor: white
bgColor: *background
sorterColor: *cyan
...
```
## Resolved Bugs/Features/PRs
* [Issue #826](https://github.com/derailed/k9s/issues/826)
* [Issue #824](https://github.com/derailed/k9s/issues/824)
* [Issue #823](https://github.com/derailed/k9s/issues/823)
* [Issue #821](https://github.com/derailed/k9s/issues/821)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.6.md
================================================
# Release v0.21.6
## 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!
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)
---
## New Skins On The Block. Part Duh!
In this drop, we've added a new skin configuration for table's cursor namely `cursorFgColor` and `cursorBgColor` as well as the ability to skin your dialogs:
```yaml
# skin.yml
k9s:
...
# Note: You can now skin your dialogs.
dialog:
fgColor: *foreground
bgColor: *background
buttonFgColor: *foreground
buttonBgColor: *magenta
buttonFocusFgColor: white
buttonFocusBgColor: *cyan
labelFgColor: *orange
fieldFgColor: *foreground
...
views:
table:
fgColor: *foreground
bgColor: *background
# Note! new tags
cursorFgColor: *foreground
cursorBgColor: *current_line
header:
fgColor: white
bgColor: *background
sorterColor: *cyan
...
```
## Resolved Bugs/Features/PRs
* [Issue #795](https://github.com/derailed/k9s/issues/795)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.7.md
================================================
# Release v0.21.7
## 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!
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)
---
Maintenance Release!
## Resolved Bugs/Features/PRs
* [Issue #281](https://github.com/derailed/k9s/issues/281)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.8.md
================================================
# Release v0.21.8
## 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!
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)
---
Maintenance Release!
## ♫ The Sound Behind The Release ♭
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure while viewing this release notes right?
[Strange Ritual - David Byrne](https://www.youtube.com/watch?v=gsramZ3sOjI) ;)
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Jean-Luc Geering](https://github.com/jlgeering)
* [Takafumi Ikeda](https://github.com/ikeike443)
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a large team. K9s is complex and does demand a lot of my time. So if this tool is useful to you and part of your daily lifecycle, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
## Resolved Issues/Features
* [Issue #871](https://github.com/derailed/k9s/issues/871) K9s memory leak when shell that launched k9s is terminated.
* [Issue #857](https://github.com/derailed/k9s/issues/857) Working in readonly mode.
* [Issue #855](https://github.com/derailed/k9s/issues/855) Some mouse support.
* [Issue #849](https://github.com/derailed/k9s/issues/849) Xray highlight color.
* [Issue #845](https://github.com/derailed/k9s/issues/845) CronJob trigger checks wrong permission.
* [Issue #837](https://github.com/derailed/k9s/issues/837) Hang after running plugin.
## Resolved PRs
* [PR #866](https://github.com/derailed/k9s/pull/866) Go 1.15 support convert int to string failure. Thank you [Trung](https://github.com/runlevel5)!
* [PR #864](https://github.com/derailed/k9s/pull/864) Add ppc64le target. Thank you once again [Trung](https://github.com/runlevel5)!
* [PR #863](https://github.com/derailed/k9s/pull/863) Update images in Dockerfile. Big thanks to [Peter Sutter](https://github.com/petersutter)!
* [PR #841](https://github.com/derailed/k9s/pull/841) Fix a type in bug report template. Thanks to [Jinsu Park](https://github.com/umi0410)!
* [PR #834](https://github.com/derailed/k9s/pull/834) Add Chocolatey installation. Thanks to [Romain](https://github.com/romch007)!
* [PR #828](https://github.com/derailed/k9s/pull/828) Add solarized dark skin. Big Thanks to [Dan Mikita](https://github.com/danmikita)!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.21.9.md
================================================
# Release v0.21.9
## 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!
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)
---
Maintenance Release!
## ♫ The Sound Behind The Release ♭
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure while viewing this release notes right?
[Strange Ritual - David Byrne](https://www.youtube.com/watch?v=gsramZ3sOjI) ;)
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Jean-Luc Geering](https://github.com/jlgeering)
* [Takafumi Ikeda](https://github.com/ikeike443)
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a large team. K9s is complex and does demand a lot of my time. So if this tool is useful to you and part of your daily lifecycle, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
## Resolved Issues/Features
* [Issue #871](https://github.com/derailed/k9s/issues/871) K9s memory leak when shell that launched k9s is terminated (With feeling!)
* [Issue #849](https://github.com/derailed/k9s/issues/849) Xray highlight color (with feeling!)
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.22.0.md
================================================
# Release v0.22.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!
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)
---
Maintenance Release!
## A Word From Our Sponsors...
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Martin Kemp](https://github.com/MartiUK)
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a large team. K9s is complex and does demand a lot of my time. So if this tool is useful to you and part of your daily lifecycle, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
## I Should've known better
Seems like I've broken the golden rule ie never add a feature without providing an option to turn it off ;( It looks like enable mouse support for K9s had unexpected side effects. So in this drop, we're introducing a new configuration aka `enableMouse` that defaults to `false`. You can opt-in mouse support, by enabling it in the K9s config file. That said when mouse support is enabled, you can still use terminal selection using either `Shift/Option` for Windows/Mac.
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
enableMouse: true # Defaults to false if not set
headless: false
...
```
## Resolved Issues/Features
* [Issue #874](https://github.com/derailed/k9s/issues/874) Latest version broke selecting text by mouse
## Resolved PRs
* [PR #877](https://github.com/derailed/k9s/pull/877) Change character used for X in RBAC view. Thank you! [Torjus](https://github.com/torjue)
* [PR #876](https://github.com/derailed/k9s/pull/876) Migrate to new sortorder import path. Big thanks to [fbbommel](https://github.com/fvbommel)
* [PR #873](https://github.com/derailed/k9s/pull/873) Fix default logger config, same as README. Thank you! [darklore](https://github.com/darklore)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.22.1.md
================================================
# Release v0.22.1
## 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!
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)
---
Maintenance Release!
## Resolved Issues/Features
* [Issue #882](https://github.com/derailed/k9s/issues/882) After filtering objects cannot enter them anymore
* [Issue #881](https://github.com/derailed/k9s/issues/881) CPU limit percentage in pod view counts containers without limits
* [Issue #880](https://github.com/derailed/k9s/issues/880) filtering/search doesn't take all columns into account anymore
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.0.md
================================================
# Release v0.23.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!
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 ♭
I figured why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure while viewing these release notes!
* [On An Island - David Gilmour With Crosby&Nash](https://www.youtube.com/watch?v=kEa__0wtIRo)
* [Cause We've Ended As Lovers - Jeff Beck](https://www.youtube.com/watch?v=VC02wGj5gPw)
## Our Release Heroes
Please join me in recognizing and applauding this drop contributors that went the extra mile to make sure K9s is better and more useful for all of us!!
Big ATTA BOY/GIRL! in full effect this week to the good folks below for their efforts and contributions to K9s!!
* [Michael Albers](https://github.com/michaeljohnalbers)
* [Wi1dcard](https://github.com/wi1dcard)
* [Saskia Keil](https://github.com/SaskiaKeil)
* [Tomasz Lipinski](https://github.com/tlipinski)
* [Antoine Méausoone](https://github.com/Ameausoone)
* [Emeric Martineau](https://github.com/emeric-martineau)
* [Eldad Assis](https://github.com/eldada)
* [David Arnold](https://github.com/blaggacao)
* [Peter Parente](https://github.com/parente)
## A Word From Our Sponsors...
First off I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [William Alexander](https://github.com/carpetfuz)
* [Jiri Valnoha](https://github.com/waldauf)
* [Pavel Tumik](https://github.com/sagor999)
* [Bart Plasmeijer](https://github.com/bplasmeijer)
* [Matt Welke](https://github.com/mattwelke)
* [Stefan Mikolajczyk](https://github.com/stefanmiko)
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a big dev team. K9s is complex and does demand lots of my time. So if this tool is useful to you and your organization and part of your daily Kubernetes flow, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
## Describe/YAML goes FullMonty!!
We've added a new option to enable full screen while describing or viewing a resource YAML. Similarly to the full screen toggle option in the log view, pressing `f` will now toggle full-screen for both YAML and Describe views.
Additionally, the YAML and Describe view are now reactive! YAML/Describe views will now watch for changes to the underlying resource manifests. I'll admit this was a feature I was missing, but decided to punt as it required a bit of re-org to make it happen correctly. So BIG thanks to [Fabian-K](https://github.com/Fabian-K) for entering this issue and for the boost!!
Not cool enough for Ya? the YAML view now also affords for getting ride of those pesky `managedFields` while viewing a resource. Use the `m` key to toggle visibility on the managedFields.
## Best Effort... Not!
In this drop, we've added 2 new columns namely `CPU/R:L` and `MEM/R:L`. These represents the current request:limit specified on containers. They are available in node, pod and container views. While in Pod view, you will need to volunteer them and use the `Go Wide` option `Ctrl-W` to see the columns. These columns will be display by default for Node/Container views. In the node view, they tally the total amount of resources for all pods hosted a given node. If that's inadequate, you can also leverage K9s [Custom Column](https://github.com/derailed/k9s#resource-custom-columns) feature to volunteer them or not.
## Set Container Images
You will have the ability to tweak your container images for experimentation, using the new SetImage binding aka `i`. This feature is available for un-managed pods, deployments, statefulsets and daemonsets. With a resource selected, pressing `i` will provision an edit dialog listing all init/container images. So you will have to ability to tweak the images and update your containers. Big Thanks to [Antoine Méausoone](https://github.com/Ameausoone) for making this feature available to all of us!!
NOTE! This is a one shot commands applied directly against your cluster and won't survive a new resource deployment.
## Crumbs On...Crumbs Off, Caterpillar
We've added a new configuration to turn off the crumbs via `crumbsLess` configuration option. You can also toggle the crumbs via the new key option `Ctrl-g`. You can enable/disable this option in your ~/.k9s/config.yml or via command line using `--crumbsless` CLI option.
```yaml
k9s:
refreshRate: 2
headless: false
crumbsless: false
readOnly: true
...
```
## BANG FILTERS!
Some folks have voiced the desire to use inverse filters to refine content while in resource table views. Appending a `!` to your filter will now enable an inverse filtering operation For example, in order to see all pods that do not contain `fred` in their name, you can now use `/!fred` as your filtering command. If you dig this implementation, please make sure to give a big thank you to [Michael Albers](https://github.com/michaeljohnalbers) for the swift implementation!
## New Conf On the Block...
In this release, we've made some changes to the retry policies when things fail on your cluster and the api-server is suffering from an hearing impediment. The current policy was to check for connection issues every 15secs and retry 15 times before exiting K9s. This rules were not configurable and could yield for overtaxing the api-server. So we've implemented exponential back-off so that K9s can attempt to remediate or bail out of the session if not.
To this end, there is a new config option namely `maxConnRetry` to will be added to your K9s config to set the retry policy. The default is currently set to 5 retries.
NOTE: This is likely an ongoing story and more will come based on your feedback!
Sample K9s configuration
```yaml
k9s:
refreshRate: 2
# Set the maximum attempt to reconnect with the api-server in case of failures.
maxConnRetry: 5
...
```
## 🏁 Start Your Engines...
As you can see, this is a pretty big drop and likely we've created some new issues in the process 🙀
Please make sure to file issues/PRs if things are not working as expected so we can improve on these features.
👻 Happy Halloween To All!! (as if 2020 is not scary enough 🙈)
Thank you all for your being fans and supporting K9s!!
---
## Resolved Issues/Features
* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view
* [Issue #903](https://github.com/derailed/k9s/issues/903) Slow down reconnection rate on auth failures
* [Issue #901](https://github.com/derailed/k9s/issues/901) Logs page for any pod/container shows Waiting for logs...
* [Issue #900](https://github.com/derailed/k9s/issues/900) Support sort by pending status
* [Issue #895](https://github.com/derailed/k9s/issues/895) Wrong highlight position when filtering logs
* [Issue #892](https://github.com/derailed/k9s/issues/892) tacit kustomize & kpt support
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable read only config via command line flag
* [Issue #887](https://github.com/derailed/k9s/issues/887) Ability to call out a separate program to parse/filter logs
* [Issue #886](https://github.com/derailed/k9s/issues/886) Full screen mode or remove borders in YAML view for easy copy/paste
* [Issue #884](https://github.com/derailed/k9s/issues/884) Refresh for describe & yaml view
* [Issue #883](https://github.com/derailed/k9s/issues/883) View logs quickly scrolls through entire logs when initially loading
* [Issue #875](https://github.com/derailed/k9s/issues/875) Lazy filter
* [Issue #848](https://github.com/derailed/k9s/issues/848) Support an inverse operator on filtered search
* [Issue #820](https://github.com/derailed/k9s/issues/820) Log file spammed despite K9s not running
* [Issue #794](https://github.com/derailed/k9s/issues/794) Events view
## Resolved PRs
* [PR #909](https://github.com/derailed/k9s/pull/909) Add support for inverse filtering
* [PR #908](https://github.com/derailed/k9s/pull/908) Remove trailing delta from the scale dialog when replicas are in flux
* [PR #907](https://github.com/derailed/k9s/pull/907) Improve docs on sinceSeconds logger option
* [PR #904](https://github.com/derailed/k9s/pull/904) PVC `UsedBy` list irrelevant statefulsets
* [PR #898](https://github.com/derailed/k9s/pull/898) Use config.CallTimeout in APIClient
* [PR #897](https://github.com/derailed/k9s/pull/897) Use DefaultColorer for aliases rendering
* [PR #896](https://github.com/derailed/k9s/pull/896) Allow remove crumbs
* [PR #894](https://github.com/derailed/k9s/pull/894) Execute plugins and pass context
* [PR #891](https://github.com/derailed/k9s/pull/891) Add command to get the latest stable kubectl version and support for KUBECTL_VERSION as Dockerfile ARG
* [PR #847](https://github.com/derailed/k9s/pull/847) Add ability to set container images
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.1.md
================================================
# Release v0.23.1
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #918](https://github.com/derailed/k9s/issues/918) NPE setting image. Totally on me ;(
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.10.md
================================================
# Release v0.23.10
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #933](https://github.com/derailed/k9s/issues/933) Unable to cordon node starting in v0.23.8
* [Issue #932](https://github.com/derailed/k9s/issues/932) Won't start if api.github.com is inaccessible
* [Issue #931](https://github.com/derailed/k9s/issues/931) Describe ingress not showing labels
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.2.md
================================================
# Release v0.23.2
## 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!
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)
---
### Write Mode
K9s is writable by default, meaning you can interact with your cluster and make changes using one shot commands ie edit, delete, scale, etc... There `readOnly` config option that can be specified in the configuration or via a cli arg to override this behavior. In this drop, we're introducing a symmetrical command line arg aka `--write` that overrides a K9s session and make it writable tho the readOnly config option is set to true.
## Inverse Log Filtering
In the last drop, we've introduces reverse filters to filter out resources from table views. Now you will be able to apply inverse filtering on your log views as well via `/!fred`
---
## Resolved Issues/Features
* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view. With Feelings. Thanks Claudio!
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable readOnly config
* [Issue #564](https://github.com/derailed/k9s/issues/564) Invert filter mode on logs
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.3.md
================================================
# Release v0.23.3
## 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!
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)
---
## Maintenance Release!
Arg.. Must get m'o sleep!!
---
## Resolved Issues/Features
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable readOnly config
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.4.md
================================================
# Release v0.23.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #920](https://github.com/derailed/k9s/issues/920) Timestamp stopped working
* [Issue #663](https://github.com/derailed/k9s/issues/663) Perf issues in v0.23.X - Better??
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.5.md
================================================
# Release v0.23.5
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #928](https://github.com/derailed/k9s/issues/928) Auto complete is too aggressive
* [Issue #663](https://github.com/derailed/k9s/issues/663) Perf issues in v0.23.X - With feelings??
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.6.md
================================================
# Release v0.23.6
## 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!
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)
---
## Maintenance Release!
Boyee! Having an awesome week here at the ranch!
It feels like k9s v0.23.X is plagued with as many election wining scenarios as the CNN's magic screen ;)
Time to lay off the pipe... but before I do, here is another drop!
### Use The Farce Luke!
I've figured it might be a good time to come up with some notification when a new release is available. To this end, when a new k9w version has been released, you should see an indicator next to the k9s top screen `K9s Rev` section indicating an updated version is ready for mass consumption.
### Thank you!
I'd like to extend a big thank you to all that have reported issues with the drops and for being patient! I get the rapid k9s rev might be an issue for some, but I do try my best to make sure pri-1 issues are resolved quickly in order to make k9s better for all of us.
Thank you all for your understanding, kindness and support!!
---
## Resolved Issues/Features
* [Issue #929](https://github.com/derailed/k9s/issues/929) Crash on startup with no metrics-server detected
* [Issue #926](https://github.com/derailed/k9s/issues/926) JSON Do you have plans to apply
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.7.md
================================================
# Release v0.23.7
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #930](https://github.com/derailed/k9s/issues/930) Version checker is not reporting a new release correctly
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.8.md
================================================
# Release v0.23.8
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #663](https://github.com/derailed/k9s/issues/663) K9s is slow on large clusters (With feelings and crossing both fingers and toes)
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.23.9.md
================================================
# Release v0.23.9
## 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!
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)
---
## Maintenance Release!
---
## Resolved Issues/Features
* [Issue #930](https://github.com/derailed/k9s/issues/930) Version checker is not reporting a new release correctly (With feelings...)
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.0.md
================================================
# Release v0.24.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!
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 ♭
* [Mother Protect - Niki & The Dove](https://www.youtube.com/watch?v=P5W2hjwBsFk)
* [Dark Star - POLIÇA](https://www.youtube.com/watch?v=2pD3hJc-8xg)
## A Word From Our Sponsors...
First and foremost, I would like to extend a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
Your sponsorships efforts are vital to keep this project alive and evolving. So please do give back!
* [Lopeg](https://github.com/lopeg)
* [Gerhard Lazu](https://github.com/gerhard)
---
## Resolved Issues/Features
* [Issue #953](https://github.com/derailed/k9s/issues/953) Pdb with percentages show as "0".
* [Issue #947](https://github.com/derailed/k9s/issues/947) Selection is applied for nonexistent items.
* [Issue #944](https://github.com/derailed/k9s/issues/944) Can not launch ksniff.
* [Issue #940](https://github.com/derailed/k9s/issues/940) Indeterminate search results when filtering with numbers.
* [Issue #914](https://github.com/derailed/k9s/issues/914) Unable to edit resources with colliding singular names.
## Resolved PRs
* [PR #941](https://github.com/derailed/k9s/pull/941) Add Monokai skin. My new favorite skin! Big Thanks to [Mike SigsWorth](https://github.com/mikesigs)!!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.1.md
================================================
# Release v0.24.1
## 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!
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)
## Maintenance Release!
☡ IMPORTANT!! v0.24.0 is a bad drop!! Apparently while upgrading the dependencies in the v0.24.0, I've managed to hose the dialog's buttons focus hence producing the incorrect default button behavior. So please upgrade to v0.24.1 ASAP!!
---
## Resolved Issues/Features
* [Issue #821](https://github.com/derailed/k9s/issues/821) Default color is no longer transparent.
* [Issue #933](https://github.com/derailed/k9s/issues/933) Unable to cordon node.
## Resolved PRs
* [PR #941](https://github.com/derailed/k9s/pull/941) Add Monokai skin. My new favorite skin! Big Thanks to [Mike SigsWorth](https://github.com/mikesigs)!!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.10.md
================================================
# Release v0.24.10
## 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!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1123](https://github.com/derailed/k9s/issues/1123) Cannot respond to keyboard strike after exit pod shell in windows 10
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.11.md
================================================
# Release v0.24.11
## 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!
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)
## Maintenance Release!
> NOTE: Made a mistake with the last release binaries including a release tag. My bad as his caused a headache for the good folks managing the release upstream. Reverted the change on this drop!
---
## Resolved Issues
* [Issue #1163](https://github.com/derailed/k9s/issues/1163) Color for autocomplete text
* [Issue #1153](https://github.com/derailed/k9s/issues/1153) Crash when scaling a deployment with a custom view
* [Issue #1151](https://github.com/derailed/k9s/issues/1151) k9s does not use current namespace of current context
* [Issue #1140](https://github.com/derailed/k9s/issues/1140) Can no longer trigger cronjobs manually
* [Issue #1137](https://github.com/derailed/k9s/issues/1137) Unreadable container name
* [Issue #1132](https://github.com/derailed/k9s/issues/1132) Searching for regex not always working
* [Issue #1131](https://github.com/derailed/k9s/issues/1131) Changed release filenames starting k9s v0.24.10
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.12.md
================================================
# Release v0.24.12
## 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!
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)
## Prompt GOT Styles!
Added a new configuration for styling your command/search prompts. So you can now specify foreground/background and suggestion color to your heart content. For example:
```yaml
# $HOME/.k9s/skin.yml
k9s:
body:
fgColor: aqua
bgColor: black
logoColor: purple
# Prompt styles
prompt:
fgColor: blue
bgColor: black
suggestColor: orange
...
```
---
## Resolved Issues
* [Issue #1169](https://github.com/derailed/k9s/issues/1169) Scaling last deployment errors out
* [Issue #1167](https://github.com/derailed/k9s/issues/1167) Cronjob trigger busted
* [Issue #1163](https://github.com/derailed/k9s/issues/1163) Color for autocomplete text
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.13.md
================================================
# Release v0.24.13
## 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!
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)
## Maintenance Release!
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and pay it forward!
* [Stephan Skydan](https://github.com/sskydan)
* [Azar](https://github.com/azarudeena)
* [Tim Orling](https://github.com/moto-timo)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Thank you!!
---
## Resolved Issues
* [Issue #1182](https://github.com/derailed/k9s/issues/1169) Cronjob suspend does not work 0.24.12
* [Issue #1167](https://github.com/derailed/k9s/issues/1167) Cronjob trigger busted with Feelings!
## Resolved PRs
* [PR #1141](https://github.com/derailed/k9s/pull/1141) Big Thanks to [Raul Cabello Martin](https://github.com/Raullllll) in making K9s better of all of us!!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.14.md
================================================
# Release v0.24.14
## 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!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1186](https://github.com/derailed/k9s/issues/1186) Viewing previous logs does not work
* [Issue #1167](https://github.com/derailed/k9s/issues/1167) Cronjob trigger busted with feelings!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.15.md
================================================
# Release v0.24.15
## 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!
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 ♭
* [Paradise Delay - Marteria, DJ Kose](https://www.youtube.com/watch?v=eM-xTN8ggOs)
* [Fool For Your Stockings - ZZ Top - Sadly this one is a tribute to Dusty Hill ;(](https://www.youtube.com/watch?v=UExKTZ3veB8)
---
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Viacheslav Moskin](https://github.com/viacheslavmoskin)
* [Thomas Peter Bernsten](https://github.com/tpberntsen)
* [EMR-Bear](https://github.com/emrbear)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Thank you!!
---
## !!BREAKING CHANGE!!... We've moved!
As of this drop, k9s home directory is now configurable via [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). Please see the specification depending on your platform of choice. You will now need to set or use the default for `$XDG_CONFIG_HOME` if not already present on your system. This is now the de facto replacement for`HOME/.k9s` as K9s will no longer honor this directory to load artifacts such as config, skins, views, etc... If you have existing customizations, you will need to move those over to your `$XDG_CONFIG_HOME/k9s` dir.
This feature is still fresh and we could have totally missed a piece, so please proceed with caution and keep that issue tracker handy...
Please join me in giving a Big Thank you! to [Arthur](https://github.com/pysen) for making this happen for us!
---
## Resolved Issues
* [Issue #1209](https://github.com/derailed/k9s/issues/1209) K9s - Popeye run instructions
* [Issue #1203](https://github.com/derailed/k9s/issues/1203) K9s does not remember last view I was in when switching contexts
* [Issue #1181](https://github.com/derailed/k9s/issues/1181) Cannot list roles
---
## PRs
* [PR #1213](https://github.com/derailed/k9s/pull/1213) Big Thanks to [Takumasa Sakao](https://github.com/sachaos)!
* [PR #1205](https://github.com/derailed/k9s/pull/1205) Great catch from [David Alger](https://github.com/davidalger)!
* [PR #1198](https://github.com/derailed/k9s/pull/1198) Once again [Takumasa Sakao](https://github.com/sachaos) to the rescue!!
* [PR #1196](https://github.com/derailed/k9s/pull/1196) ATTA Boy! [Daniel Lee Harple](https://github.com/dlh)
* [PR #1025](https://github.com/derailed/k9s/pull/1025) Big Thanks to [Arthur](https://github.com/pysen)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.2.md
================================================
# Release v0.24.2
## 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!
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 ♭
* [ZZ Top - My Head's in Mississippi](https://www.youtube.com/watch?v=Gp2PosHepzg)
## A Word From Our Sponsors...
I would like to extend a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Tim Orling](https://github.com/moto-timo)
* [Jiri Valnoha](https://github.com/waldauf)
* [Osx2000](https://github.com/osx2000)
## Our Release Heroes
Major ATTA BOY/GIRL! in full effect this week to the good folks below for their efforts and contributions in making sure K9s is better for all of us!
* [Ainslie Hsu](https://github.com/ainslie-hsu)
* [Lucas Teligioridis](https://github.com/lucasteligioridis)
* [Gergely Tankovics](https://github.com/gtankovics)
* [Michal Kuratczyk](https://github.com/mkuratczyk)
* [Simon Caron](https://github.com/simoncaron)
## She Can't Take Much More Capt'n!!
### Background
Thanks to all of you for supporting K9s and being avid fans. I am truly humbled and amazed by your continued kindness and support!! As we're nearing K9s second anniversary, the project has reached over 10k stars and 384k downloads! That said, while these numbers sound stunning, there is another number on this project that is not so and that is number of sponsors 😿.
As I understand it, there are a several organizations leveraging K9s productivity to better their bottom line, without much care for ours...
As you all know, K9s is a complex tool in a continually evolving space and we find ourselves spending a lot of our free time, thinking, experimenting and supporting K9s to continually improve the offering. As it stands, there is currently a very small fraction of you that actively sponsor this project either financially or by filing issues/PRs while the rest are benefiting from these efforts. This just does not sound like a fair deal and if we were in the music business it would be a total outrage!
### There Are Some That Call Me... Alpha!
To this end, I'd like to introduce a new member of the K9s pack, the main dog, aka `k9sAlpha`. This is going to be a licensed version of K9s. The current plan is to offer a tiered license scheme starting at `$10/month` for a license. K9s𝞪 will provide fixes, enhancements, further integrations and a bunch of new features that have been sitting in the back burner...
### So what does this entail?
1. The current k9s branch will be in feature freeze
1. K9s𝞪 users will need to purchase a license from our store
1. Active sponsors get a K9s𝞪 license
1. Documentation, binaries, issue trackers, will be provisioned under a new K9s𝞪 site
Given any license schemes are meant to be hacked/broken, we're not going to over complicate things with calling out to license servers and such to ensure the keys are legit.
The current plan is to email out your license keys and trusting our `Gentlemen Agreement` that you will not share or distribute your keys to other folks.
In the current economic climate, if you can't afford a K9s𝞪 license, we will provide you one on a case by case basis.
The process should be simple:
1. Acquire a license
1. Get a key via email
1. Store your key somewhere on disk
1. Download the K9s𝞪 binary
1. Administer your Kubernetes clusters with K9s𝞪
1. Rinse and repeat when your license expires
### K9s𝞪 Needs You!
To this end, I'd like to enlist a few of you to help me validate license keys, K9s𝞪 store and site to ensure the flow well... flows!
If you are so inclined, please reach out for your `shoephones` and send me an email with why you want to participate. Folks with K9s chops in multi clusters env would be preferred.
It should not take too much of your time to ensure all is cool, but want to make sure I have at least another 5 pairs of eyes to help out with the K9s𝞪 drop.
My hope is to get an initial K9s𝞪 revision dropped before Santa comes around...
### Pipe In!
By all means, this is a democracy and not a dictatorship! So... if you have better/other ideas or concerns please pipe in! Open an issue on the repo so we can track, discuss, opiniate and figure out the best course of action that will be fair to both K9s maintainers and users alike.
---
## Resolved Issues/Features
* [Issue #972](https://github.com/derailed/k9s/issues/972) Default color is no longer transparent.
* [Issue #933](https://github.com/derailed/k9s/issues/933) Unable to cordon node.
## Resolved PRs
* [PR #982](https://github.com/derailed/k9s/pull/982) Fix typo
* [PR #976](https://github.com/derailed/k9s/pull/976) Add OneDark color theme
* [PR #975](https://github.com/derailed/k9s/pull/982) Handling non json lines as raw with red color
* [PR #968](https://github.com/dserailed/k9s/pull/968) Disable filtering on help screen ... and broke the build ;)
* [PR #960](https://github.com/derailed/k9s/pull/960) Handle empty port list in PortForward view
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.3.md
================================================
# Release v0.24.3
## 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!
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)
## A Word From Our Sponsors...
I would like to extend a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
* [Levkov](https://github.com/levkov)
* [Michael McCafferty](https://github.com/mikemcc)
* [Stephan Skydan](https://github.com/sskydan)
* [Terrac Skiens](https://github.com/bluefishforsale)
* [Zafer Abo-Samra](https://github.com/Inbiten)
* [Gabriel Martinez](https://github.com/GMartinez-Sisti)
* [Pierre Lebrun](https://github.com/pierreyves-lebrun)
* [Luc Suryo](https://github.com/my10c)
* [Sean O'Brien](https://github.com/sob)
## Maintenance Release!
o Update Kubernetes to v0.20.5
## There are some that call me... Alpha!
K9s is still and will remain an open source software. As such it is free and we will continue to maintain this repo!
That said in order to support our efforts, we've recently launched [K9sAlpha](https://k9salpha.io) which is a freemium version of K9s. K9sAlpha unlocks additional features and enhancement.
If you would like to support us, you can either join our github sponsors or purchase a K9sAlpha license. If you are an active member of our github sponsorship program, you are eligible for a free K9sAlpha license. Please reach out for your shoe-phone and contact us for your personalized license key.
---
## Resolved Issues
* [Issue #1038](https://github.com/derailed/k9s/issues/1038) Release Cronjob API
* [Issue #1035](https://github.com/derailed/k9s/issues/1035) Update Ingress API Group
* [Issue #1028](https://github.com/derailed/k9s/issues/1028) Go compile
* [Issue #1024](https://github.com/derailed/k9s/issues/1024) Add Pod Readiness/Nominated cols
* [Issue #1013](https://github.com/derailed/k9s/issues/1013) Panic string negative repeat count
* [Issue #1005](https://github.com/derailed/k9s/issues/1005) No x86_64 binaries
* [Issue #735](https://github.com/derailed/k9s/issues/735) Shell into windows containers
## Resolved PRs
* [PR #1022](https://github.com/derailed/k9s/pull/1022) Update release
* [PR #1012](https://github.com/derailed/k9s/pull/1012) Fix typo for cluster based skins
* [PR #1009](https://github.com/derailed/k9s/pull/1009) Add webi installer info
* [PR #1004](https://github.com/derailed/k9s/pull/1004) Correction CronJob ApiVersion
* [PR #1026](https://github.com/derailed/k9s/pull/1026) Add option to hide logo
* [PR #997](https://github.com/derailed/k9s/pull/997) Shell into windows containers
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.4.md
================================================
# Release v0.24.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
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 ♭
* [The Dream - Albert Collins/Robert Cray](https://www.youtube.com/watch?v=XLkjF4s2Ms0)
## A Word From Our Sponsors!
I would like to extend a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
Without your support this project will be another cadaver in GitHub's infamous `Dead Program Society`. Thank you!!
* 😻 [Antoine Meaussone](https://github.com/Ameausoone)
## Maintenance Release!
## There are some that call me... Alpha!
K9s is still and will remain an open source software. As such it is free and we will continue to maintain this repo!
That said in order to support our efforts, we've recently launched [K9sAlpha](https://k9salpha.io) which is a freemium version of K9s. K9sAlpha unlocks additional features and enhancement.
If you would like to support us, you can either join our github sponsors or purchase a K9sAlpha license. If you are an active member of our github sponsorship program, you are eligible for a free K9sAlpha license. Please reach out for your shoe-phone and contact us for your personalized license key.
---
## Resolved Issues
* [Issue #1056](https://github.com/derailed/k9s/issues/1056) K9s hangs on edits
* [Issue #1024](https://github.com/derailed/k9s/issues/1024) Add Pod Readiness/Nominated cols. With feelings!
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.5.md
================================================
# Release v0.24.5
## 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!
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)
## Maintenance Release!
## There are some that call me... Alpha!
K9s is still and will remain an open source software. As such it is free and we will continue to maintain this repo!
That said in order to support our efforts, we've recently launched [K9sAlpha](https://k9salpha.io) which is a freemium version of K9s. K9sAlpha unlocks additional features and enhancements.
If you would like to support us, you can either join our github sponsors or purchase a K9sAlpha license. If you are an active member of our github sponsorship program, you are eligible for a free K9sAlpha license. Please reach out for your shoe-phone and contact us for your personalized license key.
---
## Resolved Issues
* [Issue #1063](https://github.com/derailed/k9s/issues/1063) Weird colors on windows (Don't do windows so please help verify!)
* [Issue #1061](https://github.com/derailed/k9s/issues/1061) Container shell Windows (Don't do windows so please help verify!)
* [Issue #1059](https://github.com/derailed/k9s/issues/1059) Monokai skin broken\
* [Issue #177](https://github.com/derailed/k9s/issues/177) Shell first character lost
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.6.md
================================================
# Release v0.24.6
## 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!
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)
## Maintenance Release!
## Our Release Heroes
Major ATTA BOY/GIRL! in full effect this week to the good folks below for their efforts and contributions in making sure K9s is better for all of us!
* 🙏 [Arash Outadi](https://github.com/arashout)
## There are some that call me... Alpha!
K9s is still and will remain an open source software. As such it is free and we will continue to maintain this repo!
That said in order to support our efforts, we've recently launched [K9sAlpha](https://k9salpha.io) which is a freemium version of K9s. K9sAlpha unlocks additional features and enhancements.
If you would like to support us, you can either join our github sponsors or purchase a K9sAlpha license. If you are an active member of our github sponsorship program, you are eligible for a free K9sAlpha license. Please reach out for your shoe-phone and contact us for your personalized license key.
---
## Resolved Issues
* [Issue #1063](https://github.com/derailed/k9s/issues/1063) Weird colors on windows (Don't do windows so please help verify!)
* [Issue #1061](https://github.com/derailed/k9s/issues/1061) Container shell Windows (Don't do windows so please help verify!)
## Resolved PRs
* [PR #1062](https://github.com/derailed/k9s/pull/1062) Add auto-refresh toggle for yaml and describe views. Now defaults to no refresh!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.7.md
================================================
# Release v0.24.7
## 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!
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)
## Maintenance Release!
## Disturbance In The Farce.. Windows!
Splendid! So I had to borrow my neighbors kids 20 pounds windows `gaming` laptop for this one ;( Recent K9s drops are looking less than optimal on windows due to dependencies changes.
I was able to narrow it down to named colors are no longer being respected on Windows platforms. I'll keep digging on this but if you find yourself in the situation where K9s is looking less than optimal on Windows, for the short term please either use a custom skin with hex colors or change the stock skin to use hex color values vs named colors. Thank you!
## There are some that call me... Alpha!
K9s is still and will remain an open source software. As such it is free and we will continue to maintain this repo!
That said in order to support our efforts, we've recently launched [K9sAlpha](https://k9salpha.io) which is a freemium version of K9s. K9sAlpha unlocks additional features and enhancements.
If you would like to support us, you can either join our github sponsors or purchase a K9sAlpha license. If you are an active member of our github sponsorship program, you are eligible for a free K9sAlpha license. Please reach out for your shoe-phone and contact us for your personalized license key.
---
## Resolved Issues
* [Issue #1067](https://github.com/derailed/k9s/issues/1067) Increase HPA target column display
* [Issue #1061](https://github.com/derailed/k9s/issues/1061) Container shell Windows (Don't do windows so please help verify!)
* [Issue #1060](https://github.com/derailed/k9s/issues/1060) Exception when setting container image
## Resolved PRs
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.8.md
================================================
# Release v0.24.8
## 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!
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)
## Maintenance Release!
### NodeShell args
In this drop, we've added additional configurations to the k9s node shell so you override the command and args on the node shell containers.
```yaml
# $HOME/.k9s/config.yml
...
minikube:
view:
active: pod
featureGates:
nodeShell: true
shellPod:
image: busybox:1.31
# New!
command: ["/bin/sh", "-c"]
# New!
args: ["ls -al"]
namespace: default
limits:
cpu: 100m
memory: 100Mi
...
```
---
## Resolved Issues
* [Issue #1106](https://github.com/derailed/k9s/issues/1106) Remove padding while in full screen
* [Issue #1104](https://github.com/derailed/k9s/issues/1104) Config args for shellPod
* [Issue #1102](https://github.com/derailed/k9s/issues/1102) Explicitly announce no metrics are available
* [Issue #1097](https://github.com/derailed/k9s/issues/1097) Delete resource dialog stopped working
* [Issue #1093](https://github.com/derailed/k9s/issues/1094) Leading comma in command column
* [Issue #1094](https://github.com/derailed/k9s/issues/1094) Screendumps empty on EKS
* [Issue #1060](https://github.com/derailed/k9s/issues/1060) Exception when setting container image
* [Issue #1081](https://github.com/derailed/k9s/issues/1081) Color issue on startup
* [Issue #1078](https://github.com/derailed/k9s/issues/1078) Nord skin
* [Issue #1075](https://github.com/derailed/k9s/issues/1075) Crash on mouse click out of main window
* [Issue #1070](https://github.com/derailed/k9s/issues/1070) lose cursor on windows 10
* [Issue #1068](https://github.com/derailed/k9s/issues/1068) Build error 0.24.7
* [Issue #1063](https://github.com/derailed/k9s/issues/1063) Weird colour scheme on windows
## Resolved PRs
* [PR #1101](https://github.com/derailed/k9s/pull/1101) propagate insecure-skip-tls-verify
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.24.9.md
================================================
# Release v0.24.9
## 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!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1111](https://github.com/derailed/k9s/issues/1111) -A switch doesn't work as advertised
* [Issue #1109](https://github.com/derailed/k9s/issues/1109) 0.24.8 edit needs an extra keystroke to process. (Crossing fingers AND toes!!)
* [Issue #1104](https://github.com/derailed/k9s/issues/1104) Configure args for shellPod
## Resolved PRs
* [PR #1103](https://github.com/derailed/k9s/pull/1103) Dynamically load style for help. Big Thanks To [Louis Garman](https://github.com/leg100)
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.0.md
================================================
# Release v0.25.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!
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 ♭
* [High Fidelity - By Elvis Costello (Yup! he started is career as a computer operator. Can u tell??)](https://www.youtube.com/watch?v=DJS-2kacmpU)
* [Walk With A Big Stick - Foster The People](https://www.youtube.com/watch?v=XMY1VMTyl8s)
* [Beirut - Steps Ahead -- Love this band!! with the ever so talented and sadly late Michael Brecker ;(](https://www.youtube.com/watch?v=UExKTZ3veB8)
---
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Andrew Regan](https://github.com/poblish)
* [Bruno Brito](https://github.com/brunohbrito)
* [ScubaDrew](https://github.com/ScubaDrew)
* [mike-code](https://github.com/mike-code)
* [Andrew Aadland](https://github.com/DaemonDude23)
* [Michael Albers](https://github.com/michaeljohnalbers)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Personal Note...
I had so many distractions this cycle so expect some `disturbance in the farce!` on this drop.
To boot rat holed quiet a bit on improving speed. So I might have drop some stuff on the floor in the process...
Please report back if that's the case and we will address shortly. Tx!!
## Port It Forward??
Ever been in a situation where you need to constantly port-forward on a given pod with multiple containers or exposing multiple ports? If so it might be cumbersome to have to type in the full container:port specification to activate a forward. If you fall in this use cases, you can now specify which container and port you would rather port-forward to by default. In this drop, we introduce a new annotation that you can use to specify and container/port to forward to by default. If set, the port-forward dialog will know to default to your settings.
> NOTE: you can either use a container port name or number in your annotation!
```yaml
# Pod fred
apiVersion: v1
kind: Pod
metadata:
name: fred
annotations:
k9scli.io/auto-portforwards: zorg::5556 # => will default to container zorg port 5556 and local port 5566. No port-forward dialog will be shown.
# Or...
k9scli.io/portforward: bozo::6666:p1 # => launches the port-forward dialog selecting default port-forward on container bozo port named p1(8081)
# mapping to local port 6666.
...
spec:
containers:
- name: zorg
ports:
- name: p1
containerPort: 5556
...
- name: bozo
ports:
- name: p1
containerPort: 8081
- name: p2
containerPort: 5555
...
```
The annotation value must specify a container to forward to as well as a local port and container port. The container port may be specified as either a port number or port name. If the local port is omitted then the local port will default to the container port number. Here are a few examples:
1. bozo::http - creates a pf on container `bozo` with port name http. If http specifies port number 8080 then the local port will be 8080 as well.
2. bozo::9090:http - creates a pf on container `bozo` mapping local port 9090->http(8080)
3. bozo::9090:8080 - creates a pf on container `bozo` mapping local port 9090->8080
---
## Resolved Issues
* [Issue #1299](https://github.com/derailed/k9s/issues/1299) After upgrade to 0.24.15 sorting shortcuts not working
* [Issue #1298](https://github.com/derailed/k9s/issues/1298) Install K9s through go get reporting ambiguous import error
* [Issue #1296](https://github.com/derailed/k9s/issues/1296) Crash when clicking between border of K9s and terminal pane
* [Issue #1289](https://github.com/derailed/k9s/issues/1289) Homebrew calling bottle :unneeded is deprecated! There is no replacement
* [Issue #1273](https://github.com/derailed/k9s/issues/1273) Not loading config from correct default location when XDG_CONFIG_HOME is unset
* [Issue #1268](https://github.com/derailed/k9s/issues/1268) Age sorting wrong for years
* [Issue #1258](https://github.com/derailed/k9s/issues/1258) Configurable or recent use based port-forward
* [Issue #1257](https://github.com/derailed/k9s/issues/1257) Why is the latest chocolatey on 0.24.10
* [Issue #1243](https://github.com/derailed/k9s/issues/1243) Port forward fails in kind on windows 10
---
## PRs
* [PR #1300](https://github.com/derailed/k9s/pull/1300) move from io/ioutil to io/os packages
* [PR #1287](https://github.com/derailed/k9s/pull/1287) Add missing styles to kiss
* [PR #1286](https://github.com/derailed/k9s/pull/1286) Some small color modifications
* [PR #1284](https://github.com/derailed/k9s/pull/1284) Fix a small typo which comes from cluster view info
* [PR #1271](https://github.com/derailed/k9s/pull/1271) Removed cursor colors that are too light to read
* [PR #1266](https://github.com/derailed/k9s/pull/1266) Skin to preserve your terminal session background color
* [PR #1264](https://github.com/derailed/k9s/pull/1205) Adding note on popeye config
* [PR #1261](https://github.com/derailed/k9s/pull/1261) Blurry logo
* [PR #1250](https://github.com/derailed/k9s/pull/1250) Gruvbox dark skin
* [PR #1249](https://github.com/derailed/k9s/pull/1249) Node shell pod tolerate all taints
* [PR #1232](https://github.com/derailed/k9s/pull/1232) Add red skin for production env
* [PR #1227](https://github.com/derailed/k9s/pull/1227) Add abbreviation ReadWriteOncePod PV access mode
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.1.md
================================================
# Release v0.25.1
## 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!
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)
## Maintenance Release!
Looks like we've broken a few little thingies...
May need a few rapid fires to regain some sanity so please bare with us and thank you for your reports!!
---
## Resolved Issues
* [Issue #1308](https://github.com/derailed/k9s/issues/1308) Command auto-complete suggestions disappear after screen refresh interval #1308
* [Issue #1307](https://github.com/derailed/k9s/issues/1307) Displayed Cluster name is always read from current-context
* [Issue #1296](https://github.com/derailed/k9s/issues/1244) Scoobie-Doo was not a cow - NOTE: Switch to dialog to keep live context!
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.10.md
================================================
# Release v0.25.10
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Maintenance Release!
Doh! Sorry ;( with feelings...
---
## Resolved Issues
* [Issue #1361](https://github.com/derailed/k9s/issues/1361) Pulses not displaying graphs
* [Issue #1358](https://github.com/derailed/k9s/issues/1358) Namespace list is empty
* [Issue #1357](https://github.com/derailed/k9s/issues/1357) Benchmarks doesn't work on windows
* [Issue #1355](https://github.com/derailed/k9s/issues/1355) Trace log level does not exist
* [Issue #1345](https://github.com/derailed/k9s/issues/1345) Access denied after context switch
---
## PRs
* [PR #1363](https://github.com/derailed/k9s/pull/1363) Add rose-pine skin.
[Sergio Soria](https://github.com/sasoria)
* [PR #1356](https://github.com/derailed/k9s/pull/1356) Add flux trace shortcut to flux plugin.
[Guillaume Berche](https://github.com/gberche-orange)
* [PR #1321](https://github.com/derailed/k9s/pull/1321) Add customizable dump directory property.
[Vlasov Artem](https://github.com/VlasovArtem)
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.11.md
================================================
# Release v0.25.11
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Maintenance Release!
Hoy! end of year suck... Feeling the burn ;( Apologize for the disruptions...
---
## Resolved Issues
* [Issue #1374](https://github.com/derailed/k9s/issues/1374) --all-namespaces does not work v0.25.10
* [Issue #1376](https://github.com/derailed/k9s/issues/1376) Events not sorted correctly by dates
* [Issue #1373](https://github.com/derailed/k9s/issues/1373) change namespace not possible
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.12.md
================================================
# Release v0.25.12
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## ♫ Sounds Behind The Release ♭
* [Border Patrol - Eek A Mouse](https://www.youtube.com/watch?v=pQVNzolpoII)
* [All Mine - Portishead](https://www.youtube.com/watch?v=cuclNJiE8NY)
* [Come on up to the house - Tom Waits](https://www.youtube.com/watch?v=9XVGAatyeNk)
## Maintenance Release!
Hoy! end of year is... sucking! Feeling the burn ;( Apologies for the disruptions!!
`You're either a pigeon or... the statue!`
---
## Resolved Issues
* [Issue #1378](https://github.com/derailed/k9s/issues/1378) Regression: Namespace filters are no longer applied on startup
* [Issue #1376](https://github.com/derailed/k9s/issues/1376) Events not sorted correctly by dates
* [Issue #1375](https://github.com/derailed/k9s/issues/1375) Unable to show port forwards
* [Issue #1374](https://github.com/derailed/k9s/issues/1374) --all-namespaces does not work v0.25.10
* [Issue #1373](https://github.com/derailed/k9s/issues/1373) change namespace not possible
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.13.md
================================================
# Release v0.25.13
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [uderik](https://github.com/uderik)
* [Daimler](https://github.com/Daimler) wOOt!! Mercedes Benz sponsorship! How cool is that?
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## ♫ Sounds Behind The Release ♭
* [Gash Dem - Chuck Fenda](https://www.youtube.com/watch?v=Y4NSYW4wusI)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1382](https://github.com/derailed/k9s/issues/1382) Watcher failed for screendumps
* [Issue #1381](https://github.com/derailed/k9s/issues/1381) --request-timeout affects logs streaming
* [Issue #1380](https://github.com/derailed/k9s/issues/1380) :pulse returning error: expecting a TableRow but got *v1.Table
* [Issue #1376](https://github.com/derailed/k9s/issues/1376) Events are not sorted correctly by dates - with feelings...
* [Issue #1291](https://github.com/derailed/k9s/issues/1291) K9s do not show any error when is unable to get logs, just do not show anything.
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.14.md
================================================
# Release v0.25.14
## 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!
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)
---
## Maintenance Release!
Doh! Hot fix on the way...
---
## Resolved Issues
* [Issue #1384](https://github.com/derailed/k9s/issues/1384) Leaving Logs View Causes Crash: "panic: send on closed channel"
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.15.md
================================================
# Release v0.25.15
## 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!
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)
---
## Maintenance Release!
Aye! Hot fix on the way...
---
## Resolved Issues
* [Issue #1384](https://github.com/derailed/k9s/issues/1384) Leaving Logs View Causes Crash: "panic: send on closed channel" - with feelings!
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.16.md
================================================
# Release v0.25.16
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Sebastian Racs](https://github.com/sebracs)
* [Timothy C. Arland](https://github.com/tcarland)
* [Julie Ng](https://github.com/julie-ng)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## ♫ Sounds Behind The Release ♭
[Blue Christmas - Fats Domino](https://www.youtube.com/watch?v=7jeo09zAskc)
[Mele Kalikimaka - Bing Crosby](https://www.youtube.com/watch?v=hEvGKUXW0iI)
[Cause - Rodriguez -- Spreading The Holiday Cheer! 🤨](https://www.youtube.com/watch?v=oKFkc19T3Dk)
---
## 🎅🎄 !!Merry Christmas To All!! 🎄🎅
I hope you will take this time of the year to relax, re-source and spend quality time with your loved ones. I know it's been a `tad rocky` of recent ;( as I've gotten seriously slammed with work in the last few months...
The fine folks here on this channel have been nothing but kind, patient and willing to help, this humbles me! I feel truly blessed to be affiliated with our great `k9sers` community!
Next month, we'll celebrate our anniversary as we've started out in this venture back in Jan 2019 (Yikes!) so get crack'in and iron out those bow ties already!!
Best wishes for great health, happiness and continued success for 2022 to you all!!
-Fernand
---
## A Christmas Story...
As of this drop, we've added a new feature to override the sort column and order for a given Kubernetes resource. This feature piggy backs of custom column views and add a new attribute namely `sortColumn`. For example say you'd like to set the default sort for pods to age descending vs name/namespace, you can now do the following in your `views.yml` file in the k9s config directory:
NOTE: This file is live thus you can nav to your fav resource, change the column config and view the resource columns and sort changes... Woot!!
```yaml
k9s:
views:
v1/endpoints:
columns:
- NAME
- NAMESPACE
- ENDPOINTS
- AGE
v1/pods:
sortColumn: AGE:desc # => suffix [:asc|:desc] for ascending or descending order.
v1/services:
...
```
---
## Resolved Issues
* [Issue #1398](https://github.com/derailed/k9s/issues/1398) Pod logs containing brackets not in k9s logs output
* [Issue #1397](https://github.com/derailed/k9s/issues/1397) Regression: k9s no longer starts in current context namespace since v0.25.12
* [Issue #1358](https://github.com/derailed/k9s/issues/1358) Namespaces list is empty
* [Issue #956](https://github.com/derailed/k9s/issues/956) Feature request : Default column sort (by resource view)
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.17.md
================================================
# Release v0.25.17
## 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!
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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1402](https://github.com/derailed/k9s/issues/1402) Sort functionality does not work properly on v0.25.16
* [Issue #1401](https://github.com/derailed/k9s/issues/1401) Nothing selected when last item deleted
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.18.md
================================================
# Release v0.25.18
## 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!
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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1402](https://github.com/derailed/k9s/issues/1402) Sort functionality does not work properly on v0.25.16. With Feelings!
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.19.md
================================================
# Release v0.25.19
## 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 this repo!!
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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1609](https://github.com/derailed/k9s/issues/1609) K9s fails to launch when active view does not exist
* [Issue #1593](https://github.com/derailed/k9s/issues/1593) Selection of namespace is changed automatically
* [Issue #1572](https://github.com/derailed/k9s/issues/1572) Wrong resource configuration being display after updating ingress
* [Issue #1569](https://github.com/derailed/k9s/issues/1569) Slight wording error when port forward already existS!
* [Issue #1565](https://github.com/derailed/k9s/issues/1565) Popeye stopped working
## Resolved PR
* [PR #1601](https://github.com/derailed/k9s/pull/1601) Ensure correct text in prompt when suspending cronjob
* [PR #1600](https://github.com/derailed/k9s/pull/1600) Fix typo in fastforwards annotation name
* [PR #1566](https://github.com/derailed/k9s/pull/1566) Correct typo in skins
* [PR #1555](https://github.com/derailed/k9s/pull/1555) Update benchmark command in readme
* [PR #1553](https://github.com/derailed/k9s/pull/1553) Allow `all` deletion propagation policy
* [PR #1539](https://github.com/derailed/k9s/pull/1539) Plugin to allow default chart values retrieval
* [PR #1529](https://github.com/derailed/k9s/pull/1529) Update example k9s config file
* [PR #1518](https://github.com/derailed/k9s/pull/1518) Add Helm values support
* [PR #1493](https://github.com/derailed/k9s/pull/1493) Fix padding is not 0 in fullscreen
* [PR #1422](https://github.com/derailed/k9s/pull/1422) Fix typo in README
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.2.md
================================================
# Release v0.25.2
## 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!
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)
## Maintenance Release!
Looks like we've broken a few little thingies...
May need a few rapid fires to regain some sanity so please bare with us and thank you for your reports!!
---
## Resolved Issues
* [Issue #1311](https://github.com/derailed/k9s/issues/1311) Pressing '?' in logs view (no logs) crashes on nil dereference
* [Issue #1310](https://github.com/derailed/k9s/issues/1310) PV/PVC accessMode getting exception
* [Issue #1293](https://github.com/derailed/k9s/issues/1293) Broken rollouts for dp/sts/ds with multiple ports of the same number
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.20.md
================================================
# Release v0.25.20
## 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 this repo!!
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)
---
## Maintenance Release
Hoy!! Cleaning up the kitchen countertops ;( Thank you all for piping in on the latest drop!
---
## Resolved Issues
* [Issue #1620](https://github.com/derailed/k9s/issues/1620) popeye view shows duplicate pdb
* [Issue #1616](https://github.com/derailed/k9s/issues/1616) Age in nodes view are n/a
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.21.md
================================================
# Release v0.25.21
## 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 this repo!!
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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1634](https://github.com/derailed/k9s/issues/1634) Namespace view all has the age field in strange format
* [Issue #1633](https://github.com/derailed/k9s/issues/1633) Nodes sort by age has wrong order
## Resolved PR
* [PR #1632](https://github.com/derailed/k9s/pull/1632) Fix delete dialog dropdown styling
* [PR #1629](https://github.com/derailed/k9s/pull/1629) Fix reference to base image in dockerfile
* [PR #1627](https://github.com/derailed/k9s/pull/1627) Fix TestToAge
* [PR #1624](https://github.com/derailed/k9s/pull/1624) Change makefile version
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.3.md
================================================
# Release v0.25.3
## 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!
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)
## Maintenance Release!
Addressing broken windows builds ;(
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.4.md
================================================
# Release v0.25.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1319](https://github.com/derailed/k9s/issues/1319) Namespace filters are no longer applied on startup
* [Issue #1317](https://github.com/derailed/k9s/issues/1317) port forwarding broke with multiple exposed ports
* [Issue #1316](https://github.com/derailed/k9s/issues/1316) Configuration for macOS is using wrong path
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.5.md
================================================
# Release v0.25.5
## 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!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1327](https://github.com/derailed/k9s/issues/1327) Switching K8s resource changes view to all namespace
* [Issue #1326](https://github.com/derailed/k9s/issues/1326) Port forwarding not possible because of "invalid container port"
* [Issue #1325](https://github.com/derailed/k9s/issues/1325) Meaning of number in brackets after context name is unclear
* [Issue #1324](https://github.com/derailed/k9s/issues/1324) Problem with Configuration for macOS is can't find configuration directory
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.6.md
================================================
# Release v0.25.6
## 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!
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)
## Maintenance Release!
And the bit goes on...
---
## Resolved Issues
* [Issue #1333](https://github.com/derailed/k9s/issues/1333) Log level not showing in k9s
* [Issue #1253](https://github.com/derailed/k9s/issues/1253) Namespace filter automatically applied after viewing a deployment
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.7.md
================================================
# Release v0.25.7
## 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!
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)
## Maintenance Release!
Happy (`Wild`) Turkey Day Everyone!!
---
## Resolved Issues
* [Issue #1341](https://github.com/derailed/k9s/issues/1341) Colored container logs are not displayed correctly.
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.8.md
================================================
# Release v0.25.8
## 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!
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)
## Maintenance Release!
---
## Resolved Issues
* [Issue #1349](https://github.com/derailed/k9s/issues/1349) Support events.k8s.io Event v1
* [Issue #1345](https://github.com/derailed/k9s/issues/1345) Access denied after context switch
* [Issue #1344](https://github.com/derailed/k9s/issues/1344) Use "Port forward",but "invalid container port"
* [Issue #1342](https://github.com/derailed/k9s/issues/1342) Log screen refreshed every second
---
© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.25.9.md
================================================
# Release v0.25.9
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our K9sers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Maintenance Release!
---
## Resolved Issues
* [Issue #1361](https://github.com/derailed/k9s/issues/1361) Pulses not displaying graphs
* [Issue #1358](https://github.com/derailed/k9s/issues/1358) Namespace list is empty
* [Issue #1357](https://github.com/derailed/k9s/issues/1357) Benchmarks doesn't work on windows
* [Issue #1355](https://github.com/derailed/k9s/issues/1355) Trace log level does not exist
* [Issue #1345](https://github.com/derailed/k9s/issues/1345) Access denied after context switch
---
## PRs
* [PR #1363](https://github.com/derailed/k9s/pull/1363) Add rose-pine skin.
[Sergio Soria](https://github.com/sasoria)
* [PR #1356](https://github.com/derailed/k9s/pull/1356) Add flux trace shortcut to flux plugin.
[Guillaume Berche](https://github.com/gberche-orange)
* [PR #1321](https://github.com/derailed/k9s/pull/1321) Add customizable dump directory property.
[Vlasov Artem](https://github.com/VlasovArtem)
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.0.md
================================================
# Release v0.26.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!!
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 ♭
* [Sugar Water - Cibo Matto](https://www.youtube.com/watch?v=EN9auBn6Jys)
* [Midnight To Stevens - The Clash](https://www.youtube.com/watch?v=9suQJthS6to)
* [Cool & Proper - Natty Nation](https://www.youtube.com/watch?v=9q337zn7bpI)
---
## Maintenance Release
Please join me in giving a big THANK YOU and ATTA BOY!! to [Aleksei Romanenko](https://github.com/slimus) for allocating his personal time in helping out his fellow K9sers with issues, PRs and slack!!
Also in the last drop, I'd updated k8s API's to the latest which caused some `disturbance in the farce!` and hosed AWS cluster connections in the same swop ;( Please see [Issue#119](https://github.com/derailed/k9s/issues/1619) for `a` resolve... I did not catch it early enough hence the release bump on this drop. My bad!!
---
## Resolved Issues
* [Issue #1655](https://github.com/derailed/k9s/issues/1655) Text not appearing in context windows
* [Issue #1654](https://github.com/derailed/k9s/issues/1654) K9s crash on m1 with index out of range [0] with length 0
* [Issue #1652](https://github.com/derailed/k9s/issues/1652) HPA with custom metrics has "Target%" column showing "unknown/unknown"
* [Issue #1639](https://github.com/derailed/k9s/issues/1639) Helm releases view broken after interacting with 0.25.21
## Resolved PR
* [PR #1656](https://github.com/derailed/k9s/pull/156) Fix PF and RS dialog colors
* [PR #163](https://github.com/derailed/k9s/pull/1636) Fix #1636: can't switch context with --kubeconfig flag
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.1.md
================================================
# Release v0.26.1
## 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 ♭
Oldies but goodies...
* [Love In Vain - Rolling Stones](https://www.youtube.com/watch?v=ryRDcE2sB2A)
* [Old Love - Eric Clapton](https://www.youtube.com/watch?v=qv63M6XXgGE)
* [Warm Weather - Pieces Of A Dream](https://www.youtube.com/watch?v=hYm6fR1Zjm4)
* [Funerailles d'antan - George Brassens](https://www.youtube.com/watch?v=-mOalHzOCCM)
---
## 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!
* [Jacky Nguyen](https://github.com/nktpro)
* [Aleksei Romanenko](https://github.com/slimus)
* [Aljoscha Pörtner](https://github.com/AljoschaP)
* [Mario Bris](https://github.com/mariobris)
* [Thorsten Schifferdecker](https://github.com/curx)
* [Lungdart](https://github.com/lungdart)
* [Azar](https://github.com/azarudeena)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1684](https://github.com/derailed/k9s/issues/1684) Crash when viewing logs index out of range [2] with length 2
* [Issue #1680](https://github.com/derailed/k9s/issues/1680) Changing to pod kill grace period from 0 to 1
* [Issue #1661](https://github.com/derailed/k9s/issues/1661) ClusterRole with wrong privilege list display
* [Issue #1677](https://github.com/derailed/k9s/issues/1677) UsedBy function on priorityclass
* [Issue #1657](https://github.com/derailed/k9s/issues/1657) Cannot delete port forwarding created inside k9s
* [Issue #1420](https://github.com/derailed/k9s/issues/1420) Unable to delete port forward
## Resolved PR
* [PR #1682](https://github.com/derailed/k9s/pull/1682) Fix: persistentvolumes not showing terminating status.
* [PR #1672](https://github.com/derailed/k9s/pull/1672) Feat: allow to disable ctrl-c behavior
* [PR #1666](https://github.com/derailed/k9s/pull/1666) Feat: show usedBy for priorityclasses
* [PR #1668](https://github.com/derailed/k9s/pull/1668) Fix: PF delete with no container
---
© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.2.md
================================================
# Release v0.26.2
## 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)
---
## Maintenance Release
Doh! Looks like I've broken windows on this last drop ;(
NOTE: I currently don't have access to a windows/m1 box. So if you do please report back and help us zoom in on the issues below...
Thank you!!
---
## Resolved Issues (Wishfully...)
* [Issue #1690](https://github.com/derailed/k9s/issues/1690) 0.26.1 stuck after exit from container shell, panels refreshed but arrow keys not works windows 10.
* [Issue #1673](https://github.com/derailed/k9s/issues/1673) Screen goes blank after existing shell while running k9s on M1
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.3.md
================================================
# Release v0.26.3
## 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)
---
## Maintenance Release
---
## Resolved Issues (Wishfully...)
* [Issue #1690](https://github.com/derailed/k9s/issues/1690) 0.26.1 stuck after exit from container shell, panels refreshed but arrow keys not works windows 10.
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.4.md
================================================
# Release v0.26.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
---
## ♫ Sounds Behind The Release ♭
* [Love's Got Me High - Terrence Parker](https://www.youtube.com/watch?v=1KuLU6lpMT8)
* [New money - Calvin Harris](https://www.youtube.com/watch?v=TUVw1PTO6Sc)
* [Shrine - Jeff Beck](https://www.youtube.com/watch?v=-zBtluqp8l8)
---
## 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!!
* [Subshell](https://github.com/subshell)
* [Dan Anglin](https://github.com/dananglin)
* [Jacob Lorenzen](https://github.com/Jaxwood)
* [Benjamin Herbert](https://github.com/BenjaminHerbert)
* [Brandon G](https://github.com/gannicottb)
* [Damyan Yordanov](https://github.com/damyan)
* [Luiz Marques](https://github.com/luizfnunesmarques)
* [Argonaut](https://github.com/argonautdev)
* [Marcin Jasion](https://github.com/mjasion)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1742](https://github.com/derailed/k9s/issues/1742) Edit and shell not working on Arch linux
* [Issue #1724](https://github.com/derailed/k9s/issues/1724) redundant conversion exists
* [Issue #1714](https://github.com/derailed/k9s/issues/1714) Cronjob: don't highlight changes in `last schedule`
* [Issue #1711](https://github.com/derailed/k9s/issues/1711) Unable to see CRDs
* [Issue #1700](https://github.com/derailed/k9s/issues/1700) Ctrl+D removes a pod instantly
---
## Contributed PRs (Thank you!!)
* [PR #1759](https://github.com/derailed/k9s/pull/1759) Fix typo in cronjob
* [PR #1755](https://github.com/derailed/k9s/pull/1755) List all helm releases by default
* [PR #1753](https://github.com/derailed/k9s/pull/1753) Fix flux plugin to properly handle trace
* [PR #1744](https://github.com/derailed/k9s/pull/1744) README: correct (auto-)port-forwards annotations
* [PR #1739](https://github.com/derailed/k9s/pull/1739) Fix GracePeriodSeconds
* [PR #1725](https://github.com/derailed/k9s/pull/1725) fix redundant type conversion code
* [PR #1721](https://github.com/derailed/k9s/pull/1721) Replace keyboard package
* [PR #1711](https://github.com/derailed/k9s/pull/1711) Fix get CustomResourceDefinition
* [PR #1709](https://github.com/derailed/k9s/pull/1709) Plugin for opening a root shell to k3d container
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.5.md
================================================
# Release v0.26.5
## 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)
---
## Maintenance Release
So it looks like replacing the clipboard package was indeed a dud ;(
While I was not keen on either running with cgo or taking on external dependencies, after further investigation it looks like the clipboard + wsl issue in the old package was [resolved](https://github.com/atotto/clipboard/pull/42). I don't run WSL so I can't test it but if that's not the case please reopen and we will figure out another solution. For the time being, I've opted for the reversal.
Thank you!!
---
## Resolved Issues
* [Issue #1742](https://github.com/derailed/k9s/issues/1770) copy to clipboard throw panic error
* [Issue #1768](https://github.com/derailed/k9s/issues/1768) build fails due to new clipboard package
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.6.md
================================================
# Release v0.26.6
## 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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1773](https://github.com/derailed/k9s/issues/1773) CustomResourceDefinition does not display
## Contributed PRs (Thank you!!)
* [PR #1777](https://github.com/derailed/k9s/pull/1777) Fix directory path when viewing screendump
* [PR #1776](https://github.com/derailed/k9s/pull/1776) Add a closing tag when showing timestamp in log view
* [PR #1775](https://github.com/derailed/k9s/pull/1775) Log toggles: add a space after "on" in logs view
* [PR #1772](https://github.com/derailed/k9s/pull/1772) docs: update homebrew installation note
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.26.7.md
================================================
# Release v0.26.7
## 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)
---
## Maintenance Release
---
## 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!!
* [Microsoft](https://github.com/microsoft)
* [Audun V. Nes](https://github.com/avnes)
* [Marco Aurelio Caldas Miranda](https://github.com/macmiranda)
* [Jon Waltom](https://github.com/jon-walton)
* [Eckl, Máté](https://github.com/ecklm)
* [Iguanasoft](https://github.com/iguanasoft)
---
## Resolved Issues
* [Issue #1805](https://github.com/derailed/k9s/issues/1805) CronJobs: allow sorting by LAST_SCHEDULE
## Contributed PRs (Thank you!!)
* [PR #1804](https://github.com/derailed/k9s/pull/1804) Allow multiple port forwards
* [PR #1797](https://github.com/derailed/k9s/pull/1797) README - use go install
* [PR #1793](https://github.com/derailed/k9s/pull/1793) Update CronJob version to v1
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.27.0.md
================================================
# Release v0.27.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)
---
## Maintenance Release
---
## ♫ Sounds Behind The Release ♭
I'd like to dedicate this release to `Jeff Beck` one of my all time favorite musicians that sadly passed away this last week ;(
* [The Pump - Jeff Beck](https://www.youtube.com/watch?v=xiDYrQp9wFQ)
* [Brush With The Blues - Jeff Beck](https://www.youtube.com/watch?v=O640IGLjnfs)
* [Cause We've Ended As Lovers - Jeff Beck](https://www.youtube.com/watch?v=VC02wGj5gPw)
* [Where Were You - Jeff Beck](https://www.youtube.com/watch?v=howz7gVecjE)
* [Rockabilly Set At Ronnie Scott](https://www.youtube.com/watch?v=_3aIEzXHBWw)
---
## 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!!
* [Vibin reddy](https://github.com/vibin)
* [Maciek Albin](https://github.com/mckk)
* [Dherraj Yennam](https://github.com/dyennam)
* [Alan Ream](https://github.com/aream2006)
* [djheap](https://github.com/djheap)
* [MaterializeInc](https://github.com/MaterializeInc)
* [Jeff Evans](https://github.com/jeff303)
---
## Resolved Issues
* [Issue #1917](https://github.com/derailed/k9s/issues/1917) Crash on open single ingress from list
* [Issue #1906](https://github.com/derailed/k9s/issues/1680) k9s exits silently if screenDumpDir cannot be created
* [Issue #1661](https://github.com/derailed/k9s/issues/1661) ClusterRole with wrong privilege list display
* [Issue #1680](https://github.com/derailed/k9s/issues/1680) Change pod kill grace period for 0 to 1
## Contributed PRs
Please give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [PR #1910](https://github.com/derailed/k9s/pull/1910) Replace x86_64 to amd64 build
* [PR #1877](https://github.com/derailed/k9s/pull/1877) Bug: portforward custom containers not showing
* [PR #1874](https://github.com/derailed/k9s/pull/1874) Feat: Add noLatestRevCheck config option
* [PR #1872](https://github.com/derailed/k9s/pull/1872) Docs: Add k8s client compatibility matrix
* [PR #1871](https://github.com/derailed/k9s/pull/1871) Bug: update scanSA calls to account for blank service accounts
* [PR #1866](https://github.com/derailed/k9s/pull/1866) Bug: Fix order of arguments for CanI function call
* [PR #1859](https://github.com/derailed/k9s/pull/1859) FEAT: Add vim-like quit force option
* [PR #1849](https://github.com/derailed/k9s/pull/1849) Bug: Fix build date for OSX
* [PR #1847](https://github.com/derailed/k9s/pull/1847) FEAT: Add labels configuration for shell node pod
* [PR #1840](https://github.com/derailed/k9s/pull/1840) FEAT: Add policy view to service accounts
* [PR #1837](https://github.com/derailed/k9s/pull/1837) FEAT: Use default terminal colors for better readability
* [PR #1830](https://github.com/derailed/k9s/pull/1830) FEAT: Plugin support for carvel kapp CR
* [PR #1829](https://github.com/derailed/k9s/pull/1829) FEAT: flux.yml plugin new displays stderr messages
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.27.1.md
================================================
# Release v0.27.1
## 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)
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1943](https://github.com/derailed/k9s/issues/1943) k9s display is broken after switching to v0.27.0
* [Issue #1935](https://github.com/derailed/k9s/issues/1935) Active namespace is dropped after accessing forbidden resources
* [Issue #1913](https://github.com/derailed/k9s/issues/1913) Exit edit mode deadlock
* [Issue #1895](https://github.com/derailed/k9s/issues/1895) AWS workspace. K9s fails on startup with unknown userid error
* [Issue #1842](https://github.com/derailed/k9s/issues/1842) Strange one - brew installed k9s
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.27.2.md
================================================
# Release v0.27.2
## 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)
---
## Maintenance Release
With feelings... Broke brew installer ;(
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.27.3.md
================================================
# Release v0.27.3
## 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 ♭
* [Bitches Brew - Miles Davis](https://www.youtube.com/watch?v=50fB5L1vmn8)
* [Sordid Affair - Röyksopp](https://www.youtube.com/watch?v=ECL5zO6ImsA)
* [Love Inc - Booka Shade](https://www.youtube.com/watch?v=sgLxTcok8kQ)
* [Twisted - Kaz James,Nick Morgan](https://www.youtube.com/watch?v=oOsYJ-Co8Y4)
---
## 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!!
* [Astraea](https://github.com/s22)
* [Arnaud Bienvenu](https://github.com/abienvenu)
* [Eric Caleb](https://github.com/iamcaleberic)
* [Sean Williams](https://github.com/SeanThomasWilliams)
* [Federico Ragona](https://github.com/fedragon)
> Sponsorship cancellations since the last release: `7` ;(
---
## Maintenance Release
---
## Resolved Issues
* [Issue #1968](https://github.com/derailed/k9s/issues/1968) Some skins are missing the definitions for the help menu
* [Issue #1967](https://github.com/derailed/k9s/issues/1967) Helm cve-2023-25165
* [Issue #1964](https://github.com/derailed/k9s/issues/1964) logger.sinceSeconds config setting inconsistent with README
* [Issue #1955](https://github.com/derailed/k9s/issues/1955) K9s crashes with empty resources and/or verbs in RBAC
* [Issue #1954](https://github.com/derailed/k9s/issues/1954) Open very slow
* [Issue #1883](https://github.com/derailed/k9s/issues/1883) Fix force deletion
* [Issue #1788](https://github.com/derailed/k9s/issues/1788) Draining nodes cannot be forced
* [Issue #1150](https://github.com/derailed/k9s/issues/1150) Add a persistent popup for drain failures
---
## Contributed PRs
Please give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [PR #1969](https://github.com/derailed/k9s/pull/1969) fix: Add missing help menu to gruvbox-dark skin
* [PR #1966](https://github.com/derailed/k9s/pull/1966) fix: Show meaningful error message when kubectl exec fails
* [PR #1965](https://github.com/derailed/k9s/pull/1965) set default sinceSeconds to 300
* [PR #1961](https://github.com/derailed/k9s/pull/1961) feat: Add sort by pod count on node view
* [PR #1960](https://github.com/derailed/k9s/pull/1960) [Misc] Add Nightfox-theme
---
© 2022 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.27.4.md
================================================
# Release v0.27.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
---
## Core Team...
Please help me welcome Aleksei Romanenko(https://github.com/slimus) to the K9s contributor team!!
Alex is very knowledgeable in this space, kind and a great human being!
He has been instrumental with issues, prs and fielding questions in forums and slack.
🎉 Welcome Alex!!🎉
---
## 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!!
* [Jon Walton](https://github.com/jon-walton)
* [gmbnomis](https://github.com/gmbnomis)
* [Alex Viscreanu](https://github.com/aexvir)
* [Björn Petersen](https://github.com/BjoernPetersen)
* [Tanner Watson](https://github.com/tannerwatson)
* [Jabunovoty](https://github.com/jabunovoty)
* [Joey Guerra](https://github.com/joeyguerra)
* [Materialize Inc](https://github.com/MaterializeInc)
* [Kijana Woodard](https://github.com/kijanawoodard)
* [Tom Saleeba](https://github.com/tomsaleeba)
* [William Alexander](https://github.com/carpetfuz)
* [Süddeutsche Zeitung](https://github.com/sueddeutsche)
> Sponsorship cancellations since the last release: `12` ;(
---
## Maintenance Release
---
## Resolved Issues
* [Issue #2072](https://github.com/derailed/k9s/issues/2072) Triggered Job from cronjob is missing annotations
* [Issue #2024](https://github.com/derailed/k9s/issues/2024) Allow customization of log indicators with skin theme
* [Issue #1971](https://github.com/derailed/k9s/issues/1971) Zip binary for windows
---
## Contributed PRs
Please give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [PR #2073](https://github.com/derailed/k9s/pull/2073) Fix for missing Job annotations created from Cronjob
* [PR #2069](https://github.com/derailed/k9s/pull/2069) Unify all go version to 1.20
* [PR #2067](https://github.com/derailed/k9s/pull/2067) Create narsingh skin
* [PR #2054](https://github.com/derailed/k9s/pull/2054) Update setup-go action, with caching
* [PR #2045](https://github.com/derailed/k9s/pull/2045) Fix: (views) use saved context view when switching
* [PR #2041](https://github.com/derailed/k9s/pull/2041) Feat: allow customization of log indicator toggles
* [PR #2030](https://github.com/derailed/k9s/pull/2030) Updated monokai skin with help styles, and more monokai appropriate colors
* [PR #2027](https://github.com/derailed/k9s/pull/2027) Roles are rendered using same colorer function from skin
* [PR #2045](https://github.com/derailed/k9s/pull/2045) Fix: (views) use saved context view when switching\
* [PR #2011](https://github.com/derailed/k9s/pull/2011) Fix #2007: Remove debug command
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.28.0.md
================================================
# Release v0.28.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 ♭
* [Moonlight Invasions - TribalNeed](https://www.youtube.com/watch?v=mJBnMSNIJL4&list=RDmJBnMSNIJL4&start_radio=1)
* [Teardrops - Neil Frances](https://www.youtube.com/watch?v=823_KoZr4mo)
* [Memory - Øystein Sevåg](https://www.youtube.com/watch?v=GKEM6lgkogY)
* [Tell me straight - Rolling Stones (Generated by KeithGPT 🐭)](https://www.youtube.com/watch?v=YxcxLi-Ld3E)
---
## 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!!
* [Hyeon Woo Jo](https://github.com/dokdo2013)
* [Artsiom Kaval](https://github.com/lezeroq)
* [Grant Linville](https://github.com/g-linville)
* [Andrew Brown](https://github.com/andrew-werdna)
* [Patrik Votoček](https://github.com/Vrtak-CZ)
* [Erik Hebisch](https://github.com/flegelleicht)
* [Juliet Boyd](https://github.com/julietrb1)
* [Chris Vertonghen](https://github.com/chrisv)
* [Acsone](https://github.com/acsone)
* [Alex Viscreanu](https://github.com/aexvir)
* [Joey Guerra](https://github.com/joeyguerra)
* [Kijana Woodard](https://github.com/kijanawoodard)
* [Tom Saleeba](https://github.com/tomsaleeba)
> Sponsorship cancellations since the last release: `11` ;(
---
## Feature Release
### File Transfers in Da House!
Added ability to exchange files from your local machine to a pod or from a pod to your local machine. The pod view now surfaces a new command `t` to initiate the download/upload file transfers.
---
## Resolved Issues
* [Issue #2249](https://github.com/derailed/k9s/issues/2249) Sort on the capacity column should consider Gi and Mb also
* [Issue #2225](https://github.com/derailed/k9s/issues/2225) View logs of all pods of a given deployment
* [Issue #2195](https://github.com/derailed/k9s/issues/2195) Some pod logs are not displayed. But I can display it when I use the command
* [Issue #2194](https://github.com/derailed/k9s/issues/2194) 0.27.4 broke custom sort orders via views.yml
* [Issue #2185](https://github.com/derailed/k9s/issues/2185) No binaries for Linux_x86_64
* [Issue #2169](https://github.com/derailed/k9s/issues/2169) Add namespace name in ServiceAccount view with RoleBinding
* [Issue #2152](https://github.com/derailed/k9s/issues/2152) Latest opened namespace not being saved between k9s sessions
* [Issue #2131](https://github.com/derailed/k9s/issues/2131) deployments are not showing up, whereas kubectl gives a list
* [Issue #2130](https://github.com/derailed/k9s/issues/2130) Pending pods show 0/0 Ready instead of 0/x Ready
* [Issue #2128](https://github.com/derailed/k9s/issues/2128) k9s command not found after snap install
* [Issue #2121](https://github.com/derailed/k9s/issues/2121) colors for crds
* [Issue #2120](https://github.com/derailed/k9s/issues/2120) kustomize deletion not working as expected
* [Issue #2106](https://github.com/derailed/k9s/issues/2106) k9s delete behaves differently with kubectl
* [Issue #2085](https://github.com/derailed/k9s/issues/2085) When specifying the context command via the -c flag, selecting a cluster always returns to the context view
* [Issue #658](https://github.com/derailed/k9s/issues/658) Feature request: Easy way to copy/download files from a pod/pv to your local PC
---
## Contributed PRs
Please give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [PR #2258](https://github.com/derailed/k9s/pull/2258) fix fsnotify watcher not fully working
* [PR #2253](https://github.com/derailed/k9s/pull/2253) fix manual sorting not working when sortColumn is configured
* [PR #2252](https://github.com/derailed/k9s/pull/2252) consider units when sorting capacity of pv and pvc
* [PR #2243](https://github.com/derailed/k9s/pull/2243) fix(typo): pdb header typo
* [PR #2239](https://github.com/derailed/k9s/pull/2239) fix: honor defaults from drain dialog in request
* [PR #2235](https://github.com/derailed/k9s/pull/2235) docs: add plugin.yml JSON schema
* [PR #2229](https://github.com/derailed/k9s/pull/2229) fix(log): clear bold log format after timestamp
* [PR #2188](https://github.com/derailed/k9s/pull/2188) Alias qa to quit
* [PR #2180](https://github.com/derailed/k9s/pull/2180) feat: Added support for arm in dockerfile
* [PR #2179](https://github.com/derailed/k9s/pull/2179) Focus command bar if active on startup
* [PR #2170](https://github.com/derailed/k9s/pull/2170) Add namespace for rolebinding on a clusterrole
* [PR #2161](https://github.com/derailed/k9s/pull/2161) Only apply keyConv to mnemonic in menus
* [PR #2158](https://github.com/derailed/k9s/pull/2158) Show the default container as the first entry
* [PR #2153](https://github.com/derailed/k9s/pull/2153) Changed checksums extension to checksums.sha256
* [PR #2158](https://github.com/derailed/k9s/pull/2158) Show the default container as the first entry
* [PR #2151](https://github.com/derailed/k9s/pull/2151) chore: pkg imported more than once
* [PR #2147](https://github.com/derailed/k9s/pull/2147) feat: plugin for adding an ephemeral debug container
* [PR #2141](https://github.com/derailed/k9s/pull/2141) Update plugin flux.yml with shortcuts for helm repo and oci repos
* [PR #2137](https://github.com/derailed/k9s/pull/2137) Correctly display the numbers in the Ready column of the pods view
* [PR #2136](https://github.com/derailed/k9s/pull/2136) Prompt window uses border styles
* [PR #2134](https://github.com/derailed/k9s/pull/2134) Remove unsupported key binding on users view
* [PR #2124](https://github.com/derailed/k9s/pull/2124) fix: add correct flags when deleting resources from Dir
* [PR #2119](https://github.com/derailed/k9s/pull/2119) feat: add indicator to title if toast is toggled
* [PR #2117](https://github.com/derailed/k9s/pull/2117) Add instruction how to install k9s through winget
* [PR #2112](https://github.com/derailed/k9s/pull/2112) Fix for styles
* [PR #2105](https://github.com/derailed/k9s/pull/2105) Fix the wrong/redundant icon in the prompt bar
* [PR #2103](https://github.com/derailed/k9s/pull/2103) Update carvel.yml to include contexts
* [PR #2096](https://github.com/derailed/k9s/pull/2096) fix: (config) only respect the --command flag once
* [PR #2091](https://github.com/derailed/k9s/pull/2091) Add get-all plugin specific for namespace view
* [PR #2089](https://github.com/derailed/k9s/pull/2089) Resources are rendered using skin.yaml colors
* [PR #2082](https://github.com/derailed/k9s/pull/2082) Fix typo introduced in #2045
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.28.1.md
================================================
# Release v0.28.1
## 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 ♭
* [If Trouble Was Money - Albert Collins](https://www.youtube.com/watch?v=cz6LbWWqX-g)
* [Old Love - Eric Clapton](https://www.youtube.com/watch?v=EklciRHZnUQ)
* [Touch And GO - The Cars](https://www.youtube.com/watch?v=L7Gpr_Auz8Y)
---
## 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!!
* [Bradley Heilbrun](https://github.com/bheilbrun)
> Sponsorship cancellations since the last release: `2` ;(
---
## Feature Release
### Sanitize Me!
Over time, you might end up with a lot of pod cruft on your cluster. Pods that might be completed, erroring out, etc... Once you've completed your pod analysis it could be useful to clear out these pods from your cluster.
In this drop, we introduce a new command `sanitize` aka `z` available on pod views otherwise known as `The Axe!`. This command performs a clean up of all pods that are in either in completed, crashloopBackoff or failed state. This could be especially handy if you run workflows jobs or commands on your cluster that might leave lots of `turd` pods. Tho this has a `phat` fail safe dialog please be careful with this one as it is a blunt tool!
---
## Resolved Issues
* [Issue #2281](https://github.com/derailed/k9s/issues/2281) Can't run Node shell
* [Issue #2277](https://github.com/derailed/k9s/issues/2277) bulk actions applied to power filters
* [Issue #2273](https://github.com/derailed/k9s/issues/2273) Error when draining node that is cordoned bug
* [Issue #2233](https://github.com/derailed/k9s/issues/2233) Invalid port-forwarding status displayed over the k9s UI
---
## 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!!
* [PR #2280](https://github.com/derailed/k9s/pull/2280) chore: replace github.com/ghodss/yaml with sigs.k8s.
* [PR #2278](https://github.com/derailed/k9s/pull/2278) README.md: fix typo in netshoot URL
* [PR #2275](https://github.com/derailed/k9s/pull/2275) check if the Node already cordoned when executing Drain
* [PR #2247](https://github.com/derailed/k9s/pull/2247) Delete port forwards when pods get deleted
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.28.2.md
================================================
# Release v0.28.2
## 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 ♭
* [If Trouble Was Money - Albert Collins](https://www.youtube.com/watch?v=cz6LbWWqX-g)
* [Old Love - Eric Clapton](https://www.youtube.com/watch?v=EklciRHZnUQ)
* [Touch And GO - The Cars](https://www.youtube.com/watch?v=L7Gpr_Auz8Y)
---
## 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!!
* [Bradley Heilbrun](https://github.com/bheilbrun)
> Sponsorship cancellations since the last release: `2` ;(
---
## Feature Release
### Sanitize Me!
Over time, you might end up with a lot of pod cruft on your cluster. Pods that might be completed, erroring out, etc... Once you've completed your pod analysis it could be useful to clear out these pods from your cluster.
In this drop, we introduce a new command `sanitize` aka `z` available on pod views otherwise known as `The Axe!`. This command performs a clean up of all pods that are in either in completed, crashloopBackoff or failed state. This could be especially handy if you run workflows jobs or commands on your cluster that might leave lots of `turd` pods. Tho this has a `phat` fail safe dialog please be careful with this one as it is a blunt tool!
---
## Resolved Issues
* [Issue #2281](https://github.com/derailed/k9s/issues/2281) Can't run Node shell
* [Issue #2277](https://github.com/derailed/k9s/issues/2277) bulk actions applied to power filters
* [Issue #2273](https://github.com/derailed/k9s/issues/2273) Error when draining node that is cordoned bug
* [Issue #2233](https://github.com/derailed/k9s/issues/2233) Invalid port-forwarding status displayed over the k9s UI
---
## 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!!
* [PR #2280](https://github.com/derailed/k9s/pull/2280) chore: replace github.com/ghodss/yaml with sigs.k8s.
* [PR #2278](https://github.com/derailed/k9s/pull/2278) README.md: fix typo in netshoot URL
* [PR #2275](https://github.com/derailed/k9s/pull/2275) check if the Node already cordoned when executing Drain
* [PR #2247](https://github.com/derailed/k9s/pull/2247) Delete port forwards when pods get deleted
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.29.0.md
================================================
# Release v0.29.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 ♭
* [Snowbound - Donald Fagen](https://www.youtube.com/watch?v=bj8ZdBdKsfo)
* [Pilgrim - Eric Clapton](https://www.youtube.com/watch?v=8V9tSQuIzbQ)
* [Lucky Number - Lene Lovich](https://www.youtube.com/watch?v=KnIJOO__jVo)
---
## 🦃 Happy (Belated!) ThanksGiving To All! 🦃
Hope you and yours had a wonderful holiday!!
Hopefully this drop won't be a cold turkey 😳
I'd like to take this opportunity to honor two very special folks:
* [Alexandru Placinta](https://github.com/placintaalexandru)
* [Jayson Wang](https://github.com/wjiec)
These guys have been relentless in fishing out bugs, helping out with support and addressing issues, not to mention enduring my code! 🙀
They dedicate a lot of their time to make `k9s` better for all of us!
So if you happen to run into them live/virtual, please be sure to `Thank` them and give them a huge hug! 🤗
I am thankful for all of you for being kind, patient, understanding and one of the coolest OSS community on the web!!
Feeling blessed and ever so humbled to be part of it.
Thank you!!
---
## 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!!
* [Marco Stuurman](https://github.com/fe-ax)
* [Paul Sweeney](https://github.com/Kolossi)
* [Cayla Fauver](https://github.com/cayla)
* [alemanek](https://github.com/alemanek)
* [Danske Commodities A/S](https://github.com/DanskeCommodities)
> Sponsorship cancellations since the last release: **8** ;(
---
## 🎉 Feature Release 🎈👯
---
### Breaking Bad!
WARNING! There are breaking change on this drop!
1. NodeShell configuration has moved up in the k9s config file from the context section to the top level config.
More than likely, one uses the same nodeShell image with all the fixins to introspect nodes no matter the cluster. This update DRY's up k9s config and still allows one to opt in/out of nodeShell via the context specific feature gate.
Please see README for the details.
> NOTE: If you haven't customize the shellPod images on your contexts, the app will move the nodeShell config section to
> it's new location and update your clusters information accordingly.
> If not, you will need to edit the nodeShell section and manage it from a single location!
1. Log view used to default to the last 5mins aka `sinceSeconds: 300`.
Changed the default to tail logs instead aka `sinceSeconds: -1`
1. Skins loading changed! In this release, we do away with the context specific skin files. You can now directly specify the skin to use for a given cluster directly in the k9s config file under the cluster configuration. K9s now expects a skins directory in the k9s config home with your skin files. You can use your custom skins and copy them to the `skins` directory or use the contributes skins found on this repo root.
Specify the name of the skin in the config file and now your cluster will load the specified skin.
For example: create a `skins` dir your k9s config home and add one_dark.yml skin file from this repo. Then edit your k9s config file as follows:
```yaml
k9s:
...
clusters:
fred:
# Override the default skin and use this skin for this cluster.
skin: one_dark # -> Look for a skin file in ~/.config/k9s/skins/one_dark.yml
namespace:
...
view:
active: pod
featureGates:
nodeShell: false
portForwardAddress: localhost
```
The `fred` cluster will now load with the specified skin name. Rinse and repeat for other clusters of your liking. In the case where neither the skin dir or skin file are present, k9s will still honor the global skin aka `skin.yml` in your k9s config home directory to skin all your clusters.
---
### Walk Of SHelm...
Added a `Releases` view to Helm!
This provides the ability for Helm users to manage their releases directly from k9s.
You can now press `enter` on a selected Helm install and view all associated releases.
While in the releases view, you can also rollback an install to a previous revision.
---
### Spock! Are You Out Of Your VulScan Mind?
Tired of having malignant folks shoot holes in your prod clusters or failing compliance testing?
Added ability to run image vulnerability scans directly from k9s. You can now monitor your security stance in dev/staging/... clusters
prior to proclaiming `It's Open Season...` in prod!
As it stands Pod, Deployment, StatefulSet, DaemonSet, CronJob, Job views will feature a new column for Vulnerability Scan aka `VS`.
> NOTE! This feature is gated so you'll need to manually opt in/out by modifying your k9s config file like so:
```yaml
k9s:
liveViewAutoRefresh: false
enableImageScan: true # <- Yes Please!!
headless: false
...
```
Once enabled, a new column `VS` (aka Vulnerability Score) should be present on the aforementioned views where you will see your vulnerability scores (*Still work in progress!!*).
The `VS` column displays a bit vector aka Sev-1|Sev-2|Sev-3|Sev-4|Sev-5|Sev-Unknown. When the bit is high it indicate the presence of the severity in the scans. Higher order bits = Higher severity
For instance, the following vector `110001` indicates the presence of both critical (Sev-1) and high (Sev-2) and an unclassified severity (aka Sev-Unknown) issues in the scan. Sev-U indicates no classification currently exist in our vulnerability database.
The image scans are run async, rendering the views eventually consistent, hence you may have to give the scores a few cycles for the dust to settle...
Once the caches are primed, subsequent loads should be faster 🤞
You can sort the views by vulnerability score using `ShiftV`.
Additionally, you can view the full scans report by pressing `v` on a selected resource.
I've synced my entire Thanksgiving holiday break on this ding dang deal, so hopefully it works for most of you??
Also if you dig this new feature, please make some noise! 😍
💘 This is an experimental feature and likely will require additional TLC 💘
> NOTE! The lib we use to scan for vulnerabilities only supports macOS and Linux!!
> NOTE: I have yet to test this feature on larger clusters, so likely this may break??
> Please take these reports with a grain of salt as likely your mileage will vary and help us
> validate the accuracy of the report ie if we cry `Wolf`, is it actually there?
The paint is still fresh on this deal!!
### Do You Tube?
My plan is to begin (again!) putting out short k9s episodes with how-tos, tips, tricks and features previews.
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
The first drop should be up by the time you read this!
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2308](https://github.com/derailed/k9s/issues/2308) Unable to list CRs for crd with only list and get verb without watch verb
* [#2301](https://github.com/derailed/k9s/issues/2301) Add imagePullPolicy and imagePullSecrets on shell_pod for internal registry uses
* [#2298](https://github.com/derailed/k9s/issues/2298) Weird color after plugin usage
* [#2297](https://github.com/derailed/k9s/issues/2297) Select nodes with space does not work anymore
* [#2290](https://github.com/derailed/k9s/issues/2290) Provide release assets for freebsd amd64/arm64
* [#2283](https://github.com/derailed/k9s/issues/2283) Adding auto complete in search bar
* [#2219](https://github.com/derailed/k9s/issues/2219) Add tty: true to the node shell pod manifest
* [#2167](https://github.com/derailed/k9s/issues/2167) Show wrong Configmap data
* [#2166](https://github.com/derailed/k9s/issues/2166) Taint count for the nodes view
* [#2165](https://github.com/derailed/k9s/issues/2165) Restart counter for init containers
* [#2162](https://github.com/derailed/k9s/issues/2162) Make edit work when describing a resource
* [#2154](https://github.com/derailed/k9s/issues/2154) Help and h command does not work if typed into cmdbuff
* [#2036](https://github.com/derailed/k9s/issues/2036) Crashed while do filtering
* [#2009](https://github.com/derailed/k9s/issues/2009) Ctrl-s: Name of file (Describe-....)
* [#1513](https://github.com/derailed/k9s/issues/1513) Problem regarding showing the logs - it hangs/slow on pods which are running for long time
NOTE: Better but not cured! Perf improvements while viewing large cm (7k lines) from 26s->9s
* [#568](https://github.com/derailed/k9s/issues/568) Allow both .yaml and .yml yaml config files
---
## 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!!
* [#2322](https://github.com/derailed/k9s/pull/2322) Check if the service provides selectors
* [#2319](https://github.com/derailed/k9s/pull/2319) Proper handling of help commands (fixes #2154)
* [#2315](https://github.com/derailed/k9s/pull/2315) Fix namespace suggestion error on context switch
* [#2313](https://github.com/derailed/k9s/pull/2313) Should not clear screen when executing plugin command
* [#2310](https://github.com/derailed/k9s/pull/2310) chore: Mot recommended to use k8s.io/kubernetes as a dependency
* [#2303](https://github.com/derailed/k9s/pull/2303) Clean up items
* [#2301](https://github.com/derailed/k9s/pull/2301) feat: Add imagePullSecrets and imagePullPolicy configuration for shellpod
* [#2289](https://github.com/derailed/k9s/pull/2289) Clean up issues introduced in #2125
* [#2288](https://github.com/derailed/k9s/pull/2288) Fix merge issues from PR #2168
* [#2284](https://github.com/derailed/k9s/issues/2284) Allow both .yaml and .yml yaml config files
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.29.1.md
================================================
# Release v0.29.1
## 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)
---
## Maintenance Release
---
## Resolved Issues
* [#2330](https://github.com/derailed/k9s/issues/2330) Skins don't work v0.29.0
* [#2329](https://github.com/derailed/k9s/issues/2329) New skin system in v0.29.0 doesn't work if you use different k8s context files
* [#2327](https://github.com/derailed/k9s/issues/2327) [Bug] Item highlighting broke in v0.29.0
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.0.md
================================================
# 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 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 with 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 excludes certain namespaces or specific workload labels
exclusions:
# 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 given 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 workloads 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 `kubectl 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.
---
# Vulnerability Scan Exclusions...
As it seems customary with all k9s new features, folks want to turn them off ;(
The `Vulscan` feature did not get out unscathed ;(
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 an exclusion 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
exclusions:
# 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
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.1.md
================================================
# Release v0.30.1
## 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)
## 🎄 Maintenance Release! 🎄
🎵 `On The eleventh day of Christmas my true love gave to me... Bugs!!` 🎵
Got to love the aftermath... Thank you all for pitch'in in and help flesh out bugs!! The gift that keeps on... giving?
🎅 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)
---
## Resolved Issues
* [#2368](https://github.com/derailed/k9s/issues/2368) Pod CPU and MEM columns are empty in 0.30.0
* [#2367](https://github.com/derailed/k9s/issues/2367) k9s 0.30.0 issue loading plugins
* [#2366](https://github.com/derailed/k9s/issues/2366) List pods of deployment is now impossible
* [#2364](https://github.com/derailed/k9s/issues/2364) k9s 0.30.0 fields and values missed in action in the "namespace view"
* [#2363](https://github.com/derailed/k9s/issues/2363) Default 0.30.0 default skin on macOS is no good
---
## 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!!
* [#2360](https://github.com/derailed/k9s/pull/2360) adding cancelable launch prompts to NodeShell
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.2.md
================================================
# Release v0.30.2
## 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)
## 🎄 Maintenance Release! 🎄
🎵 `On The eleventh day of Christmas my true love gave to me... More Bugs!!` 🎵
Thank you all for pitching in and help flesh out bugs!!
---
## [!!FEATURE NAME CHANGED!!] Vulnerability Scan Exclusions...
As it seems customary with all k9s new features, folks want to turn them off ;(
The `Vulscan` feature did not get out unscathed ;(
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 an exclusion 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
# MOTE!! Field Name changed!!
exclusions:
# Skip scans on these namespaces
namespaces:
- ns-1
- ns-2
# Skip scans for pods matching these labels
labels:
- app:
- fred
- blee
- duh
- env:
- dev
```
---
## 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)
---
## Resolved Issues
* [#2374](https://github.com/derailed/k9s/issues/2374) The headless parameter does not function properly (v0.30.1)
* [#2372](https://github.com/derailed/k9s/issues/2372) Unable to set default resource to load (v0.30.1)
* [#2371](https://github.com/derailed/k9s/issues/2371) --write cli option does not work (0.30.X)
* [#2370](https://github.com/derailed/k9s/issues/2370) Wrong list of pods on node (0.30.X)
* [#2362](https://github.com/derailed/k9s/issues/2362) blackList: Use inclusive language alternatives
---
## 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!!
* [#2375](https://github.com/derailed/k9s/pull/2375) get node filtering params from matching context values
* [#2373](https://github.com/derailed/k9s/pull/2373) fix command line flags not working
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.3.md
================================================
# Release v0.30.3
## 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)
## 🎄 Maintenance Release! 🎄
🎵 `On The twelfth day of Christmas my true love gave to me... More Bugs!!` 🎵
Thank you all for pitching in and help flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2379](https://github.com/derailed/k9s/issues/2379) Filtering with equal sign (=) does not work in 0.30.X
* [#2378](https://github.com/derailed/k9s/issues/2378) Logs directory not created in the k9s config/home dir 0.30.1
* [#2377](https://github.com/derailed/k9s/issues/2377) Opening AWS EKS contexts create two directories per cluster 0.30.1
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.4.md
================================================
# Release v0.30.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
## 🎄 Maintenance Release! 🎄
Thank you all for pitching in and helping flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2391](https://github.com/derailed/k9s/issues/2391) Version 0.30.* has issues with : chars in the cluster names from AWS
* [#2397](https://github.com/derailed/k9s/issues/2387) Error: invalid namespace xxx
* [#2389](https://github.com/derailed/k9s/issues/2389) Mixed-case named contexts cannot be switched to from contexts view
* [#2382](https://github.com/derailed/k9s/issues/2382) Header always shows Cluster from kubeconfig current-context
---
## 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!!
* [#2390](https://github.com/derailed/k9s/pull/2390) case sensitive for specific command args and flags
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.5.md
================================================
# Release v0.30.5
## 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)
## 🎄 Maintenance Release! 🎄
Thank you all for pitching in and helping flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2394](https://github.com/derailed/k9s/issues/2394) Allow setting custom log dir
* [#2393](https://github.com/derailed/k9s/issues/2393) When switching contexts k9s does not switch to cluster's pod/namespaces/other k8s kinds view
* [#2387](https://github.com/derailed/k9s/issues/2387) Invalid namespace xxx - with feelings!
---
## 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!!
* [#2396](https://github.com/derailed/k9s/pull/2396) feat: allow to customize logs dir through environment variable
* [#2395](https://github.com/derailed/k9s/pull/2395) fix: create user tmp directory before the app one
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.6.md
================================================
# Release v0.30.6
## 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)
## 🎄 Maintenance Release! 🎄
Thank you all for pitching in and helping flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2401](https://github.com/derailed/k9s/issues/2401) Context completion broken with mixed case context names
* [#2400](https://github.com/derailed/k9s/issues/2400) Panic on start if dns lookup fails
* [#2387](https://github.com/derailed/k9s/issues/2387) Invalid namespace xxx - with feelings??
---
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.7.md
================================================
# Release v0.30.7
## 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)
## Maintenance Release!
Thank you all for pitching in and helping flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2414](https://github.com/derailed/k9s/issues/2414) View pods with context filter, along with namespace filter, prompts an error if the namespace exists only in the desired context
* [#2413](https://github.com/derailed/k9s/issues/2413) Typing apply -f in command bar causes k9s to crash
* [#2407](https://github.com/derailed/k9s/issues/2407) Long-running background plugins block UI rendering
---
## 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!!
* [#2415](https://github.com/derailed/k9s/pull/2415) Add boundary check for args parser
* [#2411](https://github.com/derailed/k9s/pull/2411) Use dash as a standard word separator in skin names
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.30.8.md
================================================
# Release v0.30.8
## 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)
## Maintenance Release!
Thank you all for pitching in and helping flesh out issues!!
---
## 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)
---
## Resolved Issues
* [#2423](https://github.com/derailed/k9s/issues/2423) CPU and MEM counters of AKS clusters show not available
* [#2418](https://github.com/derailed/k9s/issues/2418) Boom! runtime error: invalid memory address or nil pointer dereference
---
## 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!!
* [#2424](https://github.com/derailed/k9s/pull/2424) fix the check for whether the cluster supports metrics
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.0.md
================================================
# Release v0.31.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 ♭
* [Border Crossing - Eek A Mouse](https://www.youtube.com/watch?v=KaAC9dBPcOM)
* [The Weight - The Band](https://www.youtube.com/watch?v=FFqb1I-hiHE)
* [Wonderin' - Neil Young](https://www.youtube.com/watch?v=h0PlwVPbM5k)
* [When Your Lover Has Gone - Louis Armstrong](https://www.youtube.com/watch?v=1tdfIj0fvlA)
---
## 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!!
* [Jacky Nguyen](https://github.com/nktpro)
* [Eckl, Máté](https://github.com/ecklm)
* [Jörgen](https://github.com/wthrbtn)
* [kmath313](https://github.com/kmath313)
* [a-thomas-22](https://github.com/a-thomas-22)
* [wpbeckwith](https://github.com/wpbeckwith)
* [Dima Altukhov](https://github.com/alt-dima)
* [Shoshin Nikita](https://github.com/ShoshinNikita)
* [Tu Hoang](https://github.com/rebyn)
* [Andreas Frangopoulos](https://github.com/qubeio)
> Sponsorship cancellations since the last release: **7!** 🥹
## Feature Release!
😳 Found a few issues in the neutrino drive...
This is another fairly heavy drop so bracing for impact 😱
Be sure to dial in the v0.31.0 SneakPeek video below for the gory details!
😵 Hopefully we've move the needle in the right direction on this drop... 🤞
Thank you all for your kindness, feedback and assistance in flushing out issues!!
### Hold My Hand...
In this drop, we've added schema validation to ensure various configs are setup as expected.
K9s will now run validation checks on the following configurations:
1. K9s main configuration (config.yaml)
2. Context specific configs (clusterX/contextY/config.yaml)
3. Skins
4. Aliases
5. HotKeys
6. Plugins
7. Views
K9s behavior changed in this release if the main configuration does not match schema expectations.
In the past, the configuration will be validated, updated and saved should validation checks failed. Now the app will stop and report validation issues.
The schemas are set to be a bit loose for the time being. Once we/ve vetted they are cool, we could publish them out (with additional TLC!) so k9s users can leverage them in their favorite editors.
In the meantime, you'll need to keep k9s logs handy, to check for validation errors. The validation messages can be somewhat cryptic at times and so please be sure to include your debug logs and config settings when reporting issues which might be plenty ;(.
### Breaking Bad!
Configuration changes:
1. DRY fullScreenLogs -> fullScreens (k9s root config.yaml)
```yaml
# $XDG_CONFIG_HOME/k9s/config.yaml
k9s:
liveViewAutoRefresh: false
logger:
sinceSeconds: -1
fullScreen: false # => Was fullScreenLogs
...
```
2. Views Configuration.
To match other configurations the root is now `views:` vs `k9s: views:`
```yaml
# $XDG_CONFIG_HOME/k9s/views.yaml
views: # => Was k9s:\n views:
v1/pods:
columns:
- AGE
- NAMESPACE
...
```
### Serenity Now!
You can now opt in/out of the `reactive ui` feature. This feature enable users to make change to some configurations and see changes reflected live in the ui. This feature is now disabled by default and one must opt-in to enable via `k9s.UI.reactive`
Reactive UI provides for monitoring various config files on disk and update the UI when changes to those files occur. This is handy while tuning skins, plugins, aliases, hotkeys and benchmarks parameters.
```yaml
# $XDG_CONFIG_HOME/k9s/config.yaml
k9s:
liveViewAutoRefresh: false
UI:
...
reactive: true # => enable/disable reactive UI
...
```
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2434](https://github.com/derailed/k9s/issues/2434) readOnly: true in config.yaml doesn't get overridden by readOnly: false in cluster config
* [#2430](https://github.com/derailed/k9s/issues/2430) Referencing a namespace with the name of an alias inside an alias causes infinite loop
* [#2428](https://github.com/derailed/k9s/issues/2428) Boom!! runtime error: invalid memory address or nil pointer dereference - v0.30.8
* [#2421](https://github.com/derailed/k9s/issues/2421) k9s/config.yaml configuration file is overwritten on launch
---
## 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!!
* [#2433](https://github.com/derailed/k9s/pull/2433) switch contexts only when needed
* [#2429](https://github.com/derailed/k9s/pull/2429) Reference correct configuration ENV var in README
* [#2426](https://github.com/derailed/k9s/pull/2426) Update carvel plugin kick to shift K
* [#2420](https://github.com/derailed/k9s/pull/2420) supports referencing envs in hotkeys
* [#2419](https://github.com/derailed/k9s/pull/2419) fix typo
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.1.md
================================================
# Release v0.31.1
## 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 ♭
* [Border Crossing - Eek A Mouse](https://www.youtube.com/watch?v=KaAC9dBPcOM)
* [The Weight - The Band](https://www.youtube.com/watch?v=FFqb1I-hiHE)
* [Wonderin' - Neil Young](https://www.youtube.com/watch?v=h0PlwVPbM5k)
* [When Your Lover Has Gone - Louis Armstrong](https://www.youtube.com/watch?v=1tdfIj0fvlA)
---
## 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!!
* [Jacky Nguyen](https://github.com/nktpro)
* [Eckl, Máté](https://github.com/ecklm)
* [Jörgen](https://github.com/wthrbtn)
* [kmath313](https://github.com/kmath313)
* [a-thomas-22](https://github.com/a-thomas-22)
* [wpbeckwith](https://github.com/wpbeckwith)
* [Dima Altukhov](https://github.com/alt-dima)
* [Shoshin Nikita](https://github.com/ShoshinNikita)
* [Tu Hoang](https://github.com/rebyn)
* [Andreas Frangopoulos](https://github.com/qubeio)
> Sponsorship cancellations since the last release: **7!** 🥹
## Feature Release!
😳 Found a few issues in the neutrino drive...
This is another fairly heavy drop so bracing for impact 😱
Be sure to dial in the v0.31.0 SneakPeek video below for the gory details!
😵 Hopefully we've move the needle in the right direction on this drop... 🤞
Thank you all for your kindness, feedback and assistance in flushing out issues!!
> ☢️ Repeating v0.31.0 release notes here as we tweaked the initial drop ☢️
### Hold My Hand...
In this drop, we've added schema validation to ensure various configs are setup as expected.
K9s will now run validation checks on the following configurations:
1. K9s main configuration (config.yaml)
2. Context specific configs (clusterX/contextY/config.yaml)
3. Skins
4. Aliases
5. HotKeys
6. Plugins
7. Views
K9s behavior changed in this release if the main configuration does not match schema expectations.
In the past, the configuration will be validated, updated and saved should validation checks failed. Now the app will stop and report validation issues.
The schemas are set to be a bit loose for the time being. Once we/ve vetted they are cool, we could publish them out (with additional TLC!) so k9s users can leverage them in their favorite editors.
In the meantime, you'll need to keep k9s logs handy, to check for validation errors. The validation messages can be somewhat cryptic at times and so please be sure to include your debug logs and config settings when reporting issues which might be plenty ;(.
### Breaking Bad!
With this release, k9s may not start correctly if the config.yaml configurations are incorrect!
Configuration changes:
1. DRY fullScreenLogs -> fullScreens (k9s root config.yaml)
```yaml
# $XDG_CONFIG_HOME/k9s/config.yaml
k9s:
liveViewAutoRefresh: false
logger:
sinceSeconds: -1
fullScreen: false # => Was fullScreenLogs
...
```
2. Views Configuration.
To match other configurations the root is now `views:` vs `k9s: views:`
```yaml
# $XDG_CONFIG_HOME/k9s/views.yaml
views: # => Was k9s:\n views:
v1/pods:
columns:
- AGE
- NAMESPACE
...
```
### Serenity Now!
You can now opt in/out of the `reactive ui` feature. This feature enable users to make change to some configurations and see changes reflected live in the ui. This feature is now disabled by default and one must opt-in to enable via `k9s.UI.reactive`
Reactive UI provides for monitoring various config files on disk and update the UI when changes to those files occur. This is handy while tuning skins, plugins, aliases, hotkeys and benchmarks parameters.
```yaml
# $XDG_CONFIG_HOME/k9s/config.yaml
k9s:
liveViewAutoRefresh: false
UI:
...
reactive: true # => enable/disable reactive UI
...
```
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2434](https://github.com/derailed/k9s/issues/2434) readOnly: true in config.yaml doesn't get overridden by readOnly: false in cluster config
* [#2430](https://github.com/derailed/k9s/issues/2430) Referencing a namespace with the name of an alias inside an alias causes infinite loop
* [#2428](https://github.com/derailed/k9s/issues/2428) Boom!! runtime error: invalid memory address or nil pointer dereference - v0.30.8
* [#2421](https://github.com/derailed/k9s/issues/2421) k9s/config.yaml configuration file is overwritten on launch
---
## 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!!
* [#2433](https://github.com/derailed/k9s/pull/2433) switch contexts only when needed
* [#2429](https://github.com/derailed/k9s/pull/2429) Reference correct configuration ENV var in README
* [#2426](https://github.com/derailed/k9s/pull/2426) Update carvel plugin kick to shift K
* [#2420](https://github.com/derailed/k9s/pull/2420) supports referencing envs in hotkeys
* [#2419](https://github.com/derailed/k9s/pull/2419) fix typo
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.2.md
================================================
# Release v0.31.2
## 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)
## Maintenance Release!
Yikes! The aftermath...
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` doesn't really help us zero in. Everyone has slightly different settings/platforms so every little bits of info helps with the resolves.
Thank you!!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2449](https://github.com/derailed/k9s/issues/2449) [Bug]: views.yaml columns not respected on startup
* [#2448](https://github.com/derailed/k9s/issues/2448) Missing '.thresholds' in config.yaml result in 'assignment to entry in nil map'
* [#2446](https://github.com/derailed/k9s/issues/2446) Context Switch unreliable/not working
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.3.md
================================================
# Release v0.31.3
## 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)
## Maintenance Release!
The aftermath...
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` doesn't really help us zero in. Everyone has slightly different settings/platforms so every little bits of info helps with the resolves.
Thank you!!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2459](https://github.com/derailed/k9s/issues/2459) No permission to see deployments/statefulsets even though I have them
* [#2458](https://github.com/derailed/k9s/issues/2458) panic on run without current context
* [#2454](https://github.com/derailed/k9s/issues/2454) Invoking K9s ends in panic question
* [#2435](https://github.com/derailed/k9s/issues/2435) "yaml: line 15: could not find expected ':'" error bug question (May be??)
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.4.md
================================================
# Release v0.31.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
## Maintenance Release!
More aftermath...
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really help us zero in.
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves.
Thank you!!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2463](https://github.com/derailed/k9s/issues/2463) v0.31.3 (Linux_amd64) gives runtime error on startup
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.5.md
================================================
# Release v0.31.5
## 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)
## Maintenance Release!
😱 More aftermath... 😱
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;(
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant.
Thank you!!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2466](https://github.com/derailed/k9s/issues/2466) Panic: index out of range [0] with length 0
* [#2465](https://github.com/derailed/k9s/issues/2465) v0.31.4 - panic; no client connection detected - with feelings!!
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.6.md
================================================
# Release v0.31.6
## 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)
## Maintenance Release!
😱 More aftermath... 😱
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;(
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant.
---
## NOTE
In this drop, we've made k9s a bit more resilient (hopefully!) to configuration issues and in most cases k9s will come up but may exhibit `limp mode` behaviors.
Please double check your k9s logs if things don't work as expected and file an issue with the `gory` details!
☢️ This drop may cause `some disturbance in the farce!` ☢️
Please proceed with caution with this one as we did our best to attempt to address potential context config file corruption by eliminating race conditions.
It's late and I am operating on minimal sleep so I may have hosed some behaviors 🫣
If you experience k9s locking up or misbehaving, as per the above👆 you know what to do now and as customary
we will do our best to address them quickly to get you back up and running!
Thank you for your support, kindness and patience!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2476](https://github.com/derailed/k9s/issues/2476) Pods are not displayed for the selected namespace. Hopefully!
* [#2471](https://github.com/derailed/k9s/issues/2471) Shell autocomplete functions do not work correctly
---
## 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!!
* [#2480](https://github.com/derailed/k9s/pull/2480) Adding system arch to nodes view
* [#2477](https://github.com/derailed/k9s/pull/2477) Shell autocomplete for k8s flags
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.7.md
================================================
# Release v0.31.7
## 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)
## Maintenance Release!
😱 More aftermath... 😱
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;(
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant.
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2488](https://github.com/derailed/k9s/issues/2488) linux_amd64 "--kubeconfig" not working on v0.31.6
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.8.md
================================================
# Release v0.31.8
## 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)
## Maintenance Release!
Thank you all for pitching in and helping flesh out issues!!
Please make sure to add gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;(
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant.
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## ♫ Sounds Behind The Release ♭
Going back to the classics...
* [Ambulance Blues - Neil Young](https://www.youtube.com/watch?v=bCQisTEdBwY)
* [Christopher Columbus - Burning Spear](https://www.youtube.com/watch?v=5qbMKTY_Cr0)
* [Feelin' the Same - Clinton Fearon](https://www.youtube.com/watch?v=aRPF2Yta_cs)
---
## 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!!
* [Andreas Frangopoulos](https://github.com/qubeio)
* [Tu Hoang](https://github.com/rebyn)
* [Shoshin Nikita](https://github.com/ShoshinNikita)
* [Dima Altukhov](https://github.com/alt-dima)
* [wpbeckwith](https://github.com/wpbeckwith)
* [a-thomas-22](https://github.com/a-thomas-22)
* [kmath313](https://github.com/kmath313)
* [Jörgen](https://github.com/wthrbtn)
* [Eckl, Máté](https://github.com/ecklm)
* [Jacky Nguyen](https://github.com/nktpro)
* [Chris Bradley](https://github.com/chrisbradleydev)
* [Vytautas Kubilius](https://github.com/vytautaskubilius)
* [Patrick Christensen](https://github.com/BuriedStPatrick)
* [Ollie Lowson](https://github.com/ollielowson-wcbs)
* [Mike Macaulay](https://github.com/mmacaula)
* [David Birks](https://github.com/dbirks)
* [James Hounshell](https://github.com/jameshounshell)
* [elapse2039](https://github.com/elapse2039)
* [Vinicius Xavier](https://github.com/vinixaavier)
* [Phuc Phung](https://github.com/Foxhound401)
* [ollielowson](https://github.com/ollielowson)
> Sponsorship cancellations since the last release: **4!** 🥹
---
## Resolved Issues
* [#2527](https://github.com/derailed/k9s/issues/2527) Multiple k9s panels open in parallel for the same cluster breaks config.yaml
* [#2520](https://github.com/derailed/k9s/issues/2520) pods with init container with restartPolicy: Always stay in Init status
* [#2501](https://github.com/derailed/k9s/issues/2501) Cannot add plugins to helm scope bug
* [#2492](https://github.com/derailed/k9s/issues/2492) API Resources "carry over" between contexts, causing errors if they share shortnames
* [#1158](https://github.com/derailed/k9s/issues/1158) Removing a helm release incorrectly determines the namespace of resources
* [#1033](https://github.com/derailed/k9s/issues/1033) Helm delete deletes only the helm entry but not the deployment
---
## 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!!
* [#2509](https://github.com/derailed/k9s/pull/2509) Fix Toggle Faults filtering
* [#2511](https://github.com/derailed/k9s/pull/2511) adding the f command to pf extender view
* [#2518](https://github.com/derailed/k9s/pull/2518) Added defaultsToFullScreen flag for Live/Details view,logs
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.31.9.md
================================================
# Release v0.31.9
## 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)
## Maintenance Release!
```text
S .-'-.
o __| F `\
S `-,-`--._ `\
[] .->' X `|-'
`=/ (__/_ /
\_, ` _)
`----; |
```
⛔️ WE HAVE A PIPER DOWN! I REPEAT PIPER IS DOWN!! ⛔️
Popeye is undergoing heavy surgery at the moment so I had to break the bridge.
If you dig Popeye please run the binary separately for the time being.
I'll post another message here once the spinach formula upgrade is successful!
Also please make sure to add the gory details to issues ie relevant configs, debug logs, etc...
Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;(
Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant.
Thank you all for pitching in and helping flesh out issues!!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## ♫ Sounds Behind The Release ♭
Ushered or Taylored out?
* [Rough God Goes Riding - Van Morrison](https://www.youtube.com/watch?v=-kGrwRlJxcM)
* [Walk On - John Hiatt](https://www.youtube.com/watch?v=YVdMyeTQCkw)
* [On The Beach - Neil Young](https://www.youtube.com/watch?v=KBVde75e4sU)
---
## 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!!
* [Francis Lalonde](https://github.com/f-lalonde)
* [e-conomic a/s](https://github.com/e-conomic)
> Sponsorship cancellations since the last release: **2!** 🥹
---
## Resolved Issues
* [#2540](https://github.com/derailed/k9s/issues/2540) Option --write not functional
* [#2538](https://github.com/derailed/k9s/issues/2538) Opening screen dumps (sd) in K9s results in Failed to launch editor error message
* [#2536](https://github.com/derailed/k9s/issues/2536) Recent namespaces are lost when changing context
* [#2535](https://github.com/derailed/k9s/issues/2535) Namespaced configmap edit fails for user with RoleBinding to a role that allows it
* [#2532](https://github.com/derailed/k9s/issues/2532) Sporadic crashes (Maybe??)
---
## 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!!
* [#2541](https://github.com/derailed/k9s/pull/2541) Add Rose Pine moon and dawn variants to skins
* [#2531](https://github.com/derailed/k9s/pull/2531) fix the --write flag
* [#2516](https://github.com/derailed/k9s/pull/2516) Added defaultsToFullScreen flag for Live/Details view,logs
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.0.md
================================================
# Release v0.32.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)
## Maintenance Release!
A lot of refactors, perf improvements (crossing fingers+toes!) and general spring cleaning items in this release.
Thus I expect a bit of `disturbance in the farce` given the major code churns, so please beware!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## 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!!
* [Justin Reid](https://github.com/jmreid)
* [Danni](https://github.com/danninov)
* [Robert Krahn](https://github.com/rksm)
* [Hao Ke](https://github.com/kehao95)
* [PH](https://github.com/raphael-com-ph)
> Sponsorship cancellations since the last release: **9!!** 🥹
---
## Resolved Issues
* [#2569](https://github.com/derailed/k9s/issues/2569) k9s panics on start if the main config file (config.yml) is owned by root
* [#2568](https://github.com/derailed/k9s/issues/2568) kube context in running k9s is no longer sticky, during kubectx context switch
* [#2560](https://github.com/derailed/k9s/issues/2560) Namespace/Settings keeps resetting
* [#2557](https://github.com/derailed/k9s/issues/2557) [Feature]: Sort CRDs by their group
* [#1462](https://github.com/derailed/k9s/issues/1462) k9s running very slowly when opening namespace with 13k pods (maybe??)
---
## 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!!
* [#2564](https://github.com/derailed/k9s/pull/2564) Add everforest skins
* [#2558](https://github.com/derailed/k9s/pull/2558) feat: sort by role in node list view
* [#2554](https://github.com/derailed/k9s/pull/2554) Added context to the debug command for debug-container plugin
* [#2554](https://github.com/derailed/k9s/pull/2554) Correctly respect the KUBECACHEDIR env var
* [#2546](https://github.com/derailed/k9s/pull/2546) Use configured log fgColor to print log markers
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.1.md
================================================
# Release v0.32.1
## 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)
## Maintenance Release!
The aftermath ;(
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption
* [#2579](https://github.com/derailed/k9s/issues/2579) Default sorting behavior changed to descending sort bug
---
## 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!!
* [#2586](https://github.com/derailed/k9s/pull/2586) Properly initialize key actions in picker
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.2.md
================================================
# Release v0.32.2
## 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)
## Maintenance Release!
Mo aftermath ;(
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2582](https://github.com/derailed/k9s/issues/2582) Slowness due to client-side throttling in v0.32.0 (Maybe??)
* [#2593](https://github.com/derailed/k9s/issues/2593) Popeye not working in 0.32.X
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.3.md
================================================
# Release v0.32.3
## 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)
## Maintenance Release!
Look like v0.32.2 drop release bins are toast. So m'o aftermath ;(
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption (with feelings!)
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.4.md
================================================
# Release v0.32.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
## Maintenance Release!
---
## ♫ Sounds Behind The Release ♭
Thinking of all you at KubeCon Paris!!
May I suggest a nice glass of `cold Merlote` or other fine grape juices from my country?
* [Le Gorille - George Brassens](https://www.youtube.com/watch?v=KVfwvk_yVyA)
* [Les Funerailles D'antan (Love this guy!) - George Brassens](https://www.youtube.com/watch?v=bwb5k4k2EMc)
* [Poinconneur Des Lilas - Serge Gainsbourg](https://www.youtube.com/watch?v=eWkWCFzkOvU)
* [Mon Legionaire (Yup! same guy??) - Serge Gainsbourg](https://www.youtube.com/watch?v=gl8gopryqWI)
* [Les Cornichons - Nino Ferrer](https://www.youtube.com/watch?v=N7JSW4NhM8I)
* [Paris s'eveille - Jacques Dutronc](https://www.youtube.com/watch?v=3WcCg6rm3uM)
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2608](https://github.com/derailed/k9s/issues/2608) Make the sanitize feature easier to use
* [#2605](https://github.com/derailed/k9s/issues/2605) Built-in shortcuts being overridden by plugins result in excessive logging
* [#2604](https://github.com/derailed/k9s/issues/2604) Ability to mark a plugin as Dangerous/destructive
* [#2592](https://github.com/derailed/k9s/issues/2592) "list access denied" when switching contexts within k9s since 0.32.0
---
## 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!!
* [#2621](https://github.com/derailed/k9s/pull/2621) Fix snap build
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.5.md
================================================
# Release v0.32.5
## 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)
## Maintenance Release!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2734](https://github.com/derailed/k9s/issues/2734) Incorrect pod containers displayed when using custom resource columns
* [#2733](https://github.com/derailed/k9s/issues/2733) Toggle Wide and Toggle Faults broken for PDB view
* [#2656](https://github.com/derailed/k9s/issues/2656) nil pointer dereference when switching contexts
* [#2617](https://github.com/derailed/k9s/issues/2617) Plugin command execution output
---
## 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!!
* [#2736](https://github.com/derailed/k9s/pull/2736) fix view sorting being reset
* [#2732](https://github.com/derailed/k9s/pull/2732) use policy/v1 instead of policy/v1beta1
* [#2728](https://github.com/derailed/k9s/pull/2728) feat: add pool col to node view
* [#2718](https://github.com/derailed/k9s/pull/2718) fix: jump to namespaceless owner reference
* [#2711](https://github.com/derailed/k9s/pull/2711) Add plugins for argo-rollouts
* [#2700](https://github.com/derailed/k9s/pull/2700) feat: allow jumping to the owner of the resource
* [#2699](https://github.com/derailed/k9s/pull/2699) Added cert-manager and openssl plugins
* [#2711](https://github.com/derailed/k9s/pull/2711) Add plugins for argo-rollouts
* [#2698](https://github.com/derailed/k9s/pull/2698) fix: job color based on failures (#2686)
* [#2685](https://github.com/derailed/k9s/pull/2685) feat: support cluster and cmp view
* [#2678](https://github.com/derailed/k9s/pull/2678) fix: do not hard-code path to kubectl in jq plugin
* [#2676](https://github.com/derailed/k9s/pull/2676) Add kanagawa skin
* [#2666](https://github.com/derailed/k9s/pull/2666) save config when closing k9s with ctrl-c
* [#2644](https://github.com/derailed/k9s/pull/2644) Allow overwriting plugin output with command's stdout
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.6.md
================================================
# Release v0.32.6
## 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)
## Maintenance Release!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2947](https://github.com/derailed/k9s/issues/2947) CTRL+Z causes k9s to crash
* [#2938](https://github.com/derailed/k9s/issues/2938) Critical Vulnerability CVE-2024-41110 in v26.0.1 of docker included in k9s
* [#2929](https://github.com/derailed/k9s/issues/2929) conflicting plugins shortcuts
* [#2896](https://github.com/derailed/k9s/issues/2896) Add a plugin to disable/enable a keda ScaledObject
* [#2811](https://github.com/derailed/k9s/issues/2811) Dockerfile build step fails due to misaligned Go versions (1.21.5 vs 1.22.0)
* [#2767](https://github.com/derailed/k9s/issues/2767) Manually triggered jobs don't get automatically cleaned up
* [#2761](https://github.com/derailed/k9s/issues/2761) Enable "jump to owner" for more kinds
* [#2754](https://github.com/derailed/k9s/issues/2754) Plugins not loaded/shown in UI
* [#2747](https://github.com/derailed/k9s/issues/2747) Combining context and namespace switching only works sporadically (e.g. ":pod foo-ns @ctx-dev")
* [#2746](https://github.com/derailed/k9s/issues/2746) k9s does not display "[::]" string in its logs
* [#2738](https://github.com/derailed/k9s/issues/2738) "Faults" view should show all Terminating pods
---
## 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!!
* [#2937](https://github.com/derailed/k9s/pull/2937) Adding Argo Rollouts plugin version for PowerShell
* [#2935](https://github.com/derailed/k9s/pull/2935) fix: show all terminating pods in Faults view (#2738)
* [#2933](https://github.com/derailed/k9s/pull/2933) chore: broken url in build-status tag in the readme.md
* [#2932](https://github.com/derailed/k9s/pull/2932) fix: add kubeconfig if k9s is launched with --kubeconfig
* [#2930](https://github.com/derailed/k9s/pull/2930) fixed conflicting plugin shortcuts, and added 2 new plugins
* [#2927](https://github.com/derailed/k9s/pull/2927) Fix "Mark Range": reduce maximum namespaces in favorites, fix shadowing of ctrl+space
* [#2926](https://github.com/derailed/k9s/pull/2926) chore(plugins,remove-finalizers): make sure the resources api group is respected
* [#2921](https://github.com/derailed/k9s/pull/2921) feat: Add plugins for kubectl node-shell
* [#2920](https://github.com/derailed/k9s/pull/2920) eat: added StartupProbes status (S) to the PROBES column in the container render
* [#2914](https://github.com/derailed/k9s/pull/2914) Adding eks-node-viewer plugin
* [#2898](https://github.com/derailed/k9s/pull/2898) Add argocd plugin to community plugins
* [#2896](https://github.com/derailed/k9s/pull/2896) feat(2896): Add toggle keda plugin
* [#2890](https://github.com/derailed/k9s/pull/2890) Update README.md
* [#2881](https://github.com/derailed/k9s/pull/2881) Fix Mark-Range command: ensure that NS Favorite doesn't exceed the limit
* [#2861](https://github.com/derailed/k9s/pull/2861) chore: fix function name
* [#2856](https://github.com/derailed/k9s/pull/2856) fix internal/render/hpa.go merge issue
* [#2848](https://github.com/derailed/k9s/pull/2848) Include sidecar containers requests and limits
* [#2844](https://github.com/derailed/k9s/pull/2844) Update README GO Version Required
* [#2830](https://github.com/derailed/k9s/pull/2830) update tview to fix log escaping problem completely
* [#2822](https://github.com/derailed/k9s/pull/2822) Adding HolmesGPT plugin
* [#2821](https://github.com/derailed/k9s/pull/2821) Add a spark-operator plugin
* [#2817](https://github.com/derailed/k9s/pull/2817) Add comment about Escape keybinding
* [#2812](https://github.com/derailed/k9s/pull/2812) fix: align build image Go version with go.mod
* [#2795](https://github.com/derailed/k9s/pull/2795) add new plugin current-ctx-terminal
* [#2791](https://github.com/derailed/k9s/pull/2791) Add leading space to Kubernetes context suggestions
* [#2789](https://github.com/derailed/k9s/pull/2789) Create kubectl-get-in-shell.yaml
* [#2788](https://github.com/derailed/k9s/pull/2788) Update README.md plugin format
* [#2787](https://github.com/derailed/k9s/pull/2787) Update helm-purge.yaml
* [#2786](https://github.com/derailed/k9s/pull/2786) Update README.md with plugin dangerous field
* [#2780](https://github.com/derailed/k9s/pull/2780) install copyright file into correct location
* [#2775](https://github.com/derailed/k9s/pull/2775) fix freebsd build failure
* [#2780](https://github.com/derailed/k9s/pull/2780) install copyright file into correct location
* [#2772](https://github.com/derailed/k9s/pull/2772) proper handle OwnerReference for manually created job
* [#2771](https://github.com/derailed/k9s/pull/2771) feat: add duplik8s plugin
* [#2770](https://github.com/derailed/k9s/pull/2770) feat: allow plugins block in plugin files
* [#2765](https://github.com/derailed/k9s/pull/2765) fix: Shellin -> ShellIn
* [#2763](https://github.com/derailed/k9s/pull/2763) enable "jump to owner" for more kinds
* [#2755](https://github.com/derailed/k9s/pull/2755) Loki plugin
* [#2751](https://github.com/derailed/k9s/pull/2751) container logs should be escaped when printed
* [#2750](https://github.com/derailed/k9s/pull/2750) fix: should switching ctx before ns
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.32.7.md
================================================
# Release v0.32.7
## 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)
## Maintenance Release!
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#2970](https://github.com/derailed/k9s/issues/2970) Ctrl-z on events view causes runtime error in v0.32.6
* [#2969](https://github.com/derailed/k9s/issues/2969) When using impersonation user information and permissions not preserved when switching context
* [#2966](https://github.com/derailed/k9s/issues/2966) Go to the Contexts page and filter, contexts that are matched will be filtered ou
* [#2962](https://github.com/derailed/k9s/issues/2962) Small colour/filtering related bug
* [#2961](https://github.com/derailed/k9s/issues/2961) Drain node with the -disable-eviction
* [#2958](https://github.com/derailed/k9s/issues/2958) Restart count in container view associated with the wrong container
* [#2945](https://github.com/derailed/k9s/issues/2945) Could we add ServiceAccount Column in v1/POD view
---
## 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!!
* [#2968](https://github.com/derailed/k9s/pull/2968) Update go version to 1.23.X in README
* [#2964](https://github.com/derailed/k9s/pull/2964) feat(dao,used-by-cmd): check imagePullSecrets as well
* [#2960](https://github.com/derailed/k9s/pull/2960) Put log levels in order in cmd help
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.0.md
================================================
# Release v0.40.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 ♭
* [Glory Box - Portishead](https://www.youtube.com/watch?v=4qQyUi4zfDs)
* [Hit Me With Your Rhythm Stick - Ian Dury And The BlockHeads](https://www.youtube.com/watch?v=0WGVgfjnLqc)
* [Cupidon s'en fout! - George Brassens](https://www.youtube.com/watch?v=a-RlZLfIeKM)
* [Shipbuilding - Elvis Costello](https://www.youtube.com/watch?v=dVhjRqBM5uw)
* [Low Sun - Hermanos Gutierrez](https://www.youtube.com/watch?v=ubaJbw7hkeQ)
---
## 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!!
* [Panfactum](https://github.com/Panfactum)
* [Bastian Pätzold](https://github.com/bastianpaetzold)
* [Mikita Vazhnik](https://github.com/Vazhnik)
* [Jacob Salway](https://github.com/jacobsalway)
* [Eckard Mühlich](https://github.com/eckardnet)
* [Luke](https://github.com/lukepatrick)
* [tomasbanet](https://github.com/tomasbanet)
* [Robin Opletal](https://github.com/fourstepper)
* [Euroblaze](https://github.com/euroblaze)
* [Jack Daniels](https://github.com/dkr91)
* [decafcode](https://github.com/decafcode)
* [Guillaume Copin](https://github.com/GuillaumeCo)
* [Lokalise](https://github.com/lokalise)
* [Gustavo Bini](https://github.com/gustavobini)
* [JMSwag](https://github.com/JMSwag)
* [Daniel Gospodinow](https://github.com/danielgospodinow)
* [Klaviyo](https://github.com/klaviyo)
* [Paul Farver](https://github.com/PaulFarver)
> Sponsorship cancellations since the last release: **12!** 🥹
## 🎉 Feature Release code name: Colon Blow! 🎈
We are pretty stocked about this drop (hopefully...) as we've fully enabled custom columns support in K9s!
Historically, one could customize the view for a given resource by adding a definition in `views.yaml`.
From there one could change sort order and re-arrange the standard column layout.
Several folks voiced the need to add a column for a given label/annotation or any other fields available on a resource.
To date, this wasn't possible 😳
So... without further ado, let see what we can now do with `Custom Views` ding dang deal!
It all starts with a few new directives available in `views.yaml`
### A Refresher...
Customize a pod view and ensure age, ns and name appear first and sort by age descending.
> NOTE! You no longer need to list out all columns.
> The remaining columns will be automatically filled from the standard columns.
```yaml
# Usual biz...
views:
v1/pods: # specify the gvr you want to customize aka group/version/resource
sortColumn: AGE:desc # set the default ordering to ascending (asc) or descending (desc)
columns: # tell the view which columns to display and in which order
- AGE # ensure age, ns and name are the first 3 cols and backfill the rest
- NAMESPACE
- NAME
- READY|H # => NEW! Do not display the READY column
- NODE|W # => NEW! Show node column only on wide
- IP|WR # => NEW! Pull the ip column and right align it in wide mode only
```
## Colon Blow!
Say your pods comes standard with a label `blee` and you want to show it while in pod view.
```yaml
# Pull labels/annotations
views:
v3/freds:
sortColumn: NAMESPACE:dsc
columns:
- NAMESPACE
- NAME
- BLEE:.metadata.labels.blee # => NEW! Pull values from a label or an annotation using json parser
# expression similar mechanic as kubectl -o custom-columns
- ZORG:.spec.zips[?(@.type == 'zorg')].ip|WR # => NEW! Same deal with a json exp + but align right and show wide only
```
## TLDR...
As you can see the CustomView feature adds a few new semantics on this drop.
You can now use the following shape for columns definition `COL_NAME<:json_parse_expression><|column attributes>`
The `:json_parse_expression` is optional.
The column attributes are as follows:
* `T` -> time column indicator
* `N` -> number column indicator
* `W` -> turns on wide column aka only shows while in wide mode. Defaults to the standard resource definition when present.
* `H` -> Hides the column
* `L` -> Left align (default)
* `R` -> Right align
When certain columns are not present in the custom view, K9s will pull the standard column definition and merge the columns.
This allows user to specify and order which columns they want to see first without having to define every single columns from the default resource representation. If you do not wish to see all these columns you can add them to your custom view definition and either specify `|W` or `|H` to `wide` it or `hide` it.
> 📢 Still work in progress so your mileage may vary!
> This feature will likely need additional TLC.
> Your feedback on this will be much appreciated and we will iterate as usual to ensure it vorks as prescribed... 🙀
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 Colon Blow Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3064](https://github.com/derailed/k9s/issues/3064) Question: brew formula k9s vs derailed/k9s/k9s
* [#3061](https://github.com/derailed/k9s/issues/3061) k9s not opening active namespace or namespace specified via -n
* [#3044](https://github.com/derailed/k9s/issues/3044) CRDs are loaded incorrectly into metadata registry, cause sporadic "Jump Owner" issues
* [#2995](https://github.com/derailed/k9s/issues/2995) Latest image on quay.io contains "failed" kubectl binary
---
## 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!!
* [#3065](https://github.com/derailed/k9s/pull/3065) Fixed trimming of favorite namespaces in Config
* [#3063](https://github.com/derailed/k9s/pull/3063) Updating CVE dependencies
* [#3062](https://github.com/derailed/k9s/pull/3062) feat: use kubectl events for plugin watch-events
* [#3060](https://github.com/derailed/k9s/pull/3060) Rename "delete local data" checkbox description in drain dialog
* [#3046](https://github.com/derailed/k9s/pull/3046) Strict unmarshal for plugin files
* [#3045](https://github.com/derailed/k9s/pull/3045) fix: CRD loading: trim group suffix from CRD name
* [#3043](https://github.com/derailed/k9s/pull/3043) Fix K9S_EDITOR
* [#3041](https://github.com/derailed/k9s/pull/3041) Fix Flux trace plugin command
* [#3038](https://github.com/derailed/k9s/pull/2038) fix check e != nil but return a nil value error err
* [#3026](https://github.com/derailed/k9s/pull/3026) Fix typos
* [#3018](https://github.com/derailed/k9s/pull/3018) fix: coloring of rose-pine for values of log options
* [#3017](https://github.com/derailed/k9s/pull/3017) feat: add helm diff plugin
* [#3009](https://github.com/derailed/k9s/pull/3009) fix(argo-rollouts plugin): resolve improper piping in watch command
* [#2996](https://github.com/derailed/k9s/pull/2996) Bump version of netshoot image in debug-container plugin
* [#2994](https://github.com/derailed/k9s/pull/2994) fix kubectl url and fail build on download errors
* [#2986](https://github.com/derailed/k9s/pull/2986) plugin/trace-dns: Trace DNS requests using Inspektor Gadget
* [#2985](https://github.com/derailed/k9s/pull/2985) feat(plugins/crossplane): change to crossplane cli & add crossplane-watch
* [#2986](https://github.com/derailed/k9s/pull/2986) plugin/trace-dns: Trace DNS requests using Inspektor Gadget
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.1.md
================================================
# Release v0.40.1
## 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)
## Maintenance Release!
😳 Aye! Buzz kill on the 0.40.0 aftermath... 🙀 👻
Likely additional `disturbance in the farce` might be observed.
Thank you all for giving v0.40.0 a rinse and reporting back!! 😍
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3113](https://github.com/derailed/k9s/issues/3113) 0.40.0 can't retain temporary view sort
* [#3111](https://github.com/derailed/k9s/issues/3111) k9s can't describe or print YAML for HPAs in all namespaces view
* [#2966](https://github.com/derailed/k9s/issues/2966) Go to the Contexts page and filter, contexts that are matched will be filtered ou
* [#2962](https://github.com/derailed/k9s/issues/2962) Small colour/filtering related bug
* [#2961](https://github.com/derailed/k9s/issues/2961) Drain node with the -disable-eviction
* [#2958](https://github.com/derailed/k9s/issues/2958) Restart count in container view associated with the wrong container
* [#2945](https://github.com/derailed/k9s/issues/2945) Could we add ServiceAccount Column in v1/POD view
---
## 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!!
* [#3094](https://github.com/derailed/k9s/pull/3094) Log in as root to the node.
* [#3033](https://github.com/derailed/k9s/pull/3033) Skip cache invalidation on failed connection
* [#2965](https://github.com/derailed/k9s/pull/2965) Make menu foreground style configurable through skins
* [#2952](https://github.com/derailed/k9s/pull/2952) A modest attempt to improve the logo aesthetics
* [#2833](https://github.com/derailed/k9s/pull/2833) allow scaling custom resource
* [#2799](https://github.com/derailed/k9s/pull/2799) feat(app): add history navigation with [ and ], most recent command with -
* [#2719](https://github.com/derailed/k9s/pull/2719) fix: stop table header cells from being selectable
* [#2865](https://github.com/derailed/k9s/pull/2865) Feature/DisableAutoscroll
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.10.md
================================================
# Release v0.40.10
## 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)
## Maintenance Release!
Sounds like I did hose plugins after all... With feelings!
* Refactored plugins implementation, hopefully we didn't hose them 😳
* Updated plugins docs
* Apparently when it comes to icons, I've chosen... poorly 🙀
Updated `write` icon 🔓->✍️, hopefully for the better 👀??
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3202](https://github.com/derailed/k9s/issues/3202) 0.40.8 breaks plugins loading
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.11.md
================================================
# Release v0.40.11
## 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)
## Maintenance Release!
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3226](https://github.com/derailed/k9s/issues/3226) Filter view will show mess when filtering some string
* [#3224](https://github.com/derailed/k9s/issues/3224) Respect kubectl.kubernetes.io/default-container annotation
* [#3222](https://github.com/derailed/k9s/issues/3222) Option to Display Resource Names Without API Version Prefix
* [#3210](https://github.com/derailed/k9s/issues/3210) Description line is buggy
---
## Contributed PRs
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [#3237](https://github.com/derailed/k9s/pull/3237) fix: List CRDs which has k8s.io in their names
* [#3223](https://github.com/derailed/k9s/pull/3223) Fixed skin config ref of in_the_navy to in-the-navy
* [#3110](https://github.com/derailed/k9s/pull/3110) feat: add splashless option to suppress splash screen on start
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.2.md
================================================
# Release v0.40.2
## 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)
## Maintenance Release!
😳 Aye! Buzz kill on the 0.40.0 aftermath ;( Hot fix in progress...🙀 👻
Likely additional `disturbance in the farce` might be observed.
Thank you all for giving this drop a rinse and reporting back!! 😍
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3116](https://github.com/derailed/k9s/issues/3116) Cannot list custom CRD's since v0.40.1
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.3.md
================================================
# Release v0.40.3
## 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)
## Maintenance Release!
😳 Aye! Buzz kill on the 0.40.0 aftermath ;( Hot fix in progress...🙀 👻
Likely additional `disturbance in the farce` might be observed.
Thank you all for giving this drop a rinse and reporting back!! 😍
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3116](https://github.com/derailed/k9s/issues/3116) Cannot list custom CRD's since v0.40.1 (with feelings!)
---
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.4.md
================================================
# Release v0.40.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
## Maintenance Release!
😳 Aye! Continued Buzz kill on the 0.40.0 aftermath 🙀 👻
Likely additional `disturbance in the farce` might be observed.
Thank you all for giving this drop a rinse and reporting back!! 😍
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3122](https://github.com/derailed/k9s/issues/3122) Viewing events is no longer sorted by LAST SEEN
* [#3120](https://github.com/derailed/k9s/issues/3120) Custom View Column Mismatch in K9s: Shuffled Values in Pods View
* [#3119](https://github.com/derailed/k9s/issues/3119) Custom Views Fail to Load with % in Column Names
* [#3118](https://github.com/derailed/k9s/issues/3118) selecting an alias, the wrong resources are being shown
---
## 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!!
* [#3123](https://github.com/derailed/k9s/pull/3123) update regex to allow '%' and '/' in column names
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.5.md
================================================
# Release v0.40.5
## 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)
## Maintenance Release!
😳 Aye! Continued Buzz kill on the 0.40.0 aftermath 🙀 👻
Likely additional `disturbance in the farce` might be observed.
Thank you all for giving this drop a rinse and reporting back!! 😍
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3131](https://github.com/derailed/k9s/issues/3131) Singular versions of native Kubernetes resource names no longer work
* [#3119](https://github.com/derailed/k9s/issues/3119) Custom Views Fail to Load with % in Column Names (with feelings!)
---
## 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!!
* [#3123](https://github.com/derailed/k9s/pull/3123) update regex to allow '%' and '/' in column names
© 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.6.md
================================================
# Release v0.40.6
## 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)
## Maintenance Release!
### Breaking change
Moved `portForwardAddress` out of clusterXXX/contextYYY/config.yaml and into the main K9s config file.
This is a global preference based on your setup vs a cluster/context specific attribute.
K9s will nag you in the logs if a specific context config still contains this attribute but should not prevent the configuration load.
### Column Blow Reloaded!
We've added another property to the custom view. You can now also specify namespace specific column definition for a given resource.
For instance, view pods in any namespace using one configuration and view pods in `fred` namespace using an alternate configuration.
```yaml
# views.yaml
views:
# Using this for all pods...
v1/pods:
columns:
- AGE
- NAMESPACE|WR # => 🌚 Specifies the NAMESPACE column to be right aligned and only visible while in wide mode
- ZORG:.metadata.labels.fred\.io\.kubernetes\.blee # => 🌚 extract fred.io.kubernetes.blee label into it's own column
- BLEE:.metadata.annotations.blee|R # => 🌚 extract annotation blee into it's own column and right align it
- NAME
- IP
- NODE
- STATUS
- READY
- MEM/RL|S # => 🌚 Overrides std resource default wide attribute via `S` for `Show`
- '%MEM/R|' # => NOTE! column names with non alpha names need to be quoted as columns must be strings!
# Use this instead for pods in namespace `fred`
v1/pods@fred: # => 🌚 New v0.40.6! Customize columns for a given resource and namespace!
columns:
- AGE
- NAMESPACE|WR
```
Additionally, we've added a new column attribute aka `Show` -> `S`. This allows you to now override the default resource column `wide` attribute when set.
---
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3179](https://github.com/derailed/k9s/issues/3179) Resource name with full api or group displayed (somewhere and sometimes)
* [#3178](https://github.com/derailed/k9s/issues/3178) Cronjobs with the same name in different namespaces appear together
* [#3176](https://github.com/derailed/k9s/issues/3176) Trigger all marked cronjobs
* [#3162](https://github.com/derailed/k9s/issues/3162) Context configs: context directory created under wrong cluster after context switch
* [#3161](https://github.com/derailed/k9s/issues/3161) Force wide-only columns to appear outside of wide view
* [#3147](https://github.com/derailed/k9s/issues/3147) Prompt style is overriden by body
* [#3139](https://github.com/derailed/k9s/issues/3139) CPU/R:L and MEM/R:L columns invalid in views.yaml
* [#3138](https://github.com/derailed/k9s/issues/3138) Subresources are not shown correctly in the RBAC view
---
## 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!!
* [#3182](https://github.com/derailed/k9s/pull/3182) fix: Use the latest version when downloading the Ubuntu deb file
* [#3168](https://github.com/derailed/k9s/pull/3168) fix(history): handle cases where special commands add their command their command to the history
* [#3159](https://github.com/derailed/k9s/pull/3159) Added hard contrast gruvbox skins
* [#3149](https://github.com/derailed/k9s/pull/3149) fix: Pass grv on gotoResource as a String to fix non-default apiGroup list
* [#3149](https://github.com/derailed/k9s/pull/3149) Add externalsecrets plugin
* [#3140](https://github.com/derailed/k9s/pull/3140) fix: Avoid false positive matches in enableRegion (#3093)
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.7.md
================================================
# Release v0.40.7
## 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)
## Maintenance Release!
🙀 Hoy! Hosed custom view loading in v0.40.6...
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
---
## 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!!
* [#3186](https://github.com/derailed/k9s/pull/3186) fix: allow absolute paths for the 'dir' command
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.8.md
================================================
# Release v0.40.8
## 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)
## Maintenance Release!
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3193](https://github.com/derailed/k9s/issues/3193) Feature Request: View aliases with custom columns
* [#3192](https://github.com/derailed/k9s/issues/3192) Allow readonly indicator respect the noIcons configuration
* [#3153](https://github.com/derailed/k9s/issues/3153) Add support for bunyan logging
---
## 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!!
* [#3186](https://github.com/derailed/k9s/pull/3186) fix: allow absolute paths for the 'dir' command
* [#3152](https://github.com/derailed/k9s/pull/3152) Feat: Add plugin support for parsing logs with bunyan cli #3153
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.40.9.md
================================================
# Release v0.40.9
## 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)
## Maintenance Release!
* Refactored plugins implementation, hopefully we didn't hose them 😳
* Updated plugins docs
* Apparently when it comes to icons, I've chosen... poorly 🙀
Updated `write` icon 🔓->✍️, hopefully for the better 👀??
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3202](https://github.com/derailed/k9s/issues/3202) 0.40.8 breaks plugins loading
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.0.md
================================================
# Release v0.50
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
---
## ♫ Sounds Behind The Release ♭
* [Afterimage - Justice](https://www.youtube.com/watch?v=9zBJlLbkfzA)
* [This Is The Day - The The](https://www.youtube.com/watch?v=qBF3YqUzYRc)
## 5-O, 5-0... Spring Cleaning In Effect!
☠️ Careful on this upgrade! 🏴☠️
We've gone thru lots of code revamp/refactor on this drop, so mileage may vary!!
### K9s Slow?
It looks like K9s performance took a dive in the wrong direction circa v0.40.x releases.
Took a big perf/cleanup pass to improve perf and think this release should help a lot (famous last words...)
> NOTE! As my dear granny use to say: `You can't cook a great meal without trashing the kitchen`,
> So likely I have broken a few things in the process. So thread carefully and report back!
### Now with Super Column Blow!
By general demand, juice up custom views! In a feature we like to refer to as `Super Column Blow...`
As of this drop, you can go full `Chuck Norris` and sprinkle some of your JQ_FU with you custom views.
For example...
```yaml
# views.yaml
views:
v1/pods:
sortColumn: NAME:asc
columns:
- AGE
- NAMESPACE
- NAME
- IMG-VERSION:.spec.containers[0].image|split(":")|.[-1]|R # => Grab the main container image name and pull the image version
# => out into the `IMG-VERSION` right aligned column
```
> NOTE: ☢️ This is very much experimental! Not all JQ queries features are supported!
> (See https://github.com/itchyny/gojq for the details!)
## Videos Are In The Can!
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
---
## Resolved Issues
* [#3226](https://github.com/derailed/k9s/issues/3226) Filter view will show mess when filtering some string
* [#3224](https://github.com/derailed/k9s/issues/3224) Respect kubectl.kubernetes.io/default-container annotation
* [#3222](https://github.com/derailed/k9s/issues/3222) Option to Display Resource Names Without API Version Prefix
* [#3210](https://github.com/derailed/k9s/issues/3210) Description line is buggy
---
## Contributed PRs
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [#3237](https://github.com/derailed/k9s/pull/3237) fix: List CRDs which has k8s.io in their names
* [#3223](https://github.com/derailed/k9s/pull/3223) Fixed skin config ref of in_the_navy to in-the-navy
* [#3110](https://github.com/derailed/k9s/pull/3110) feat: add splashless option to suppress splash screen on start
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.1.md
================================================
# Release v0.51
## 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)
## 5-0, 5-0 HotFix!
It looks like we've broken a few things in the clean up process 😳
Apologizes for the `disruption in the farce`. Hopefully happier on v0.50.1...
Crossing fingers and toes!
☠️ Careful on this upgrade! 🏴☠️
We've gone thru lots of code revamp/refactor in the v0.50.0, so mileage may vary...
---
## Resolved Issues
* [#3262](https://github.com/derailed/k9s/issues/3262) Crash when no shellPod is defined in config file
* [#3261](https://github.com/derailed/k9s/issues/3261) aliases with namespace and/or labels produce an error
* [#3258](https://github.com/derailed/k9s/issues/3258) mac silicon 0.50.0 runtime error
* [#3257](https://github.com/derailed/k9s/issues/3257) pods are reported to run on nodes they are not running on
* [#3256](https://github.com/derailed/k9s/issues/3256) Pods view seems broken in 0.50.0
* [#3255](https://github.com/derailed/k9s/issues/3255) Custom view does not work randomly
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.10.md
================================================
# Release v0.50.10
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## 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!!
* [rufusshrestha](https://github.com/rufusshrestha)
* [Ovidijus Balkauskas](https://github.com/Stogas)
* [Konrad Konieczny](https://github.com/Psyhackological)
* [Serit Tromsø](https://github.com/serit)
* [Dennis](https://github.com/dennisTGC)
* [LinPr](https://github.com/LinPr)
* [franzXaver987](https://github.com/franzXaver987)
* [Drew Showalter](https://github.com/one19)
* [Sandylen](https://github.com/Sandylen)
* [Uriah Carpenter](https://github.com/uriahcarpenter)
* [Vector Group](https://github.com/vectorgrp)
* [Stefan Roman](https://github.com/katapultcloud)
* [Phillip](https://github.com/Loki-Afro)
* [Lasse Bang Mikkelsen](https://github.com/lassebm)
> Sponsorship cancellations since the last release: **19!** 🥹
---
## Resolved Issues
* [#3541](https://github.com/derailed/k9s/issues/3541) ServiceAccount RBAC Rules not displayed if RoleBinding subject doesn't specify namespace
* [#3535](https://github.com/derailed/k9s/issues/3535) Current Release process will cause code changes been reverted
* [#3525](https://github.com/derailed/k9s/issues/3525) k9s suspends when launching foreground plugin
* [#3495](https://github.com/derailed/k9s/issues/3495) Regression: filtering no long works with aliases
* [#3478](https://github.com/derailed/k9s/issues/3478) High Disk and CPU usage when imageScans Is enabled in K9s
* [#3470](https://github.com/derailed/k9s/issues/3470) Aliases for pods with unequal (!=) label filters not working
* [#3466](https://github.com/derailed/k9s/issues/3466) Shared GPU (nvidia.com/gpu.shared) is shown as n/a on K9s node view
* [#3455](https://github.com/derailed/k9s/issues/3455) memory command not found
---
## 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!!
* [#3558](https://github.com/derailed/k9s/pull/3558) refactor(duplik8s): consolidate duplicate resource commands and updat…
* [#3555](https://github.com/derailed/k9s/pull/3555) feat: add dup plugin
* [#3543](https://github.com/derailed/k9s/pull/3543) Make "flux trace" more generic
* [#3536](https://github.com/derailed/k9s/pull/3536) Add flux-operator resources to flux plugin
* [#3528](https://github.com/derailed/k9s/pull/3528) feat(plugins): add pvc debug container plugin
* [#3517](https://github.com/derailed/k9s/pull/3517) Feature/refresh rate
* [#3516](https://github.com/derailed/k9s/pull/3516) Fixes flickering/jumping issue in context suggestions caused by inconsistent spacing behavior
* [#3515](https://github.com/derailed/k9s/pull/3515) Fix/suppress init no resources warning
* [#3513](https://github.com/derailed/k9s/pull/3513) fix: Color PV row according to its STATUS column
* [#3513](https://github.com/derailed/k9s/pull/3513) fix: Color PV row according to its STATUS column
* [#3505](https://github.com/derailed/k9s/pull/3505) docs: Add installation method with gah
* [#3503](https://github.com/derailed/k9s/pull/3503) fix(logs): enhance log streaming with retry mechanism and error handling
* [#3489](https://github.com/derailed/k9s/pull/3489) feat: Add context deletion functionality
* [#3487](https://github.com/derailed/k9s/pull/3487) fsupport core group resources in k9s/plugins/watch-events.yaml
* [#3485](https://github.com/derailed/k9s/pull/3485) Add disable-self-subject-access-reviews flag to disable can-i check…
* [#3464](https://github.com/derailed/k9s/pull/3464) fix: get-all command in get all plugin
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.11.md
================================================
# Release v0.50.11
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
Oh dear! Hopefully we're happier on this drop?? Apologizes for the `disturbance in the farce`...
## Resolved Issues
* [#3567](https://github.com/derailed/k9s/issues/3567) Extra slash '/' added when filtering from the command prompt
* [#3566](https://github.com/derailed/k9s/issues/3566) unable to switch context or use k9s after upgrade to 0.50.10
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.12.md
================================================
# Release v0.50.12
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
## Resolved Issues
* [#3570](https://github.com/derailed/k9s/issues/3570) 0.50.11 could not display any resources
* [#3562](https://github.com/derailed/k9s/issues/3562) Can't delete namespace
* [#3547](https://github.com/derailed/k9s/issues/3547) Error message from admission controller
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.13.md
================================================
# Release v0.50.13
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
## Resolved Issues
* [#3587](https://github.com/derailed/k9s/issues/3587) UI doesn't show any updates when restarting a Deployment
* [#3585](https://github.com/derailed/k9s/issues/3585) abbreviation sec for secret not working
* [#3584](https://github.com/derailed/k9s/issues/3584) Show managed fields doesn't show them
* [#3583](https://github.com/derailed/k9s/issues/3583) Cannot open shell to pods without node read access as of 0.50.12
* [#3577](https://github.com/derailed/k9s/issues/3577) Log view is broken as of v0.50.10
* [#3574](https://github.com/derailed/k9s/issues/3574) Aliases for pods with label filters not working
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.14.md
================================================
# Release v0.50.14
## 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 big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.
Thank you!
## Resolved Issues
* [#3608](https://github.com/derailed/k9s/issues/3608) k9s crashes when :namespaces used
* [#3606](https://github.com/derailed/k9s/issues/3606) Xray not working anymore on (possible) v0.50.X
* [#3594](https://github.com/derailed/k9s/issues/3594) Show pod yaml - Boom!! cannot deep copy int
* [#3591](https://github.com/derailed/k9s/issues/3591) Accept suggestion with enter (without having to "tab")
* [#3576](https://github.com/derailed/k9s/issues/3576) Custom alias/view not working anymore since v0.50.10
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.15.md
================================================
# Release v0.50.15
## 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 big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.
Thank you!
## Resolved Issues
* [#3591](https://github.com/derailed/k9s/issues/3591) REVERTED! Accept suggestion with enter (without having to "tab")
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.16.md
================================================
# Release v0.50.16
## 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 big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.
Thank you!
### Warp Speed Scotty!
As of this drop, we are introducing `namespace warp` via shortcut `w`.
This affords to view all resources of that type based on the currently selected resource namespace.
This command is only available on namespaced resources.
For example, if you are in pod view and select pod-xxx in namespace `bozo`, hitting `w` will `warp`
you to view all pods in namespace `bozo`.
## Resolved Issues
* [#3629](https://github.com/derailed/k9s/issues/3629) vulnerability in k9s project
* [#3621](https://github.com/derailed/k9s/issues/3621) Switching to ":Deploy" sends you to deployments from namespace "deploy"
* [#3620](https://github.com/derailed/k9s/issues/3620) Trying to show pod yaml using custom views.yaml crashes k9s
* [#3608](https://github.com/derailed/k9s/issues/3608) k9s crashes when :namespaces used
* [#3601](https://github.com/derailed/k9s/issues/3601) Can't delete namespace
* [#3595](https://github.com/derailed/k9s/issues/3595) Toggle Namespace Filter in Pods View with 'n' Key
* [#3576](https://github.com/derailed/k9s/issues/3576) Custom alias/view not working anymore since v0.50.10
---
## 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!!
* [#3625](https://github.com/derailed/k9s/pull/3625) fix: debug-container plugin when KUBECONFIG has multiple files
* [#3623](https://github.com/derailed/k9s/pull/3623) bugfix: fix panic in BenchmarkPodRender by using NewPod() constructor
* [#3619](https://github.com/derailed/k9s/pull/3619) feat: plugin to list all resources by namespace
* [#3605](https://github.com/derailed/k9s/pull/3605) browser: do not prevent redraw when connection unavailable
* [#3600](https://github.com/derailed/k9s/pull/3600) fix(shell): set linux when OS detection fails
* [#3588](https://github.com/derailed/k9s/pull/3588) fix: do not error out of shellIn if OS detection fails
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.17.md
================================================
# Release v0.50.18
## Notes
🥳🎉 Happy new year fellow k9ers!🎊🍾 Hoping 2026 will bring good health and great success to you and yours...
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 big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
---
## ♫ Sounds Behind The Release ♭
* [A cool new way - Joe Satriani](https://www.youtube.com/watch?v=4apA948yOF0)
* [Song for you - Ray Charles](https://www.youtube.com/watch?v=CzAkTrDiXxg)
* [Kill the pain - SYZGYX](https://www.youtube.com/watch?v=5XuvMhHZorw&list=RD5XuvMhHZorw&start_radio=1)
---
## Maintenance Release!
Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.
Thank you!
## A Word From Our Sponsors...
To all the good folks and orgs below that opted to `pay it forward` and join our sponsorship program, I salute you!!
* [Philomena Yeboah](https://github.com/PhilomenaYeboah1989)
* [Kilian](https://github.com/kaerbr)
* [TVRiddle](https://github.com/TVRiddle)
* [Tom Morelly](https://github.com/FalcoSuessgott)
* [Nikhil Narayen](https://github.com/nnarayen)
* [Andrew Aadland](https://github.com/DaemonDude23)
* [Radek](https://github.com/radvym)
* [Timothée Gerber](https://github.com/TimotheeGerber)
* [Matthias](https://github.com/maetthu)
* [DKB](https://github.com/dkb-bank) ❤️
* [Kraken Tech](https://github.com/kraken-tech)
* [Daniel](https://github.com/sherlock7402)
* [Fred Loucks](https://github.com/fullmetal-fred)
* [Patricia Mascaros](https://github.com/ccong2586)
* [Qube Research & Technologies](https://github.com/qube-rt)
* [Michel Jung](https://github.com/micheljung)
* [Ümüt Özalp](https://github.com/uozalp)
* [Nathan Papapietro](https://github.com/npapapietro)
* [Oleksandr Podze](https://github.com/dasdy)
* [Lee Jones](https://github.com/leejones)
* [tsahlif](https://github.com/tshalif)
* [Jean-Christophe Amiel](https://github.com/jcamiel)
* [Lightspark](https://github.com/lightsparkdev)
* [egs-hub](https://github.com/egs-hub) ❤️
* [Sergey](https://github.com/malsatin)
* [Wynter Inc](https://github.com/copytesting)
* [Jen Norris](https://github.com/tnorris)
* [Joakim-Byg](https://github.com/Joakim-Byg)
* [Oleksandr Podze](https://github.com/dasdy)
* [Lee Jones](https://github.com/leejones)
> Sponsorship cancellations since the last release: **17!** 🥹
## Resolved Issues
* [#3765](https://github.com/derailed/k9s/issues/3765) quay.io docker images not up to date but referenced in README.md
* [#3762](https://github.com/derailed/k9s/issues/3762) Copy multiple selected items
* [#3751](https://github.com/derailed/k9s/issues/3751) Improve visual distinction for cordoned nodes in Node view
* [#3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
* [#3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
* [#3731](https://github.com/derailed/k9s/issues/3731) feat: add neat plugin
* [#3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
* [#3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
---
## 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!!
* [#3763](https://github.com/derailed/k9s/pull/3763) feat: enable copying multiple resource, namespace names to clipboard
* [#3760](https://github.com/derailed/k9s/pull/3760) fix: Editing a single Namespace opens the editor with a list of all Namespaces
* [#3756](https://github.com/derailed/k9s/pull/3756) feat: Add reconcile plugin for Flux instances
* [#3755](https://github.com/derailed/k9s/pull/3755) fix: panic on 'jump to owner' of reflect.Value.Elem on zero Value
* [#3753](https://github.com/derailed/k9s/pull/3553) feat: add plugins for argo workflows
* [#3750](https://github.com/derailed/k9s/pull/3750) fix: Flux trace plugin shortcut conflict by changing to Shift-Q
* [#3749](https://github.com/derailed/k9s/pull/3749) feat: add dark/light theme inversion using Oklch
* [#3739](https://github.com/derailed/k9s/pull/3739) chore: refine LabelsSelector comment to match function behavior
* [#3738](https://github.com/derailed/k9s/pull/3738) feat: add symlink handle for plugin directory
* [#3720](https://github.com/derailed/k9s/pull/3720) fix(internal/render): ensure object is deep copied before realization in Render method
* [#3704](https://github.com/derailed/k9s/pull/3704) Allow k9s to start without a valid Kubernetes context
* [#3699](https://github.com/derailed/k9s/pull/3699) feat(pulse): map hjkl to navigate as help shows
* [#3697](https://github.com/derailed/k9s/pull/3697) Issue 3667 Fix
* [#3696](https://github.com/derailed/k9s/pull/3696) fix for scale option appearing on non-scalable resources
* [#3690](https://github.com/derailed/k9s/pull/3690) feat: add support for scaling HPA targets
* [#3671](https://github.com/derailed/k9s/pull/3671) fix fails to modify or delete namespaces using RBAC
* [#3669](https://github.com/derailed/k9s/pull/3669) feat: logs column lock
* [#3663](https://github.com/derailed/k9s/pull/3663) Map Q to "Back"
* [#3859](https://github.com/derailed/k9s/pull/3859) fix: update busybox image version to 1.37.0 in configuration files
* [#3650](https://github.com/derailed/k9s/pull/3650) Sort all columns
* [#3458](https://github.com/derailed/k9s/pull/3458) Document how to install on Fedora
---
© 2026 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.18.md
================================================
# Release v0.50.18
## Notes
🥳🎉 Happy new year fellow k9ers!🎊🍾 Hoping 2026 will bring good health and great success to you and yours...
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 big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
---
## ♫ Sounds Behind The Release ♭
* [A cool new way - Joe Satriani](https://www.youtube.com/watch?v=4apA948yOF0)
* [Song for you - Ray Charles](https://www.youtube.com/watch?v=CzAkTrDiXxg)
* [Kill the pain - SYZGYX](https://www.youtube.com/watch?v=5XuvMhHZorw&list=RD5XuvMhHZorw&start_radio=1)
---
## Maintenance Release!
Oops! I've missed a PR in the v0.50.17 excitement ;( Dropping v0.50.18 with feelings...
Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.
Thank you!
---
## A Word From Our Sponsors...
To all the good folks and orgs below that opted to `pay it forward` and join our sponsorship program, I salute you!!
* [Philomena Yeboah](https://github.com/PhilomenaYeboah1989)
* [Kilian](https://github.com/kaerbr)
* [TVRiddle](https://github.com/TVRiddle)
* [Tom Morelly](https://github.com/FalcoSuessgott)
* [Nikhil Narayen](https://github.com/nnarayen)
* [Andrew Aadland](https://github.com/DaemonDude23)
* [Radek](https://github.com/radvym)
* [Timothée Gerber](https://github.com/TimotheeGerber)
* [Matthias](https://github.com/maetthu)
* [DKB](https://github.com/dkb-bank) ❤️
* [Kraken Tech](https://github.com/kraken-tech)
* [Daniel](https://github.com/sherlock7402)
* [Fred Loucks](https://github.com/fullmetal-fred)
* [Patricia Mascaros](https://github.com/ccong2586)
* [Qube Research & Technologies](https://github.com/qube-rt)
* [Michel Jung](https://github.com/micheljung)
* [Ümüt Özalp](https://github.com/uozalp)
* [Nathan Papapietro](https://github.com/npapapietro)
* [Oleksandr Podze](https://github.com/dasdy)
* [Lee Jones](https://github.com/leejones)
* [tsahlif](https://github.com/tshalif)
* [Jean-Christophe Amiel](https://github.com/jcamiel)
* [Lightspark](https://github.com/lightsparkdev)
* [egs-hub](https://github.com/egs-hub) ❤️
* [Sergey](https://github.com/malsatin)
* [Wynter Inc](https://github.com/copytesting)
* [Jen Norris](https://github.com/tnorris)
* [Joakim-Byg](https://github.com/Joakim-Byg)
* [Oleksandr Podze](https://github.com/dasdy)
* [Lee Jones](https://github.com/leejones)
> Sponsorship cancellations since the last release: **17!** 🥹
## Resolved Issues
* [#3765](https://github.com/derailed/k9s/issues/3765) quay.io docker images not up to date but referenced in README.md
* [#3762](https://github.com/derailed/k9s/issues/3762) Copy multiple selected items
* [#3751](https://github.com/derailed/k9s/issues/3751) Improve visual distinction for cordoned nodes in Node view
* [#3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
* [#3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
* [#3731](https://github.com/derailed/k9s/issues/3731) feat: add neat plugin
* [#3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
* [#3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
* [#3649](https://github.com/derailed/k9s/issues/3649) Improved Column Sorting
---
## 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!!
* [#3763](https://github.com/derailed/k9s/pull/3763) feat: enable copying multiple resource, namespace names to clipboard
* [#3760](https://github.com/derailed/k9s/pull/3760) fix: Editing a single Namespace opens the editor with a list of all Namespaces
* [#3756](https://github.com/derailed/k9s/pull/3756) feat: Add reconcile plugin for Flux instances
* [#3755](https://github.com/derailed/k9s/pull/3755) fix: panic on 'jump to owner' of reflect.Value.Elem on zero Value
* [#3753](https://github.com/derailed/k9s/pull/3553) feat: add plugins for argo workflows
* [#3750](https://github.com/derailed/k9s/pull/3750) fix: Flux trace plugin shortcut conflict by changing to Shift-Q
* [#3749](https://github.com/derailed/k9s/pull/3749) feat: add dark/light theme inversion using Oklch
* [#3739](https://github.com/derailed/k9s/pull/3739) chore: refine LabelsSelector comment to match function behavior
* [#3738](https://github.com/derailed/k9s/pull/3738) feat: add symlink handle for plugin directory
* [#3720](https://github.com/derailed/k9s/pull/3720) fix(internal/render): ensure object is deep copied before realization in Render method
* [#3704](https://github.com/derailed/k9s/pull/3704) Allow k9s to start without a valid Kubernetes context
* [#3699](https://github.com/derailed/k9s/pull/3699) feat(pulse): map hjkl to navigate as help shows
* [#3697](https://github.com/derailed/k9s/pull/3697) Issue 3667 Fix
* [#3696](https://github.com/derailed/k9s/pull/3696) fix for scale option appearing on non-scalable resources
* [#3690](https://github.com/derailed/k9s/pull/3690) feat: add support for scaling HPA targets
* [#3671](https://github.com/derailed/k9s/pull/3671) fix fails to modify or delete namespaces using RBAC
* [#3669](https://github.com/derailed/k9s/pull/3669) feat: logs column lock
* [#3663](https://github.com/derailed/k9s/pull/3663) Map Q to "Back"
* [#3661](https://github.com/derailed/k9s/pull/3661) refactor: remove unused sorting key bindings from various views
* [#3859](https://github.com/derailed/k9s/pull/3859) fix: update busybox image version to 1.37.0 in configuration files
* [#3650](https://github.com/derailed/k9s/pull/3650) Sort all columns
* [#3458](https://github.com/derailed/k9s/pull/3458) Document how to install on Fedora
---
© 2026 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.2.md
================================================
# Release v0.50.2
## 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)
## 5-0, 5-0 HotFix!
It looks like we've broken a few (more) things in the clean up process 😳
This is what you get for trying to refresh a ~10 year old code base 🙀
Apologizes for the `disruption in the farce`. Hopefully much happier on v0.50.2...
Are we there yet? Crossing fingers AND toes...
☠️ Careful on this upgrade! 🏴☠️
We've gone thru lots of code revamp/refactor in the v0.50.0, so mileage may vary...
---
## Resolved Issues
* [#3267](https://github.com/derailed/k9s/issues/3267) Show some output or message when no resources are found
* [#3266](https://github.com/derailed/k9s/issues/3266) Command alias :dp fails with "no resource meta defined for deployments" error
* [#3264](https://github.com/derailed/k9s/issues/3264) can't execute get(y) or describe(d) in StorageClass view
* [#3260](https://github.com/derailed/k9s/issues/3260) yaml view of pod will crash the app (Boom!! cannot deep copy int. (Maybe??)
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.3.md
================================================
# Release v0.50.3
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
A bit more code spring cleaning/TLC and address a few bugs:
1. [RBAC View] Fix issue bombing out on RBAC cluster roles
2. [Custom Views] Fix issue with parsing `jq` filters and bombing out (Big Thanks to Pierre for flagging it!)
---
## 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!!
* [#3273](https://github.com/derailed/k9s/pull/3273) k9s plugin scopes containers issue
* [#3169](https://github.com/derailed/k9s/pull/3169) feat: pass context and token flags to kubectl exec commands
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.4.md
================================================
# Release v0.50.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3288](https://github.com/derailed/k9s/issues/3288) Resource search doesn't filter by name in custom view
* [#3286](https://github.com/derailed/k9s/issues/3286) K9S doesn't understand matchExpressions selector in Deployment to Pod navigation
* [#3285](https://github.com/derailed/k9s/issues/3285) Rollout Restart method conflicts with GitOps (Flux, ArgoCD)
* [#3283](https://github.com/derailed/k9s/issues/3283) Deployment status showing wrong ready state
* [#3278](https://github.com/derailed/k9s/issues/3278) k9s doesn't honor the --namespace parameter
---
## Contributed PRs
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [#3292](https://github.com/derailed/k9s/pull/3292) fix: respect insecure flag when switch context
* [#3277](https://github.com/derailed/k9s/pull/3277) feat: add hostPathVolume (docker)
* [#3253](https://github.com/derailed/k9s/pull/3253) fix: set default request timeout to 120 seconds
* [#2866](https://github.com/derailed/k9s/pull/2866) Feature/default_view
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.5.md
================================================
# Release v0.50.5
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3328](https://github.com/derailed/k9s/issues/3328) Pod overview shows wrong number of running containers with sidecar init-container
* [#3309](https://github.com/derailed/k9s/issues/3309) [0.50.4] k9s crashes when attempting to load logs
* [#3301](https://github.com/derailed/k9s/issues/3301) Port Forward deleted without UI notification when forwarding to wrong port
* [#3294](https://github.com/derailed/k9s/issues/3294) [0.50.4] k9s crashes when filtering based on labels
* [#3278](https://github.com/derailed/k9s/issues/3278) k9s doesn't honor the --namespace parameter
---
## Contributed PRs
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
* [#3311](https://github.com/derailed/k9s/pull/3311) Fix concurrent read writes
* [#3310](https://github.com/derailed/k9s/pull/3310) fix: use full path of date to avoid conflict
* [#3308](https://github.com/derailed/k9s/pull/3308) Show replicasets from deployment view
* [#3300](https://github.com/derailed/k9s/pull/3300) fix: truncate label selector input to max length
* [#3296](https://github.com/derailed/k9s/pull/3296) fix: update time format in logging to 24-hour format
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.6.md
================================================
# Release v0.50.6
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3334](https://github.com/derailed/k9s/issues/3334) Watcher failed for events.k8s.io/v1/events -- expecting a meta table but got *unstructured.Unstructure
---
## 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!!
* [#3332](https://github.com/derailed/k9s/pull/3332) fix: pre-check for get permissions only on port-forward
* [#3311](https://github.com/derailed/k9s/pull/3311) Fix concurrent read writes
* [#3310](https://github.com/derailed/k9s/pull/3310) fix: use full path of date to avoid conflict
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
================================================
FILE: change_logs/release_v0.50.7.md
================================================
# Release v0.50.7
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3435](https://github.com/derailed/k9s/issues/3435) noExitOnCtrlC
* [#3434](https://github.com/derailed/k9s/issues/3434) Pulses - navigation selection is invisible
* [#3424](https://github.com/derailed/k9s/issues/3424) feat: Add GPUs to nodes view
* [#3422](https://github.com/derailed/k9s/issues/3422) Changing ns should keep current kind
* [#3412](https://github.com/derailed/k9s/issues/3412) "Toggle Decode" for secret has no effect
* [#3406](https://github.com/derailed/k9s/issues/3406) History navigation: new view after going back should truncate forward history
* [#3398](https://github.com/derailed/k9s/issues/3398) Improve the UX of FieldManager field on restart
* [#3383](https://github.com/derailed/k9s/issues/3383) Triggering a CronJob fails as Unauthorized since v0.50
* [#3406](https://github.com/derailed/k9s/issues/3406) History navigation: new view after going back should truncate forward history
---
## 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!!
* [#3433](https://github.com/derailed/k9s/pull/3433) feat(plugins): add kube-metrics plugin
* [#3371](https://github.com/derailed/k9s/pull/3371) Add context to condition in keda-toggle plugin
* [#3347](https://github.com/derailed/k9s/pull/3347) Fix GVR Title option in readme
* [#3346](https://github.com/derailed/k9s/pull/3346) revert: #3322
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.8.md
================================================
# Release v0.50.8
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3453](https://github.com/derailed/k9s/issues/3453) [Feature Request] Add GPU column to pod/container view
* [#3451](https://github.com/derailed/k9s/issues/3451) Weirdness when filtering namespaces
* [#3439](https://github.com/derailed/k9s/issues/3438) Allow KnownGPUVendors customization
---
## 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!!
* [#3437](https://github.com/derailed/k9s/pull/3437) feat: Add GPU usage to pod view
* [#3421](https://github.com/derailed/k9s/pull/3421) Fix #3421 - can't switch namespaces in helm view
* [#3356](https://github.com/derailed/k9s/pull/3356) allow skin to be selected via K9S_SKIN env var
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: change_logs/release_v0.50.9.md
================================================
# Release v0.50.9
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
## Maintenance Release!
---
## Resolved Issues
* [#3459](https://github.com/derailed/k9s/issues/3459) Update the tablewriter dependency + implementation
* [#3458](https://github.com/derailed/k9s/issues/3458) Unable to switch namespaces with 0.50.8
---
## 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!!
* [#3460](https://github.com/derailed/k9s/pull/3460) update to tablewriter v1 apis
---
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
================================================
FILE: cmd/info.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package cmd
import (
"fmt"
"log/slog"
"os"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
func infoCmd() *cobra.Command {
return &cobra.Command{
Use: "info",
Short: "List K9s configurations info",
RunE: printInfo,
}
}
func printInfo(*cobra.Command, []string) error {
if err := config.InitLocs(); err != nil {
return err
}
const fmat = "%-27s %s\n"
printLogo(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) {
for _, l := range ui.LogoSmall {
_, _ = fmt.Fprintln(out, color.Colorize(l, c))
}
_, _ = fmt.Fprintln(out)
}
// getScreenDumpDirForInfo get default screen dump config dir or from config.K9sConfigFile configuration.
func getScreenDumpDirForInfo() string {
if config.AppConfigFile == "" {
return config.AppDumpsDir
}
f, err := os.ReadFile(config.AppConfigFile)
if err != nil {
slog.Error("Unable to reads k9s config file", slogs.Error, err)
return config.AppDumpsDir
}
var cfg config.Config
if err := yaml.Unmarshal(f, &cfg); err != nil {
slog.Error("Unable to unmarshal k9s config file", slogs.Error, err)
return config.AppDumpsDir
}
if cfg.K9s == nil {
return config.AppDumpsDir
}
return cfg.K9s.AppScreenDumpDir()
}
================================================
FILE: cmd/info_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package cmd
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func Test_getScreenDumpDirForInfo(t *testing.T) {
tests := map[string]struct {
k9sConfigFile string
expectedScreenDumpDir string
}{
"withK9sConfigFile": {
k9sConfigFile: "testdata/k9s.yaml",
expectedScreenDumpDir: "/tmp",
},
"withEmptyK9sConfigFile": {
k9sConfigFile: "",
expectedScreenDumpDir: config.AppDumpsDir,
},
"withInvalidK9sConfigFilePath": {
k9sConfigFile: "invalid",
expectedScreenDumpDir: config.AppDumpsDir,
},
"withScreenDumpDirEmptyInK9sConfigFile": {
k9sConfigFile: "testdata/k9s1.yaml",
expectedScreenDumpDir: config.AppDumpsDir,
},
}
for k := range tests {
u := tests[k]
t.Run(k, func(t *testing.T) {
initK9sConfigFile := config.AppConfigFile
config.AppConfigFile = u.k9sConfigFile
assert.Equal(t, u.expectedScreenDumpDir, getScreenDumpDirForInfo())
config.AppConfigFile = initK9sConfigFile
})
}
}
================================================
FILE: cmd/root.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package cmd
import (
"errors"
"fmt"
"log/slog"
"os"
"runtime/debug"
"strings"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view"
"github.com/lmittmann/tint"
"github.com/mattn/go-colorable"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd/api"
)
const (
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 _ data.KubeSettings = (*client.Config)(nil)
var (
version, commit, date = "dev", "dev", client.NA
k9sFlags *config.Flags
k8sFlags *genericclioptions.ConfigFlags
rootCmd = &cobra.Command{
Use: appName,
Short: shortAppDesc,
Long: longAppDesc,
RunE: run,
}
out = colorable.NewColorableStdout()
)
type flagError struct{ err error }
func (e flagError) Error() string { return e.err.Error() }
func init() {
if err := config.InitLogLoc(); err != nil {
fmt.Printf("Fail to init k9s logs location %s\n", err)
}
rootCmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error {
return flagError{err: err}
})
rootCmd.AddCommand(versionCmd(), infoCmd())
initK9sFlags()
initK8sFlags()
}
// Execute root command.
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func run(*cobra.Command, []string) error {
if err := config.InitLocs(); err != nil {
return err
}
logFile, err := os.OpenFile(
*k9sFlags.LogFile,
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
data.DefaultFileMod,
)
if err != nil {
return fmt.Errorf("log file %q init failed: %w", *k9sFlags.LogFile, err)
}
defer func() {
if logFile != nil {
_ = logFile.Close()
}
}()
defer func() {
if err := recover(); err != nil {
slog.Error("Boom!! k9s init failed", slogs.Error, err)
slog.Error("", slogs.Stack, string(debug.Stack()))
printLogo(color.Red)
fmt.Printf("%s", color.Colorize("Boom!! ", color.Red))
fmt.Printf("%v.\n", err)
}
}()
slog.SetDefault(slog.New(tint.NewHandler(logFile, &tint.Options{
Level: parseLevel(*k9sFlags.LogLevel),
TimeFormat: time.RFC3339,
})))
cfg, err := loadConfiguration()
if err != nil {
slog.Warn("Fail to load global/context configuration", slogs.Error, err)
}
app := view.NewApp(cfg)
if app.Config.K9s.DefaultView != "" {
app.Config.SetActiveView(app.Config.K9s.DefaultView)
}
if err := app.Init(version, int(*k9sFlags.RefreshRate)); err != nil {
return err
}
if err := app.Run(); err != nil {
return err
}
if view.ExitStatus != "" {
return fmt.Errorf("view exit status %s", view.ExitStatus)
}
return nil
}
func loadConfiguration() (*config.Config, error) {
slog.Info("🐶 K9s starting up...")
k8sCfg := client.NewConfig(k8sFlags)
k9sCfg := config.NewConfig(k8sCfg)
var errs error
conn, err := client.InitConnection(k8sCfg, slog.Default())
if err != nil {
errs = errors.Join(errs, err)
}
k9sCfg.SetConnection(conn)
if err := k9sCfg.Load(config.AppConfigFile, false); err != nil {
errs = errors.Join(errs, err)
}
k9sCfg.K9s.Override(k9sFlags)
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil {
slog.Error("Fail to refine k9s config", slogs.Error, err)
errs = errors.Join(errs, err)
}
// Try to access server version if that fail. Connectivity issue?
if !conn.CheckConnectivity() {
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
}
if !conn.ConnectionOK() {
slog.Warn("💣 Kubernetes connectivity toast!")
errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()))
} else {
slog.Info("✅ Kubernetes connectivity OK")
}
if err := k9sCfg.Save(false); err != nil {
slog.Error("K9s config save failed", slogs.Error, err)
errs = errors.Join(errs, err)
}
return k9sCfg, errs
}
func parseLevel(level string) slog.Level {
switch level {
case "debug":
return slog.LevelDebug
case "warn":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
func initK9sFlags() {
k9sFlags = config.NewFlags()
rootCmd.Flags().Float32VarP(
k9sFlags.RefreshRate,
"refresh", "r",
config.DefaultRefreshRate,
"Specify the default refresh rate as a float (sec)",
)
rootCmd.Flags().StringVarP(
k9sFlags.LogLevel,
"logLevel", "l",
config.DefaultLogLevel,
"Specify a log level (error, warn, info, debug)",
)
rootCmd.Flags().StringVarP(
k9sFlags.LogFile,
"logFile", "",
config.AppLogFile,
"Specify the log file",
)
rootCmd.Flags().BoolVar(
k9sFlags.Headless,
"headless",
false,
"Turn K9s header off",
)
rootCmd.Flags().BoolVar(
k9sFlags.Logoless,
"logoless",
false,
"Turn K9s logo off",
)
rootCmd.Flags().BoolVar(
k9sFlags.Crumbsless,
"crumbsless",
false,
"Turn K9s crumbs off",
)
rootCmd.Flags().BoolVar(
k9sFlags.Splashless,
"splashless",
false,
"Turn K9s splash screen off",
)
rootCmd.Flags().BoolVar(
k9sFlags.Invert,
"invert",
false,
"Invert skin (dark to light, light to dark), preserving colors",
)
rootCmd.Flags().BoolVarP(
k9sFlags.AllNamespaces,
"all-namespaces", "A",
false,
"Launch K9s in all namespaces",
)
rootCmd.Flags().StringVarP(
k9sFlags.Command,
"command", "c",
config.DefaultCommand,
"Overrides the default resource to load when the application launches",
)
rootCmd.Flags().BoolVar(
k9sFlags.ReadOnly,
"readonly",
false,
"Sets readOnly mode by overriding readOnly configuration setting",
)
rootCmd.Flags().BoolVar(
k9sFlags.Write,
"write",
false,
"Sets write mode by overriding the readOnly configuration setting",
)
rootCmd.Flags().StringVar(
k9sFlags.ScreenDumpDir,
"screen-dump-dir",
"",
"Sets a path to a dir for a screen dumps",
)
rootCmd.Flags()
}
func initK8sFlags() {
k8sFlags = genericclioptions.NewConfigFlags(client.UsePersistentConfig)
rootCmd.Flags().StringVar(
k8sFlags.KubeConfig,
"kubeconfig",
"",
"Path to the kubeconfig file to use for CLI requests",
)
rootCmd.Flags().StringVar(
k8sFlags.Timeout,
"request-timeout",
"",
"The length of time to wait before giving up on a single server request",
)
rootCmd.Flags().StringVar(
k8sFlags.Context,
"context",
"",
"The name of the kubeconfig context to use",
)
rootCmd.Flags().StringVar(
k8sFlags.ClusterName,
"cluster",
"",
"The name of the kubeconfig cluster to use",
)
rootCmd.Flags().StringVar(
k8sFlags.AuthInfoName,
"user",
"",
"The name of the kubeconfig user to use",
)
rootCmd.Flags().StringVarP(
k8sFlags.Namespace,
"namespace",
"n",
"",
"If present, the namespace scope for this CLI request",
)
initAsFlags()
initCertFlags()
initK8sFlagCompletion()
}
func initAsFlags() {
rootCmd.Flags().StringVar(
k8sFlags.Impersonate,
"as",
"",
"Username to impersonate for the operation",
)
rootCmd.Flags().StringArrayVar(
k8sFlags.ImpersonateGroup,
"as-group",
[]string{},
"Group to impersonate for the operation",
)
}
func initCertFlags() {
rootCmd.Flags().BoolVar(
k8sFlags.Insecure,
"insecure-skip-tls-verify",
false,
"If true, the server's caCertFile will not be checked for validity",
)
rootCmd.Flags().StringVar(
k8sFlags.CAFile,
"certificate-authority",
"",
"Path to a cert file for the certificate authority",
)
rootCmd.Flags().StringVar(
k8sFlags.KeyFile,
"client-key",
"",
"Path to a client key file for TLS",
)
rootCmd.Flags().StringVar(
k8sFlags.CertFile,
"client-certificate",
"",
"Path to a client certificate file for TLS",
)
rootCmd.Flags().StringVar(
k8sFlags.BearerToken,
"token",
"",
"Bearer token for authentication to the API server",
)
}
type (
k8sPickerFn[T any] func(cfg *api.Config) map[string]T
completeFn func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
)
func initK8sFlagCompletion() {
_ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.Context {
return cfg.Contexts
}))
_ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.Cluster {
return cfg.Clusters
}))
_ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.AuthInfo {
return cfg.AuthInfos
}))
_ = rootCmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, s string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags)
if c, err := client.InitConnection(conn, slog.Default()); err == nil {
if nss, err := c.ValidNamespaceNames(); err == nil {
return filterFlagCompletions(nss, s)
}
}
return nil, cobra.ShellCompDirectiveError
})
}
func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn {
return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags)
cfg, err := conn.RawConfig()
if err != nil {
slog.Error("K8s raw config getter failed", slogs.Error, err)
}
return filterFlagCompletions(picker(&cfg), toComplete)
}
}
func filterFlagCompletions[T any](m map[string]T, s string) ([]string, cobra.ShellCompDirective) {
cc := make([]string, 0, len(m))
for name := range m {
if strings.HasPrefix(name, s) {
cc = append(cc, name)
}
}
return cc, cobra.ShellCompDirectiveNoFileComp
}
================================================
FILE: cmd/testdata/k9s.yaml
================================================
k9s:
refreshRate: 2
readOnly: false
logger:
tail: 200
buffer: 2000
currentContext: minikube
currentCluster: minikube
clusters:
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po
screenDumpDir: /tmp
================================================
FILE: cmd/testdata/k9s1.yaml
================================================
k9s:
refreshRate: 10
namespace:
active: fred
favorites:
- blee
- duh
- crap
================================================
FILE: cmd/version.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package cmd
import (
"fmt"
"github.com/derailed/k9s/internal/color"
"github.com/spf13/cobra"
)
func versionCmd() *cobra.Command {
var short bool
command := cobra.Command{
Use: "version",
Short: "Print version/build info",
Long: "Print version/build information",
Run: func(*cobra.Command, []string) {
printVersion(short)
},
}
command.PersistentFlags().BoolVarP(&short, "short", "s", false, "Prints K9s version info in short format")
return &command
}
func printVersion(short bool) {
const fmat = "%-20s %s\n"
var outputColor color.Paint
if short {
outputColor = -1
} else {
outputColor = color.Cyan
printLogo(outputColor)
}
printTuple(fmat, "Version", version, outputColor)
printTuple(fmat, "Commit", commit, outputColor)
printTuple(fmat, "Date", date, outputColor)
}
func printTuple(fmat, section, value string, outputColor color.Paint) {
if outputColor != -1 {
_, _ = fmt.Fprintf(out, fmat, color.Colorize(section+":", outputColor), value)
return
}
_, _ = fmt.Fprintf(out, fmat, section, value)
}
================================================
FILE: go.mod
================================================
module github.com/derailed/k9s
go 1.25.1
require (
github.com/adrg/xdg v0.5.3
github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084
github.com/anchore/grype v0.109.0
github.com/anchore/syft v1.42.1
github.com/atotto/clipboard v0.1.4
github.com/cenkalti/backoff/v4 v4.3.0
github.com/derailed/tcell/v2 v2.3.1-rc.4
github.com/derailed/tview v0.8.5
github.com/fatih/color v1.18.0
github.com/fsnotify/fsnotify v1.9.0
github.com/fvbommel/sortorder v1.1.0
github.com/go-errors/errors v1.5.1
github.com/itchyny/gojq v0.12.18
github.com/karrick/godirwalk v1.17.0
github.com/lmittmann/tint v1.1.3
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/mattn/go-colorable v0.1.14
github.com/mattn/go-runewidth v0.0.19
github.com/olekukonko/tablewriter v1.1.3
github.com/petergtz/pegomock v2.9.0+incompatible
github.com/rakyll/hey v0.1.5
github.com/sahilm/fuzzy v0.1.1
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
golang.org/x/text v0.34.0
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.20.0
k8s.io/api v0.35.2
k8s.io/apiextensions-apiserver v0.35.2
k8s.io/apimachinery v0.35.2
k8s.io/cli-runtime v0.35.1
k8s.io/client-go v0.35.2
k8s.io/klog/v2 v2.140.0
k8s.io/kubectl v0.35.0
k8s.io/metrics v0.35.2
sigs.k8s.io/yaml v1.6.0
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.58.0 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.10.0 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
github.com/Intevation/gval v1.3.0 // indirect
github.com/Intevation/jsonpath v0.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/acobaugh/osrelease v0.1.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232 // indirect
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c // indirect
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d // indirect
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 // indirect
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-macholibre v0.0.0-20250320151634-807da7ad2331 // indirect
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec // indirect
github.com/anchore/go-struct-converter v0.1.0 // indirect
github.com/anchore/go-sync v0.0.0-20250714163430-add63db73ad1 // indirect
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect
github.com/anchore/stereoscope v0.1.20 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-pep440-version v0.0.1 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.11.5 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.9.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/cgroups/v3 v3.1.2 // indirect
github.com/containerd/containerd v1.7.30 // indirect
github.com/containerd/containerd/api v1.10.0 // indirect
github.com/containerd/containerd/v2 v2.2.1 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.2 // indirect
github.com/containerd/plugin v1.0.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/creack/pty v1.1.20 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb // indirect
github.com/diskfs/go-diskfs v1.7.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.5.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/phpserialize v1.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/facebookincubator/nvdtools v0.1.5 // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/github/go-spdx/v2 v2.3.6 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/go-git/go-git/v5 v5.16.5 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gocsaf/csaf/v3 v3.5.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gohugoio/hashstructure v0.6.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.7 // indirect
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gookit/color v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gpustack/gguf-parser-go v0.23.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.8.4 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/henvic/httpretty v0.1.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.7 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.38.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/openvex/go-vex v0.2.7 // indirect
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect
github.com/package-url/packageurl-go v0.1.3 // indirect
github.com/pandatix/go-cvss v0.6.2 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.12 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rubenv/sql-migrate v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sassoftware/go-rpmutils v0.4.0 // indirect
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spdx/gordf v0.0.0-20250128162952-000978ccd6fb // indirect
github.com/spdx/tools-golang v0.5.7 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/sylabs/squashfs v1.0.6 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vbatts/go-mtree v0.7.0 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/vifraa/gopom v1.0.0 // indirect
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b // indirect
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.256.0 // indirect
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gorm.io/gorm v1.31.1 // indirect
gotest.tools/v3 v3.4.0 // indirect
k8s.io/apiserver v0.35.2 // indirect
k8s.io/component-base v0.35.2 // indirect
k8s.io/component-helpers v0.35.0 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.44.3 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)
================================================
FILE: go.sum
================================================
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CycloneDX/cyclonedx-go v0.10.0 h1:7xyklU7YD+CUyGzSFIARG18NYLsKVn4QFg04qSsu+7Y=
github.com/CycloneDX/cyclonedx-go v0.10.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw=
github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o=
github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A=
github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ=
github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084 h1:7DUAXEdAxoANPlDgxYiaSRKnWnTygvdrrWhnmvEjNLg=
github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084/go.mod h1:42dWox8z4//b898OIELsQnSdYq9q1aCXkwp5fKF+BEU=
github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232 h1:aVC6r9h5wGNh8BYTW3CXxOdPoZzY/bBRWne1NvSTlO8=
github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232/go.mod h1:Zees1AEKNpXIRgdVAMYWITncarLFiPOtEQ7rl45V/h0=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c h1:eoJXyC0n7DZ4YvySG/ETdYkTar2Due7eH+UmLK6FbrA=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50=
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM=
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
github.com/anchore/go-macholibre v0.0.0-20250320151634-807da7ad2331 h1:fWPHXkH3FQGVCyPkFMqNvMjQvdNMfkylBTsDqZC4lE4=
github.com/anchore/go-macholibre v0.0.0-20250320151634-807da7ad2331/go.mod h1:DYvTRnWrlJ//6YOR83SiewmJiNFDEMRaOTnrzgco9FA=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30=
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/anchore/go-sync v0.0.0-20250714163430-add63db73ad1 h1:UK1SWZf2xD5jq8QVeDdpt6wW31cO3RckBvPmGlDrTkg=
github.com/anchore/go-sync v0.0.0-20250714163430-add63db73ad1/go.mod h1:hd0Ol9qFM8tRDdF50a+DpZEoB0HFNaEnCp/BSVyBRlg=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg=
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/grype v0.109.0 h1:CRknQY4Mbpxh57E1O7Q/vSul9ltgzHqeSzza6alErME=
github.com/anchore/grype v0.109.0/go.mod h1:4YbSZ8quer0N1CFJ8LQPFfcHIerXo6ccBTLlhd6IYGY=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
github.com/anchore/stereoscope v0.1.20 h1:32720yZ/YtvzF5tvsoRL/ibdAJzOdIaR444fDXW4arQ=
github.com/anchore/stereoscope v0.1.20/go.mod h1:6Ef0xQAuN2Ito7eV9A9pYjD1x/0cX5fy56MwgEGyrB4=
github.com/anchore/syft v1.42.1 h1:aZvkRXzclT2VrQUfu6tsyiixqusGJk9DeoOJktcQBrU=
github.com/anchore/syft v1.42.1/go.mod h1:uo2xEPi6gyc/qabZFv0Oni6W2pL0gE7sshAyZJCnHNg=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/aquasecurity/go-pep440-version v0.0.1 h1:8VKKQtH2aV61+0hovZS3T//rUF+6GDn18paFTVS0h0M=
github.com/aquasecurity/go-pep440-version v0.0.1/go.mod h1:3naPe+Bp6wi3n4l5iBFCZgS0JG8vY6FT0H4NGhFJ+i4=
github.com/aquasecurity/go-version v0.0.1 h1:4cNl516agK0TCn5F7mmYN+xVs1E3S45LkgZk3cbaW2E=
github.com/aquasecurity/go-version v0.0.1/go.mod h1:s1UU6/v2hctXcOa3OLwfj5d9yoXHa3ahf+ipSwEvGT0=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607 h1:lBg3tHGquFySSblLi9zNi2iGNmVLRHBzVal2fqphCM8=
github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/charmbracelet/bubbles v0.21.1 h1:nj0decPiixaZeL9diI4uzzQTkkz1kYY8+jgzCZXSmW0=
github.com/charmbracelet/bubbles v0.21.1/go.mod h1:HHvIYRCpbkCJw2yo0vNX1O5loCwSr9/mWS8GYSg50Sk=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.5 h1:NBWeBpj/lJPE3Q5l+Lusa4+mH6v7487OP8K0r1IhRg4=
github.com/charmbracelet/x/ansi v0.11.5/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4=
github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/containerd/containerd v1.7.30 h1:/2vezDpLDVGGmkUXmlNPLCCNKHJ5BbC5tJB5JNzQhqE=
github.com/containerd/containerd v1.7.30/go.mod h1:fek494vwJClULlTpExsmOyKCMUAbuVjlFsJQc4/j44M=
github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o=
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
github.com/containerd/containerd/v2 v2.2.1 h1:TpyxcY4AL5A+07dxETevunVS5zxqzuq7ZqJXknM11yk=
github.com/containerd/containerd/v2 v2.2.1/go.mod h1:NR70yW1iDxe84F2iFWbR9xfAN0N2F0NcjTi1OVth4nU=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=
github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4=
github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
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-20240306090643-c67ab88f10cb h1:4W/2rQ3wzEimF5s+J6OY3ODiQtJZ5W1sForSgogVXkY=
github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/derailed/tcell/v2 v2.3.1-rc.4 h1:hrBFOQjcmt1I86Cvcq/NKP7sdRHH+6ibXWBFl0Hn3jY=
github.com/derailed/tcell/v2 v2.3.1-rc.4/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY=
github.com/derailed/tview v0.8.5 h1:pogM/OnWlgDo6j4zyzdiIXh7E7+eT7D4CPfBnyaETug=
github.com/derailed/tview v0.8.5/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM=
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk=
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY=
github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.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=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk=
github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ=
github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/github/go-spdx/v2 v2.3.6 h1:9flm625VmmTlWXi0YH5W9V8FdMfulvxalHdYnUfoqxc=
github.com/github/go-spdx/v2 v2.3.6/go.mod h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-snaps v0.5.19 h1:hUJlCQOpTt1M+kSisMwioDWZDWpDtdAvUhvWCx1YGW0=
github.com/gkampitakis/go-snaps v0.5.19/go.mod h1:gC3YqxQTPyIXvQrw/Vpt3a8VqR1MO8sVpZFWN4DGwNs=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=
github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gocsaf/csaf/v3 v3.5.1 h1:jTA1fLrK0/JIczPs7itTD53qANoO4tn2VaGvUeitePc=
github.com/gocsaf/csaf/v3 v3.5.1/go.mod h1:pga89lE+iWJm7smTdzYcXuetYUbgY8caXfaIP4BJG98=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg=
github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=
github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gpustack/gguf-parser-go v0.23.1 h1:0U7DOrsi7ryx2L/dlMy+BSQ5bJV4AuMEIgGBs4RK46A=
github.com/gpustack/gguf-parser-go v0.23.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A=
github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU=
github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc=
github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg=
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y=
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg=
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8=
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 h1:dWzdsqjh1p2gNtRKqNwuBvKqMNwnLOPLzVZT1n6DK7s=
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23/go.mod h1:lUaIXCWzf7BRKTY5iEcrYy1TfgbYLYVIS/B2vPkJzOc=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a h1:eLvAzVoRfHEOl64OxFhepPf3vj7SKvXY/tFc3BS0b7s=
github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a/go.mod h1:jZ3F25l7DbD7l7DcA8aj7eo1EZ84nbzcQHBB4lCSrI8=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
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/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 h1:kpt9ZfKcm+EDG4s40hMwE//d5SBgDjUOrITReV2u4aA=
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1/go.mod h1:qgCw4bBKZX8qMgGeEZzGFVT3notl42dBjNqO2jut0M0=
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE=
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
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.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/openvex/go-vex v0.2.7 h1:/pN3bqvS4QOc6WkkL0hbKzJuAtsUD9vmvk9IZkzD3Zc=
github.com/openvex/go-vex v0.2.7/go.mod h1:ZyQC3NXl9jjS53JOpBG3LAUXySkW8IlJ/GIhsnf5D54=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 h1:FvA4bwjKpPqik5WsQ8+4z4DKWgA1tO1RTTtNKr5oYNA=
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94=
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs=
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI=
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/petergtz/pegomock v2.9.0+incompatible h1:BKfb5XfkJfehe5T+O1xD4Zm26Sb9dnRj7tHxLYwUPiI=
github.com/petergtz/pegomock v2.9.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=
github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rakyll/hey v0.1.5 h1:oc3QhpT8ETXcr5xIE2xgWYNSNA/Z52XA20ku9hWCchY=
github.com/rakyll/hey v0.1.5/go.mod h1:tLUK++7gal6z92HvVEaP4QfIhEb5cYywEJgrUqmqt7Y=
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8gOLsYwaY2JwlTMT4brS5/9XJdrdIbmk2obvQ748CC0=
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d h1:3VwvTjiRPA7cqtgOWddEL+JrcijMlXUmj99c/6YyZoY=
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spdx/gordf v0.0.0-20250128162952-000978ccd6fb h1:7G2Czq97VORM5xNRrD8tSQdhoXPRs8s+Otlc7st9TS0=
github.com/spdx/gordf v0.0.0-20250128162952-000978ccd6fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sylabs/sif/v2 v2.22.0 h1:Y+xXufp4RdgZe02SR3nWEg7S6q4tPWN237WHYzkDSKA=
github.com/sylabs/sif/v2 v2.22.0/go.mod h1:W1XhWTmG1KcG7j5a3KSYdMcUIFvbs240w/MMVW627hs=
github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI=
github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE=
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/go-mtree v0.7.0 h1:ytmOc3MTRidZiBi9VBCyZ2BHe4fZS47L5v7BVXDWW4E=
github.com/vbatts/go-mtree v0.7.0/go.mod h1:EjdpFC+LZy1TXbRGNa1MKKgjQ+7ew3foMFJK8o4/TdY=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b h1:uWNQ0khA6RdFzODOMwKo9XXu7fuewnnkHykUtuKru8s=
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b/go.mod h1:ewlIKbKV8l+jCj8rkdXIs361ocR5x3qGyoCSca47Gx8=
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8=
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA=
go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
helm.sh/helm/v3 v3.20.0 h1:2M+0qQwnbI1a2CxN7dbmfsWHg/MloeaFMnZCY56as50=
helm.sh/helm/v3 v3.20.0/go.mod h1:rTavWa0lagZOxGfdhu4vgk1OjH2UYCnrDKE2PVC4N0o=
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=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=
k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/apiserver v0.35.2 h1:rb52v0CZGEL0FkhjS+I6jHflAp7fZ4MIaKcEHX7wmDk=
k8s.io/apiserver v0.35.2/go.mod h1:CROJUAu0tfjZLyYgSeBsBan2T7LUJGh0ucWwTCSSk7g=
k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE=
k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw=
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
k8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc=
k8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0=
k8s.io/component-helpers v0.35.0 h1:wcXv7HJRksgVjM4VlXJ1CNFBpyDHruRI99RrBtrJceA=
k8s.io/component-helpers v0.35.0/go.mod h1:ahX0m/LTYmu7fL3W8zYiIwnQ/5gT28Ex4o2pymF63Co=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc=
k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo=
k8s.io/metrics v0.35.2 h1:PJRP88qeadR5evg4ZKJAh3NR3ICchwM51/Aidd0LHjc=
k8s.io/metrics v0.35.2/go.mod h1:w1pJmSu2j8ftVI26MGcJtMnpmZ06oKwb4Enm+xVl06Q=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
================================================
FILE: internal/client/client.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/derailed/k9s/internal/slogs"
authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/cache"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
"k8s.io/metrics/pkg/client/clientset/versioned"
)
const (
cacheSize = 100
cacheExpiry = 5 * time.Minute
cacheMXAPIKey = "metricsAPI"
serverVersion = "serverVersion"
cacheNSKey = "validNamespaces"
)
var supportedMetricsAPIVersions = []string{"v1beta1"}
// NamespaceNames tracks a collection of namespace names.
type NamespaceNames map[string]struct{}
// APIClient represents a Kubernetes api client.
type APIClient struct {
client, logClient kubernetes.Interface
dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
cachedClient *disk.CachedDiscoveryClient
config *Config
mx sync.RWMutex
cache *cache.LRUExpireCache
connOK bool
log *slog.Logger
}
// NewTestAPIClient for testing ONLY!!
func NewTestAPIClient() *APIClient {
return &APIClient{
config: NewConfig(nil),
cache: cache.NewLRUExpireCache(cacheSize),
}
}
// InitConnection initialize connection from command line args.
// Checks for connectivity with the api server.
func InitConnection(config *Config, log *slog.Logger) (*APIClient, error) {
a := APIClient{
config: config,
cache: cache.NewLRUExpireCache(cacheSize),
connOK: true,
log: log.With(slogs.Subsys, "client"),
}
if err := a.supportsMetricsResources(); err != nil {
slog.Warn("Fail to locate metrics-server", slogs.Error, err)
if !errors.Is(err, noMetricServerErr) && !errors.Is(err, metricsUnsupportedErr) {
a.connOK = false
return &a, err
}
}
return &a, nil
}
// ConnectionOK returns connection status.
func (a *APIClient) ConnectionOK() bool {
return a.connOK
}
func makeSAR(ns string, gvr *GVR, name string) *authorizationv1.SelfSubjectAccessReview {
if ns == ClusterScope {
ns = BlankNamespace
}
res := gvr.GVR()
return &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: ns,
Group: res.Group,
Version: res.Version,
Resource: res.Resource,
Subresource: gvr.SubResource(),
Name: name,
},
},
}
}
func makeCacheKey(ns string, gvr *GVR, n string, vv []string) string {
return ns + ":" + gvr.String() + ":" + n + "::" + strings.Join(vv, ",")
}
// ActiveContext returns the current context name.
func (a *APIClient) ActiveContext() string {
c, err := a.config.CurrentContextName()
if err != nil {
slog.Error("unable to located active cluster", slogs.Error, err)
return ""
}
return c
}
// IsActiveNamespace returns true if namespaces matches.
func (a *APIClient) IsActiveNamespace(ns string) bool {
if a.ActiveNamespace() == BlankNamespace {
return true
}
return a.ActiveNamespace() == ns
}
// ActiveNamespace returns the current namespace.
func (a *APIClient) ActiveNamespace() string {
if ns, err := a.CurrentNamespaceName(); err == nil {
return ns
}
return BlankNamespace
}
func (a *APIClient) clearCache() {
for _, k := range a.cache.Keys() {
a.cache.Remove(k)
}
}
// CanI checks if user has access to a certain resource.
func (a *APIClient) CanI(ns string, gvr *GVR, name string, verbs []string) (auth bool, err error) {
if !a.getConnOK() {
return false, errors.New("ACCESS -- No API server connection")
}
if gvr == NsGVR {
// The name of the namespace is required to check permissions in some cases
ns = name
}
if IsClusterWide(ns) {
ns = BlankNamespace
}
if gvr == HmGVR {
// helm stores release data in secrets
gvr = SecGVR
}
key := makeCacheKey(ns, gvr, name, verbs)
if v, ok := a.cache.Get(key); ok {
if auth, ok = v.(bool); ok {
return auth, nil
}
}
clog := a.log.With(slogs.Subsys, "can")
dial, err := a.Dial()
if err != nil {
return false, err
}
client, sar := dial.AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr, name)
ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
defer cancel()
for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v
resp, err := client.Create(ctx, sar, metav1.CreateOptions{})
clog.Debug("[CAN] access",
slogs.GVR, gvr,
slogs.Namespace, ns,
slogs.ResName, name,
slogs.Verb, verbs,
)
if resp != nil {
clog.Debug("[CAN] response",
slogs.AuthStatus, resp.Status.Allowed,
slogs.AuthReason, resp.Status.Reason,
)
}
if err != nil {
clog.Warn("Auth request failed", slogs.Error, err)
a.cache.Add(key, false, cacheExpiry)
return auth, err
}
if !resp.Status.Allowed {
a.cache.Add(key, false, cacheExpiry)
return auth, fmt.Errorf("(%s) access denied for user on resource %q:%s in namespace %q", v, name, gvr, ns)
}
}
auth = true
a.cache.Add(key, true, cacheExpiry)
return
}
// CurrentNamespaceName return namespace name set via either cli arg or cluster config.
func (a *APIClient) CurrentNamespaceName() (string, error) {
return a.config.CurrentNamespaceName()
}
// ServerVersion returns the current server version info.
func (a *APIClient) ServerVersion() (*version.Info, error) {
if v, ok := a.cache.Get(serverVersion); ok {
if vi, ok := v.(*version.Info); ok {
return vi, nil
}
}
dial, err := a.CachedDiscovery()
if err != nil {
return nil, err
}
info, err := dial.ServerVersion()
if err != nil {
return nil, err
}
a.cache.Add(serverVersion, info, cacheExpiry)
return info, nil
}
func (a *APIClient) IsValidNamespace(ns string) bool {
ok, err := a.isValidNamespace(ns)
if err != nil {
slog.Warn("Namespace validation failed",
slogs.Namespace, ns,
slogs.Error, err,
)
}
return ok
}
func (a *APIClient) isValidNamespace(n string) (bool, error) {
if IsClusterWide(n) || n == NotNamespaced {
return true, nil
}
nn, err := a.ValidNamespaceNames()
if err != nil {
return false, err
}
_, ok := nn[n]
return ok, nil
}
// 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(cacheNSKey); ok {
if nss, ok := nn.(NamespaceNames); ok {
return nss, nil
}
}
ok, err := a.CanI(ClusterScope, NsGVR, "", ListAccess)
if !ok || err != nil {
a.cache.Add(cacheNSKey, NamespaceNames{}, cacheExpiry)
return nil, fmt.Errorf("user not authorized to list all namespaces")
}
dial, err := a.Dial()
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
defer cancel()
nn, err := dial.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
nns := make(NamespaceNames, len(nn.Items))
for i := range nn.Items {
nns[nn.Items[i].Name] = struct{}{}
}
a.cache.Add(cacheNSKey, nns, cacheExpiry)
return nns, nil
}
// CheckConnectivity return true if api server is cool or false otherwise.
func (a *APIClient) CheckConnectivity() bool {
defer func() {
if err := recover(); err != nil {
a.setConnOK(false)
}
if !a.getConnOK() {
a.clearCache()
}
}()
cfg, err := a.config.RESTConfig()
if err != nil {
slog.Error("RestConfig load failed", slogs.Error, err)
a.connOK = false
return a.connOK
}
cfg.Timeout = a.config.CallTimeout()
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
slog.Error("Unable to connect to api server", slogs.Error, err)
a.setConnOK(false)
return a.getConnOK()
}
if _, err := client.ServerVersion(); err == nil {
a.setClient(client)
if !a.getConnOK() {
a.reset()
}
} else {
slog.Error("Unable to fetch server version", slogs.Error, err)
a.setConnOK(false)
}
return a.getConnOK()
}
// Config return a kubernetes configuration.
func (a *APIClient) Config() *Config {
return a.config
}
// HasMetrics checks if the cluster supports metrics.
func (a *APIClient) HasMetrics() bool {
return a.supportsMetricsResources() == nil
}
func (a *APIClient) getMxsClient() *versioned.Clientset {
a.mx.RLock()
defer a.mx.RUnlock()
return a.mxsClient
}
func (a *APIClient) setMxsClient(c *versioned.Clientset) {
a.mx.Lock()
defer a.mx.Unlock()
a.mxsClient = c
}
func (a *APIClient) getCachedClient() *disk.CachedDiscoveryClient {
a.mx.RLock()
defer a.mx.RUnlock()
return a.cachedClient
}
func (a *APIClient) setCachedClient(c *disk.CachedDiscoveryClient) {
a.mx.Lock()
defer a.mx.Unlock()
a.cachedClient = c
}
func (a *APIClient) getDClient() dynamic.Interface {
a.mx.RLock()
defer a.mx.RUnlock()
return a.dClient
}
func (a *APIClient) setDClient(c dynamic.Interface) {
a.mx.Lock()
defer a.mx.Unlock()
a.dClient = c
}
func (a *APIClient) getConnOK() bool {
a.mx.RLock()
defer a.mx.RUnlock()
return a.connOK
}
func (a *APIClient) setConnOK(b bool) {
a.mx.Lock()
defer a.mx.Unlock()
a.connOK = b
}
func (a *APIClient) setLogClient(k kubernetes.Interface) {
a.mx.Lock()
defer a.mx.Unlock()
a.logClient = k
}
func (a *APIClient) getLogClient() kubernetes.Interface {
a.mx.RLock()
defer a.mx.RUnlock()
return a.logClient
}
func (a *APIClient) setClient(k kubernetes.Interface) {
a.mx.Lock()
defer a.mx.Unlock()
a.client = k
}
func (a *APIClient) getClient() kubernetes.Interface {
a.mx.RLock()
defer a.mx.RUnlock()
return a.client
}
// DialLogs returns a handle to api server for logs.
func (a *APIClient) DialLogs() (kubernetes.Interface, error) {
if !a.getConnOK() {
return nil, errors.New("dialLogs - no connection to dial")
}
if clt := a.getLogClient(); clt != nil {
return clt, nil
}
cfg, err := a.RestConfig()
if err != nil {
return nil, err
}
cfg.Timeout = 0
c, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
a.setLogClient(c)
return a.getLogClient(), nil
}
// Dial returns a handle to api server or die.
func (a *APIClient) Dial() (kubernetes.Interface, error) {
if !a.getConnOK() {
return nil, errors.New("no connection to dial")
}
if c := a.getClient(); c != nil {
return c, nil
}
cfg, err := a.RestConfig()
if err != nil {
return nil, err
}
c, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
a.setClient(c)
return a.getClient(), nil
}
// RestConfig returns a rest api client.
func (a *APIClient) RestConfig() (*restclient.Config, error) {
return a.config.RESTConfig()
}
// CachedDiscovery returns a cached discovery client.
func (a *APIClient) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
if !a.getConnOK() {
return nil, errors.New("no connection to cached dial")
}
if c := a.getCachedClient(); c != nil {
return c, nil
}
cfg, err := a.RestConfig()
if err != nil {
return nil, err
}
baseCacheDir := os.Getenv("KUBECACHEDIR")
if baseCacheDir == "" {
baseCacheDir = filepath.Join(mustHomeDir(), ".kube", "cache")
}
httpCacheDir := filepath.Join(baseCacheDir, "http")
discCacheDir := filepath.Join(baseCacheDir, "discovery", toHostDir(cfg.Host))
c, err := disk.NewCachedDiscoveryClientForConfig(cfg, discCacheDir, httpCacheDir, cacheExpiry)
if err != nil {
return nil, err
}
a.setCachedClient(c)
return a.getCachedClient(), nil
}
// DynDial returns a handle to a dynamic interface.
func (a *APIClient) DynDial() (dynamic.Interface, error) {
if c := a.getDClient(); c != nil {
return c, nil
}
cfg, err := a.RestConfig()
if err != nil {
return nil, err
}
c, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, err
}
a.setDClient(c)
return a.getDClient(), nil
}
// MXDial returns a handle to the metrics server.
func (a *APIClient) MXDial() (*versioned.Clientset, error) {
if c := a.getMxsClient(); c != nil {
return c, nil
}
cfg, err := a.RestConfig()
if err != nil {
return nil, err
}
c, err := versioned.NewForConfig(cfg)
if err != nil {
return nil, err
}
a.setMxsClient(c)
return a.getMxsClient(), err
}
func (a *APIClient) invalidateCache() error {
dial, err := a.CachedDiscovery()
if err != nil {
return err
}
dial.Invalidate()
return nil
}
// SwitchContext handles kubeconfig context switches.
func (a *APIClient) SwitchContext(name string) error {
slog.Debug("Switching context", slogs.Context, name)
if err := a.config.SwitchContext(name); err != nil {
return err
}
a.reset()
ResetMetrics()
a.config = NewConfig(a.config.flags)
if !a.CheckConnectivity() {
slog.Warn("SwitchContext: connectivity check failed", slogs.Context, name)
}
if _, err := a.DynDial(); err != nil {
slog.Warn("SwitchContext: DynDial pre-warm failed", slogs.Error, err)
}
return a.invalidateCache()
}
func (a *APIClient) reset() {
a.config.reset()
a.cache = cache.NewLRUExpireCache(cacheSize)
a.nsClient = nil
a.setDClient(nil)
a.setMxsClient(nil)
a.setCachedClient(nil)
a.setClient(nil)
a.setLogClient(nil)
a.setConnOK(true)
}
func (a *APIClient) checkCacheBool(key string) (state, ok bool) {
v, found := a.cache.Get(key)
if !found {
return
}
state, ok = v.(bool)
return
}
func (a *APIClient) supportsMetricsResources() error {
supported, ok := a.checkCacheBool(cacheMXAPIKey)
if ok {
if supported {
return nil
}
return noMetricServerErr
}
defer func() {
a.cache.Add(cacheMXAPIKey, supported, cacheExpiry)
}()
dial, err := a.Dial()
if err != nil {
slog.Warn("Unable to dial API client for metrics", slogs.Error, err)
return err
}
apiGroups, err := dial.Discovery().ServerGroups()
if err != nil {
return err
}
for i := range apiGroups.Groups {
if apiGroups.Groups[i].Name != metricsapi.GroupName {
continue
}
if checkMetricsVersion(&(apiGroups.Groups[i])) {
supported = true
return nil
}
}
return metricsUnsupportedErr
}
func checkMetricsVersion(grp *metav1.APIGroup) bool {
for _, v := range grp.Versions {
for _, supportedVersion := range supportedMetricsAPIVersions {
if v.Version == supportedVersion {
return true
}
}
}
return false
}
================================================
FILE: internal/client/client_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
authorizationv1 "k8s.io/api/authorization/v1"
)
func TestMakeSAR(t *testing.T) {
uu := map[string]struct {
ns string
gvr *GVR
sar *authorizationv1.SelfSubjectAccessReview
}{
"all-pods": {
ns: NamespaceAll,
gvr: PodGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: NamespaceAll,
Version: "v1",
Resource: "pods",
},
},
},
},
"ns-pods": {
ns: "fred",
gvr: PodGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: "fred",
Version: "v1",
Resource: "pods",
},
},
},
},
"clusterscope-ns": {
ns: ClusterScope,
gvr: NsGVR,
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Version: "v1",
Resource: "namespaces",
},
},
},
},
"subres-pods": {
ns: "fred",
gvr: NewGVR("v1/pods:logs"),
sar: &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: "fred",
Version: "v1",
Resource: "pods",
Subresource: "logs",
},
},
},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr, ""))
})
}
}
func TestIsValidNamespace(t *testing.T) {
c := NewTestAPIClient()
uu := map[string]struct {
ns string
cache NamespaceNames
ok bool
}{
"all-ns": {
ns: NamespaceAll,
cache: NamespaceNames{
DefaultNamespace: {},
},
ok: true,
},
"blank-ns": {
ns: BlankNamespace,
cache: NamespaceNames{
DefaultNamespace: {},
},
ok: true,
},
"cluster-ns": {
ns: ClusterScope,
cache: NamespaceNames{
DefaultNamespace: {},
},
ok: true,
},
"no-ns": {
ns: NotNamespaced,
cache: NamespaceNames{
DefaultNamespace: {},
},
ok: true,
},
"default-ns": {
ns: DefaultNamespace,
cache: NamespaceNames{
DefaultNamespace: {},
},
ok: true,
},
"valid-ns": {
ns: "fred",
cache: NamespaceNames{
"fred": {},
},
ok: true,
},
"invalid-ns": {
ns: "fred",
cache: NamespaceNames{
DefaultNamespace: {},
},
},
}
expiry := 1 * time.Millisecond
for k := range uu {
u := uu[k]
c.cache.Add("validNamespaces", u.cache, expiry)
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.ok, c.IsValidNamespace(u.ns))
})
}
}
func TestCheckCacheBool(t *testing.T) {
c := NewTestAPIClient()
const key = "fred"
uu := map[string]struct {
key string
val any
found, actual, sleep bool
}{
"setTrue": {
key: key,
val: true,
found: true,
actual: true,
},
"setFalse": {
key: key,
val: false,
found: true,
},
"missing": {
key: "blah",
val: false,
},
"expired": {
key: key,
val: true,
sleep: true,
},
}
expiry := 1 * time.Millisecond
for k := range uu {
u := uu[k]
c.cache.Add(key, u.val, expiry)
if u.sleep {
time.Sleep(expiry)
}
t.Run(k, func(t *testing.T) {
val, ok := c.checkCacheBool(u.key)
assert.Equal(t, u.found, ok)
assert.Equal(t, u.actual, val)
})
}
}
================================================
FILE: internal/client/config.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
const (
// DefaultCallTimeoutDuration is the default api server call timeout duration.
DefaultCallTimeoutDuration time.Duration = 120 * time.Second
// UsePersistentConfig caches client config to avoid reloads.
UsePersistentConfig = true
)
// Config tracks a kubernetes configuration.
type Config struct {
flags *genericclioptions.ConfigFlags
mx sync.RWMutex
proxy func(*http.Request) (*url.URL, error)
}
// NewConfig returns a new k8s config or an error if the flags are invalid.
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
return &Config{
flags: f,
}
}
// CallTimeout returns the call timeout if set or the default if not set.
func (c *Config) CallTimeout() time.Duration {
if !isSet(c.flags.Timeout) {
return DefaultCallTimeoutDuration
}
dur, err := time.ParseDuration(*c.flags.Timeout)
if err != nil {
return DefaultCallTimeoutDuration
}
return dur
}
func (c *Config) RESTConfig() (*restclient.Config, error) {
cfg, err := c.clientConfig().ClientConfig()
if err != nil {
return nil, err
}
if c.proxy != nil {
cfg.Proxy = c.proxy
}
return cfg, nil
}
// Flags returns configuration flags.
func (c *Config) Flags() *genericclioptions.ConfigFlags {
return c.flags
}
func (c *Config) RawConfig() (api.Config, error) {
return c.clientConfig().RawConfig()
}
func (c *Config) clientConfig() clientcmd.ClientConfig {
return c.flags.ToRawKubeConfigLoader()
}
func (*Config) reset() {}
// SwitchContext changes the kubeconfig context to a new cluster.
func (c *Config) SwitchContext(name string) error {
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, flags.ClusterName = &name, &ct.Cluster
flags.Namespace = c.flags.Namespace
flags.Timeout = c.flags.Timeout
flags.KubeConfig = c.flags.KubeConfig
flags.Impersonate = c.flags.Impersonate
flags.ImpersonateGroup = c.flags.ImpersonateGroup
flags.ImpersonateUID = c.flags.ImpersonateUID
flags.Insecure = c.flags.Insecure
flags.BearerToken = c.flags.BearerToken
c.flags = flags
return nil
}
func (c *Config) Clone(ns string) (*genericclioptions.ConfigFlags, error) {
flags := genericclioptions.NewConfigFlags(false)
ct, err := c.CurrentContextName()
if err != nil {
return nil, err
}
cl, err := c.CurrentClusterName()
if err != nil {
return nil, err
}
flags.Context, flags.ClusterName = &ct, &cl
flags.Namespace = &ns
flags.Timeout = c.Flags().Timeout
flags.KubeConfig = c.Flags().KubeConfig
return flags, 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, ok := cfg.Contexts[cfg.CurrentContext]
if !ok {
return "", fmt.Errorf("invalid current context specified: %q", cfg.CurrentContext)
}
if isSet(c.flags.Context) {
ct, ok = cfg.Contexts[*c.flags.Context]
if !ok {
return "", fmt.Errorf("current-cluster - invalid context specified: %q", *c.flags.Context)
}
}
return ct.Cluster, nil
}
// CurrentContextName returns the currently active config context.
func (c *Config) CurrentContextName() (string, error) {
if isSet(c.flags.Context) {
return *c.flags.Context, nil
}
cfg, err := c.RawConfig()
if err != nil {
return "", fmt.Errorf("fail to load rawConfig: %w", err)
}
return cfg.CurrentContext, nil
}
func (c *Config) CurrentContextNamespace() (string, error) {
name, err := c.CurrentContextName()
if err != nil {
return "", err
}
context, err := c.GetContext(name)
if err != nil {
return "", err
}
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 exist.
func (c *Config) GetContext(n string) (*api.Context, error) {
cfg, err := c.RawConfig()
if err != nil {
return nil, err
}
if c, ok := cfg.Contexts[n]; ok {
return c, nil
}
return nil, fmt.Errorf("getcontext - invalid context specified: %q", n)
}
// SetProxy sets the proxy function.
func (c *Config) SetProxy(proxy func(*http.Request) (*url.URL, error)) {
c.proxy = proxy
}
// Contexts fetch all available contexts.
func (c *Config) Contexts() (map[string]*api.Context, error) {
cfg, err := c.RawConfig()
if err != nil {
return nil, err
}
return cfg.Contexts, nil
}
// DelContext remove a given context from the configuration.
func (c *Config) DelContext(n string) error {
cfg, err := c.RawConfig()
if err != nil {
return err
}
delete(cfg.Contexts, n)
acc, err := c.ConfigAccess()
if err != nil {
return err
}
return clientcmd.ModifyConfig(acc, cfg, true)
}
// RenameContext renames a context.
func (c *Config) RenameContext(oldCtx, newCtx string) error {
cfg, err := c.RawConfig()
if err != nil {
return err
}
if _, ok := cfg.Contexts[newCtx]; ok {
return fmt.Errorf("context with name %s already exists", newCtx)
}
cfg.Contexts[newCtx] = cfg.Contexts[oldCtx]
delete(cfg.Contexts, oldCtx)
acc, err := c.ConfigAccess()
if err != nil {
return err
}
if e := clientcmd.ModifyConfig(acc, cfg, true); e != nil {
return e
}
current, err := c.CurrentContextName()
if err != nil {
return err
}
if current == oldCtx {
return c.SwitchContext(newCtx)
}
return nil
}
// ContextNames fetch all available contexts.
func (c *Config) ContextNames() (map[string]struct{}, error) {
cfg, err := c.RawConfig()
if err != nil {
return nil, err
}
cc := make(map[string]struct{}, len(cfg.Contexts))
for n := range cfg.Contexts {
cc[n] = struct{}{}
}
return cc, nil
}
// CurrentGroupNames retrieves the active group names.
func (c *Config) CurrentGroupNames() ([]string, error) {
if areSet(c.flags.ImpersonateGroup) {
return *c.flags.ImpersonateGroup, nil
}
return []string{}, errors.New("unable to locate current group")
}
// ImpersonateGroups retrieves the active groups if set on the CLI.
func (c *Config) ImpersonateGroups() (string, error) {
if areSet(c.flags.ImpersonateGroup) {
return strings.Join(*c.flags.ImpersonateGroup, ","), nil
}
return "", errors.New("no groups set")
}
// ImpersonateUser retrieves the active user name if set on the CLI.
func (c *Config) ImpersonateUser() (string, error) {
if isSet(c.flags.Impersonate) {
return *c.flags.Impersonate, nil
}
return "", errors.New("no user set")
}
// CurrentUserName retrieves the active user name.
func (c *Config) CurrentUserName() (string, error) {
if isSet(c.flags.Impersonate) {
return *c.flags.Impersonate, nil
}
if isSet(c.flags.AuthInfoName) {
return *c.flags.AuthInfoName, nil
}
cfg, err := c.RawConfig()
if err != nil {
return "", err
}
current := cfg.CurrentContext
if isSet(c.flags.Context) {
current = *c.flags.Context
}
if ctx, ok := cfg.Contexts[current]; ok {
return ctx.AuthInfo, nil
}
return "", errors.New("unable to locate current user")
}
// CurrentNamespaceName retrieves the active namespace.
func (c *Config) CurrentNamespaceName() (string, error) {
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 set in context if any??
return c.CurrentContextNamespace()
}
// ConfigAccess return the current kubeconfig api server access configuration.
func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
c.mx.RLock()
defer c.mx.RUnlock()
return c.clientConfig().ConfigAccess(), nil
}
// ----------------------------------------------------------------------------
// Helpers...
func isSet(s *string) bool {
return s != nil && *s != ""
}
func areSet(ss *[]string) bool {
return ss != nil && len(*ss) != 0
}
================================================
FILE: internal/client/config_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client_test
import (
"errors"
"log/slog"
"os"
"testing"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var kubeConfig = "./testdata/config"
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
func TestCallTimeout(t *testing.T) {
uu := map[string]struct {
t string
e time.Duration
}{
"custom": {
t: "1m",
e: 1 * time.Minute,
},
"default": {
e: client.DefaultCallTimeoutDuration,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
flags := genericclioptions.NewConfigFlags(false)
flags.Timeout = &u.t
cfg := client.NewConfig(flags)
assert.Equal(t, u.e, cfg.CallTimeout())
})
}
}
func TestConfigCurrentContext(t *testing.T) {
uu := map[string]struct {
context string
e string
}{
"default": {
e: "fred",
},
"custom": {
context: "blee",
e: "blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
flags := genericclioptions.NewConfigFlags(false)
flags.KubeConfig = &kubeConfig
if u.context != "" {
flags.Context = &u.context
}
cfg := client.NewConfig(flags)
ctx, err := cfg.CurrentContextName()
require.NoError(t, err)
assert.Equal(t, u.e, ctx)
})
}
}
func TestConfigCurrentCluster(t *testing.T) {
name := "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
cluster string
}{
"default": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
},
cluster: "zorg",
},
"custom": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &name,
},
cluster: "blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := client.NewConfig(u.flags)
ct, err := cfg.CurrentClusterName()
require.NoError(t, err)
assert.Equal(t, u.cluster, ct)
})
}
}
func TestConfigCurrentUser(t *testing.T) {
name := "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
user string
}{
"default": {
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig},
user: "fred",
},
"custom": {
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, AuthInfoName: &name},
user: "blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := client.NewConfig(u.flags)
ctx, err := cfg.CurrentUserName()
require.NoError(t, err)
assert.Equal(t, u.user, ctx)
})
}
}
func TestConfigCurrentNamespace(t *testing.T) {
bleeNS, bleeCTX := "blee", "blee"
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
namespace string
}{
"default": {
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig},
namespace: "",
},
"withContext": {
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Context: &bleeCTX},
namespace: "zorg",
},
"withNS": {
flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Namespace: &bleeNS},
namespace: "blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := client.NewConfig(u.flags)
ns, err := cfg.CurrentNamespaceName()
if ns != "" {
require.NoError(t, err)
}
assert.Equal(t, u.namespace, ns)
})
}
}
func TestConfigGetContext(t *testing.T) {
uu := map[string]struct {
cluster string
err error
}{
"default": {
cluster: "blee",
},
"custom": {
cluster: "bozo",
err: errors.New(`getcontext - invalid context specified: "bozo"`),
},
}
flags := &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}
cfg := client.NewConfig(flags)
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
ctx, err := cfg.GetContext(u.cluster)
if err != nil {
assert.Equal(t, u.err, err)
} else {
assert.NotNil(t, ctx)
assert.Equal(t, u.cluster, ctx.Cluster)
}
})
}
}
func TestConfigSwitchContext(t *testing.T) {
cluster := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &cluster,
}
cfg := client.NewConfig(&flags)
err := cfg.SwitchContext("blee")
require.NoError(t, err)
ctx, err := cfg.CurrentContextName()
require.NoError(t, err)
assert.Equal(t, "blee", ctx)
}
func TestConfigAccess(t *testing.T) {
context := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &context,
}
cfg := client.NewConfig(&flags)
acc, err := cfg.ConfigAccess()
require.NoError(t, err)
assert.NotEmpty(t, acc.GetDefaultFilename())
}
func TestConfigContextNames(t *testing.T) {
cluster := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &cluster,
}
cfg := client.NewConfig(&flags)
cc, err := cfg.ContextNames()
require.NoError(t, err)
assert.Len(t, cc, 3)
}
func TestConfigContexts(t *testing.T) {
context := "duh"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
Context: &context,
}
cfg := client.NewConfig(&flags)
cc, err := cfg.Contexts()
require.NoError(t, err)
assert.Len(t, cc, 3)
}
func TestConfigDelContext(t *testing.T) {
require.NoError(t, cp("./testdata/config.2", "./testdata/config.1"))
context, kubeCfg := "duh", "./testdata/config.1"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeCfg,
Context: &context,
}
cfg := client.NewConfig(&flags)
err := cfg.DelContext("fred")
require.NoError(t, err)
cc, err := cfg.ContextNames()
require.NoError(t, err)
assert.Len(t, cc, 1)
_, ok := cc["blee"]
assert.True(t, ok)
}
func TestConfigRestConfig(t *testing.T) {
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
}
cfg := client.NewConfig(&flags)
rc, err := cfg.RESTConfig()
require.NoError(t, err)
assert.Equal(t, "https://localhost:3002", rc.Host)
}
func TestConfigBadConfig(t *testing.T) {
kubeConfig := "./testdata/bork_config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
}
cfg := client.NewConfig(&flags)
_, err := cfg.RESTConfig()
assert.Error(t, err)
}
// Helpers...
func cp(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0600)
}
================================================
FILE: internal/client/errors.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import metricsapi "k8s.io/metrics/pkg/apis/metrics"
// Error represents an error.
type Error string
// Error returns the error text.
func (e Error) Error() string {
return string(e)
}
const (
noMetricServerErr = Error("No metrics-server detected")
metricsUnsupportedErr = Error("No metrics api group " + metricsapi.GroupName + " found on cluster")
)
================================================
FILE: internal/client/gvr.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"fmt"
"log/slog"
"path"
"strings"
"sync"
"github.com/derailed/k9s/internal/slogs"
"github.com/fvbommel/sortorder"
"gopkg.in/yaml.v3"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var NoGVR = new(GVR)
// GVR represents a kubernetes resource schema as a string.
// Format is group/version/resources:subresource.
type GVR struct {
raw, g, v, r, sr string
}
type gvrCache struct {
data map[string]*GVR
sync.RWMutex
}
func (c *gvrCache) add(gvr *GVR) {
if c.get(gvr.String()) == nil {
c.Lock()
c.data[gvr.String()] = gvr
c.Unlock()
}
}
func (c *gvrCache) get(gvrs string) *GVR {
c.RLock()
defer c.RUnlock()
if gvr, ok := c.data[gvrs]; ok {
return gvr
}
return nil
}
var gvrsCache = gvrCache{
data: make(map[string]*GVR),
}
// NewGVR builds a new gvr from a group, version, resource.
func NewGVR(s string) *GVR {
raw := s
tokens := strings.Split(s, ":")
var g, v, r, sr string
if len(tokens) == 2 {
raw, sr = tokens[0], tokens[1]
}
tokens = strings.Split(raw, "/")
switch len(tokens) {
case 3:
g, v, r = tokens[0], tokens[1], tokens[2]
case 2:
v, r = tokens[0], tokens[1]
case 1:
r = tokens[0]
default:
slog.Error("GVR init failed!", slogs.Error, fmt.Errorf("can't parse GVR %q", s))
}
gvr := GVR{raw: s, g: g, v: v, r: r, sr: sr}
if cgvr := gvrsCache.get(gvr.String()); cgvr != nil {
return cgvr
}
gvrsCache.add(&gvr)
return &gvr
}
func (g *GVR) IsAlias() bool {
return !g.IsK8sRes()
}
func (g *GVR) IsK8sRes() bool {
return g != nil && ((!strings.Contains(g.raw, " ") && strings.Contains(g.raw, "/") && !strings.Contains(g.raw, " /")) || reservedGVRs.Has(g))
}
// WithSubResource builds a new gvr with a sub resource.
func (g *GVR) WithSubResource(sub string) *GVR {
return NewGVR(g.String() + ":" + sub)
}
// NewGVRFromMeta builds a gvr from resource metadata.
func NewGVRFromMeta(a *metav1.APIResource) *GVR {
return NewGVR(path.Join(a.Group, a.Version, a.Name))
}
// NewGVRFromCRD builds a gvr from a custom resource definition.
func NewGVRFromCRD(crd *apiext.CustomResourceDefinition) map[*GVR]*apiext.CustomResourceDefinitionVersion {
mm := make(map[*GVR]*apiext.CustomResourceDefinitionVersion, len(crd.Spec.Versions))
for _, v := range crd.Spec.Versions {
if v.Served && !v.Deprecated {
gvr := NewGVRFromMeta(&metav1.APIResource{
Kind: crd.Spec.Names.Kind,
Group: crd.Spec.Group,
Name: crd.Spec.Names.Plural,
Version: v.Name,
})
mm[gvr] = &v
}
}
return mm
}
// FromGVAndR builds a gvr from a group/version and resource.
func FromGVAndR(gv, r string) *GVR {
return NewGVR(path.Join(gv, r))
}
// FQN returns a fully qualified resource name.
func (g *GVR) FQN(n string) string {
return path.Join(g.AsResourceName(), n)
}
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
func (g *GVR) AsResourceName() string {
if g.g == "" {
return g.r
}
return g.r + "." + g.v + "." + g.g
}
// SubResource returns a sub resource if available.
func (g *GVR) SubResource() string {
return g.sr
}
// String returns gvr as string.
func (g *GVR) String() string {
return g.raw
}
// GV returns the group version scheme representation.
func (g *GVR) GV() schema.GroupVersion {
return schema.GroupVersion{
Group: g.g,
Version: g.v,
}
}
// GVK returns a full schema representation.
func (g *GVR) GVK() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: g.G(),
Version: g.V(),
Kind: g.R(),
}
}
// GVR returns a full schema representation.
func (g *GVR) GVR() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: g.G(),
Version: g.V(),
Resource: g.R(),
}
}
// GVSub returns group vervion sub path.
func (g *GVR) GVSub() string {
if g.G() == "" {
return g.V()
}
return g.G() + "/" + g.V()
}
// GR returns a full schema representation.
func (g *GVR) GR() *schema.GroupResource {
return &schema.GroupResource{
Group: g.G(),
Resource: g.R(),
}
}
// V returns the resource version.
func (g *GVR) V() string {
return g.v
}
// RG returns the resource and group.
func (g *GVR) RG() (resource, group string) {
return g.r, g.g
}
// R returns the resource name.
func (g *GVR) R() string {
return g.r
}
// G returns the resource group name.
func (g *GVR) G() string {
return g.g
}
// IsDecodable checks if the k8s resource has a decodable view
func (g *GVR) IsDecodable() bool {
return g == SecGVR
}
var _ = yaml.Marshaler((*GVR)(nil))
var _ = yaml.Unmarshaler((*GVR)(nil))
func (g *GVR) MarshalYAML() (any, error) {
return g.String(), nil
}
func (g *GVR) UnmarshalYAML(n *yaml.Node) error {
*g = *NewGVR(n.Value)
return nil
}
// GVRs represents a collection of gvr.
type GVRs []*GVR
// Len returns the list size.
func (g GVRs) Len() int {
return len(g)
}
// Swap swaps list values.
func (g GVRs) Swap(i, j int) {
g[i], g[j] = g[j], g[i]
}
// Less returns true if i < j.
func (g GVRs) Less(i, j int) bool {
g1, g2 := g[i].G(), g[j].G()
return sortorder.NaturalLess(g1, g2)
}
// Helper...
// Can determines the available actions for a given resource.
func Can(verbs []string, v string) bool {
if verbs == nil {
return true
}
if len(verbs) == 0 {
return false
}
for _, verb := range verbs {
candidates, err := mapVerb(v)
if err != nil {
slog.Error("Access verb mapping failed", slogs.Error, err)
return false
}
for _, c := range candidates {
if verb == c {
return true
}
}
}
return false
}
func mapVerb(v string) ([]string, error) {
switch v {
case "describe":
return []string{"get"}, nil
case "view":
return []string{"get", "list"}, nil
case "delete":
return []string{"delete"}, nil
case "edit":
return []string{"patch", "update"}, nil
default:
return []string{}, fmt.Errorf("no standard verb for %q", v)
}
}
================================================
FILE: internal/client/gvr_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client_test
import (
"path"
"sort"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestGVRSort(t *testing.T) {
gg := client.GVRs{
client.PodGVR,
client.SvcGVR,
client.DpGVR,
}
sort.Sort(gg)
assert.Equal(t, client.GVRs{
client.PodGVR,
client.SvcGVR,
client.DpGVR,
}, gg)
}
func TestGVRCan(t *testing.T) {
uu := map[string]struct {
vv []string
v string
e bool
}{
"describe": {[]string{"get"}, "describe", true},
"view": {[]string{"get", "list", "watch"}, "view", true},
"delete": {[]string{"delete", "list", "watch"}, "delete", true},
"no_delete": {[]string{"get", "list", "watch"}, "delete", false},
"edit": {[]string{"path", "update", "watch"}, "edit", true},
"no_edit": {[]string{"get", "list", "watch"}, "edit", false},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.Can(u.vv, u.v))
})
}
}
func TestGVR(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersionResource
}{
"full": {client.DpGVR.String(), schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
"core": {client.PodGVR.String(), schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
"bork": {client.UsrGVR.String(), schema.GroupVersionResource{Resource: "users"}},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).GVR())
})
}
}
func TestAsGV(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersion
}{
"full": {client.DpGVR.String(), schema.GroupVersion{Group: "apps", Version: "v1"}},
"core": {client.PodGVR.String(), schema.GroupVersion{Version: "v1"}},
"bork": {client.UsrGVR.String(), schema.GroupVersion{}},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).GV())
})
}
}
func TestNewGVR(t *testing.T) {
uu := map[string]struct {
g, v, r string
e string
}{
"full": {"apps", "v1", "deployments", client.DpGVR.String()},
"core": {"", "v1", "pods", client.PodGVR.String()},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(path.Join(u.g, u.v, u.r)).String())
})
}
}
func TestGVRAsResourceName(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {client.DpGVR.String(), "deployments.v1.apps"},
"core": {client.PodGVR.String(), "pods"},
"k9s": {client.UsrGVR.String(), "users"},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).AsResourceName())
})
}
}
func TestToR(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {client.DpGVR.String(), "deployments"},
"core": {client.PodGVR.String(), "pods"},
"k9s": {client.UsrGVR.String(), "users"},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).R())
})
}
}
func TestToG(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {client.DpGVR.String(), "apps"},
"core": {client.PodGVR.String(), ""},
"k9s": {client.UsrGVR.String(), ""},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).G())
})
}
}
func TestToV(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {client.DpGVR.String(), "v1"},
"core": {"v1beta1/pods", "v1beta1"},
"k9s": {client.UsrGVR.String(), ""},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.gvr).V())
})
}
}
func TestToString(t *testing.T) {
uu := map[string]struct {
gvr string
}{
"full": {client.DpGVR.String()},
"core": {"v1beta1/pods"},
"k9s": {client.UsrGVR.String()},
"empty": {""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, client.NewGVR(u.gvr).String())
})
}
}
================================================
FILE: internal/client/gvrs.go
================================================
package client
import "k8s.io/apimachinery/pkg/util/sets"
var (
// Apps...
DpGVR = NewGVR("apps/v1/deployments")
StsGVR = NewGVR("apps/v1/statefulsets")
DsGVR = NewGVR("apps/v1/daemonsets")
RsGVR = NewGVR("apps/v1/replicasets")
RcGVR = NewGVR("apps/v1/replicationcontrollers")
// Core...
SaGVR = NewGVR("v1/serviceaccounts")
PvcGVR = NewGVR("v1/persistentvolumeclaims")
PvGVR = NewGVR("v1/persistentvolumes")
CmGVR = NewGVR("v1/configmaps")
SecGVR = NewGVR("v1/secrets")
EvGVR = NewGVR("events.k8s.io/v1/events")
EpGVR = NewGVR("v1/endpoints")
PodGVR = NewGVR("v1/pods")
NsGVR = NewGVR("v1/namespaces")
NodeGVR = NewGVR("v1/nodes")
SvcGVR = NewGVR("v1/services")
// Discovery...
EpsGVR = NewGVR("discovery.k8s.io/v1/endpointslices")
// Autoscaling...
HpaGVR = NewGVR("autoscaling/v1/horizontalpodautoscalers")
// Batch...
CjGVR = NewGVR("batch/v1/cronjobs")
JobGVR = NewGVR("batch/v1/jobs")
// Misc...
CrdGVR = NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")
PcGVR = NewGVR("scheduling.k8s.io/v1/priorityclasses")
NpGVR = NewGVR("networking.k8s.io/v1/networkpolicies")
ScGVR = NewGVR("storage.k8s.io/v1/storageclasses")
// Policy...
PdbGVR = NewGVR("policy/v1/poddisruptionbudgets")
PspGVR = NewGVR("policy/v1beta1/podsecuritypolicies")
IngGVR = NewGVR("networking.k8s.io/v1/ingresses")
// Metrics...
NmxGVR = NewGVR("metrics.k8s.io/v1beta1/nodes")
PmxGVR = NewGVR("metrics.k8s.io/v1beta1/pods")
// K9s...
CpuGVR = NewGVR("cpu")
MemGVR = NewGVR("memory")
WkGVR = NewGVR("workloads")
CoGVR = NewGVR("containers")
CtGVR = NewGVR("contexts")
RefGVR = NewGVR("references")
PuGVR = NewGVR("pulses")
ScnGVR = NewGVR("scans")
DirGVR = NewGVR("dirs")
PfGVR = NewGVR("portforwards")
SdGVR = NewGVR("screendumps")
BeGVR = NewGVR("benchmarks")
AliGVR = NewGVR("aliases")
XGVR = NewGVR("xrays")
HlpGVR = NewGVR("help")
QGVR = NewGVR("quit")
// Helm...
HmGVR = NewGVR("helm")
HmhGVR = NewGVR("helm-history")
// RBAC...
RbacGVR = NewGVR("rbac")
PolGVR = NewGVR("policy")
UsrGVR = NewGVR("users")
GrpGVR = NewGVR("groups")
CrGVR = NewGVR("rbac.authorization.k8s.io/v1/clusterroles")
CrbGVR = NewGVR("rbac.authorization.k8s.io/v1/clusterrolebindings")
RoGVR = NewGVR("rbac.authorization.k8s.io/v1/roles")
RobGVR = NewGVR("rbac.authorization.k8s.io/v1/rolebindings")
)
var reservedGVRs = sets.New(
CpuGVR,
MemGVR,
WkGVR,
CoGVR,
CtGVR,
RefGVR,
PuGVR,
ScnGVR,
DirGVR,
PfGVR,
SdGVR,
BeGVR,
AliGVR,
XGVR,
HlpGVR,
QGVR,
HmGVR,
HmhGVR,
RbacGVR,
PolGVR,
UsrGVR,
GrpGVR,
)
================================================
FILE: internal/client/helper_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client_test
import (
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestMetaFQN(t *testing.T) {
uu := map[string]struct {
meta metav1.ObjectMeta
e string
}{
"empty": {
e: "-/",
},
"full": {
meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"},
e: "ns1/blee",
},
"no-ns": {
meta: metav1.ObjectMeta{Name: "blee"},
e: "-/blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.MetaFQN(&u.meta))
})
}
}
func TestCoFQN(t *testing.T) {
uu := map[string]struct {
meta metav1.ObjectMeta
co string
e string
}{
"empty": {
e: "-/:",
},
"full": {
meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"},
co: "fred",
e: "ns1/blee:fred",
},
"no-co": {
meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"},
e: "ns1/blee:",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.CoFQN(&u.meta, u.co))
})
}
}
func TestIsClusterScoped(t *testing.T) {
uu := map[string]struct {
ns string
e bool
}{
"empty": {},
"all": {
ns: client.NamespaceAll,
},
"none": {
ns: client.BlankNamespace,
},
"custom": {
ns: "fred",
},
"scoped": {
ns: "-",
e: true,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.IsClusterScoped(u.ns))
})
}
}
func TestIsNamespaced(t *testing.T) {
uu := map[string]struct {
ns string
e bool
}{
"empty": {},
"all": {
ns: client.NamespaceAll,
},
"cluster": {
ns: client.ClusterScope,
},
"none": {
ns: client.BlankNamespace,
},
"custom": {
ns: "fred",
e: true,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.IsNamespaced(u.ns))
})
}
}
func TestIsAllNamespaces(t *testing.T) {
uu := map[string]struct {
ns string
e bool
}{
"empty": {
e: true,
},
"all": {
ns: client.NamespaceAll,
e: true,
},
"none": {
ns: client.BlankNamespace,
e: true,
},
"custom": {
ns: "fred",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.IsAllNamespaces(u.ns))
})
}
}
func TestIsAllNamespace(t *testing.T) {
uu := map[string]struct {
ns string
e bool
}{
"empty": {},
"all": {
ns: client.NamespaceAll,
e: true,
},
"custom": {
ns: "fred",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.IsAllNamespace(u.ns))
})
}
}
func TestCleanseNamespace(t *testing.T) {
uu := map[string]struct {
ns, e string
}{
"empty": {},
"all": {
ns: client.NamespaceAll,
e: client.BlankNamespace,
},
"custom": {
ns: "fred",
e: "fred",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.CleanseNamespace(u.ns))
})
}
}
func TestNamespaced(t *testing.T) {
uu := []struct {
p, ns, n string
}{
{"fred/blee", "fred", "blee"},
{"blee", "", "blee"},
}
for _, u := range uu {
ns, n := client.Namespaced(u.p)
assert.Equal(t, u.ns, ns)
assert.Equal(t, u.n, n)
}
}
func TestFQN(t *testing.T) {
uu := []struct {
ns, n string
e string
}{
{"fred", "blee", "fred/blee"},
{"", "blee", "blee"},
}
for _, u := range uu {
assert.Equal(t, u.e, client.FQN(u.ns, u.n))
}
}
================================================
FILE: internal/client/helpers.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"log/slog"
"os"
"os/user"
"path"
"regexp"
"strings"
"github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var toFileName = regexp.MustCompile(`[^(\w/.)]`)
// IsClusterWide returns true if ns designates cluster scope, false otherwise.
func IsClusterWide(ns string) bool {
return ns == NamespaceAll || ns == BlankNamespace || ns == ClusterScope
}
func PrintNamespace(ns string) string {
if IsAllNamespaces(ns) {
return "all"
}
return ns
}
// CleanseNamespace ensures all ns maps to blank.
func CleanseNamespace(ns string) string {
if IsAllNamespace(ns) {
return BlankNamespace
}
return ns
}
// IsAllNamespace returns true if ns == all.
func IsAllNamespace(ns string) bool {
return ns == NamespaceAll
}
// IsAllNamespaces returns true if all namespaces, false otherwise.
func IsAllNamespaces(ns string) bool {
return ns == NamespaceAll || ns == BlankNamespace
}
// IsNamespaced returns true if a specific ns is given.
func IsNamespaced(ns string) bool {
return !IsAllNamespaces(ns) && !IsClusterScoped(ns)
}
// IsClusterScoped returns true if resource is not namespaced.
func IsClusterScoped(ns string) bool {
return ns == ClusterScope
}
// Namespaced converts a resource path to namespace and resource name.
func Namespaced(p string) (ns, name string) {
ns, name = path.Split(p)
return strings.Trim(ns, "/"), name
}
// CoFQN returns a fully qualified container name.
func CoFQN(m *metav1.ObjectMeta, co string) string {
return MetaFQN(m) + ":" + co
}
// FQN returns a fully qualified resource name.
func FQN(ns, n string) string {
if ns == "" {
return n
}
return ns + "/" + n
}
// MetaFQN returns a fully qualified resource name.
func MetaFQN(m *metav1.ObjectMeta) string {
if m.Namespace == "" {
return FQN(ClusterScope, m.Name)
}
return FQN(m.Namespace, m.Name)
}
func mustHomeDir() string {
usr, err := user.Current()
if err != nil {
slog.Error("Die getting user home directory", slogs.Error, err)
os.Exit(1)
}
return usr.HomeDir
}
func toHostDir(host string) string {
h := strings.Replace(
strings.Replace(host, "https://", "", 1),
"http://", "", 1,
)
return toFileName.ReplaceAllString(h, "_")
}
================================================
FILE: internal/client/metrics.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"context"
"errors"
"fmt"
"math"
"strconv"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/cache"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
const (
mxCacheSize = 100
mxCacheExpiry = 1 * time.Minute
)
// MetricsDial tracks global metric server handle.
var MetricsDial *MetricsServer
// DialMetrics dials the metrics server.
func DialMetrics(c Connection) *MetricsServer {
if MetricsDial == nil {
MetricsDial = NewMetricsServer(c)
}
return MetricsDial
}
// ResetMetrics resets the metric server handle.
func ResetMetrics() {
MetricsDial = nil
}
// MetricsServer serves cluster metrics for nodes and pods.
type MetricsServer struct {
Connection
cache *cache.LRUExpireCache
}
// NewMetricsServer return a metric server instance.
func NewMetricsServer(c Connection) *MetricsServer {
return &MetricsServer{
Connection: c,
cache: cache.NewLRUExpireCache(mxCacheSize),
}
}
// ClusterLoad retrieves all cluster nodes metrics.
func (*MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsList, mx *ClusterMetrics) error {
if nos == nil || nmx == nil {
return fmt.Errorf("invalid node or node metrics lists")
}
nodeMetrics := make(NodesMetrics, len(nos.Items))
for i := range nos.Items {
nodeMetrics[nos.Items[i].Name] = NodeMetrics{
AllocatableCPU: nos.Items[i].Status.Allocatable.Cpu().MilliValue(),
AllocatableMEM: nos.Items[i].Status.Allocatable.Memory().Value(),
}
}
for i := range nmx.Items {
if node, ok := nodeMetrics[nmx.Items[i].Name]; ok {
node.CurrentCPU = nmx.Items[i].Usage.Cpu().MilliValue()
node.CurrentMEM = nmx.Items[i].Usage.Memory().Value()
nodeMetrics[nmx.Items[i].Name] = node
}
}
var ccpu, cmem, tcpu, tmem int64
for _, mx := range nodeMetrics {
ccpu += mx.CurrentCPU
cmem += mx.CurrentMEM
tcpu += mx.AllocatableCPU
tmem += mx.AllocatableMEM
}
mx.PercCPU, mx.PercMEM = ToPercentage(ccpu, tcpu), ToPercentage(cmem, tmem)
return nil
}
func (m *MetricsServer) checkAccess(ns string, gvr *GVR, msg string) error {
if !m.HasMetrics() {
return errors.New("no metrics-server detected on cluster")
}
auth, err := m.CanI(ns, gvr, "", ListAccess)
if err != nil {
return err
}
if !auth {
return errors.New(msg)
}
return nil
}
// NodesMetrics retrieves metrics for a given set of nodes.
func (*MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
if nodes == nil || metrics == nil {
return
}
for i := range nodes.Items {
mmx[nodes.Items[i].Name] = NodeMetrics{
AllocatableCPU: nodes.Items[i].Status.Allocatable.Cpu().MilliValue(),
AllocatableMEM: ToMB(nodes.Items[i].Status.Allocatable.Memory().Value()),
AllocatableEphemeral: ToMB(nodes.Items[i].Status.Allocatable.StorageEphemeral().Value()),
TotalCPU: nodes.Items[i].Status.Capacity.Cpu().MilliValue(),
TotalMEM: ToMB(nodes.Items[i].Status.Capacity.Memory().Value()),
TotalEphemeral: ToMB(nodes.Items[i].Status.Capacity.StorageEphemeral().Value()),
}
}
for i := range metrics.Items {
mx, ok := mmx[metrics.Items[i].Name]
if !ok {
continue
}
mx.CurrentCPU = metrics.Items[i].Usage.Cpu().MilliValue()
mx.CurrentMEM = ToMB(metrics.Items[i].Usage.Memory().Value())
mx.AvailableCPU = mx.AllocatableCPU - mx.CurrentCPU
mx.AvailableMEM = mx.AllocatableMEM - mx.CurrentMEM
mmx[metrics.Items[i].Name] = mx
}
}
// FetchNodesMetricsMap fetch node metrics as a map.
func (m *MetricsServer) FetchNodesMetricsMap(ctx context.Context) (NodesMetricsMap, error) {
mm, err := m.FetchNodesMetrics(ctx)
if err != nil {
return nil, err
}
hh := make(NodesMetricsMap, len(mm.Items))
for i := range mm.Items {
mx := mm.Items[i]
hh[mx.Name] = &mx
}
return hh, nil
}
// FetchNodesMetrics return all metrics for nodes.
func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMetricsList, error) {
const msg = "user is not authorized to list node metrics"
mx := new(mv1beta1.NodeMetricsList)
if err := m.checkAccess(ClusterScope, NmxGVR, msg); err != nil {
return mx, err
}
const key = "nodes"
if entry, ok := m.cache.Get(key); ok && entry != nil {
mxList, ok := entry.(*mv1beta1.NodeMetricsList)
if !ok {
return nil, fmt.Errorf("expected nodemetricslist but got %T", entry)
}
return mxList, nil
}
client, err := m.MXDial()
if err != nil {
return mx, err
}
mxList, err := client.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
if err != nil {
return mx, err
}
m.cache.Add(key, mxList, mxCacheExpiry)
return mxList, nil
}
// FetchNodeMetrics return all metrics for nodes.
func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1beta1.NodeMetrics, error) {
const msg = "user is not authorized to list node metrics"
mx := new(mv1beta1.NodeMetrics)
if err := m.checkAccess(ClusterScope, NmxGVR, msg); err != nil {
return mx, err
}
mmx, err := m.FetchNodesMetricsMap(ctx)
if err != nil {
return nil, err
}
mx, ok := mmx[n]
if !ok {
return nil, fmt.Errorf("unable to retrieve node metrics for %q", n)
}
return mx, nil
}
// FetchPodsMetricsMap fetch pods metrics as a map.
func (m *MetricsServer) FetchPodsMetricsMap(ctx context.Context, ns string) (PodsMetricsMap, error) {
mm, err := m.FetchPodsMetrics(ctx, ns)
if err != nil {
return nil, err
}
hh := make(PodsMetricsMap, len(mm.Items))
for i := range mm.Items {
mx := mm.Items[i]
hh[FQN(mx.Namespace, mx.Name)] = &mx
}
return hh, nil
}
// FetchPodsMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1beta1.PodMetricsList, error) {
mx := new(mv1beta1.PodMetricsList)
const msg = "user is not authorized to list pods metrics"
if ns == NamespaceAll {
ns = BlankNamespace
}
if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
return mx, err
}
key := FQN(ns, "pods")
if entry, ok := m.cache.Get(key); ok {
mxList, ok := entry.(*mv1beta1.PodMetricsList)
if !ok {
return mx, fmt.Errorf("expected PodMetricsList but got %T", entry)
}
return mxList, nil
}
client, err := m.MXDial()
if err != nil {
return mx, err
}
mxList, err := client.MetricsV1beta1().PodMetricses(ns).List(ctx, metav1.ListOptions{})
if err != nil {
return mx, err
}
m.cache.Add(key, mxList, mxCacheExpiry)
return mxList, err
}
// FetchContainersMetrics returns a pod's containers metrics.
func (m *MetricsServer) FetchContainersMetrics(ctx context.Context, fqn string) (ContainersMetrics, error) {
mm, err := m.FetchPodMetrics(ctx, fqn)
if err != nil {
return nil, err
}
cmx := make(ContainersMetrics, len(mm.Containers))
for i := range mm.Containers {
c := mm.Containers[i]
cmx[c.Name] = &c
}
return cmx, nil
}
// FetchPodMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1beta1.PodMetrics, error) {
var mx *mv1beta1.PodMetrics
const msg = "user is not authorized to list pod metrics"
ns, _ := Namespaced(fqn)
if ns == NamespaceAll {
ns = BlankNamespace
}
if err := m.checkAccess(ns, PmxGVR, msg); err != nil {
return mx, err
}
mmx, err := m.FetchPodsMetricsMap(ctx, ns)
if err != nil {
return nil, err
}
pmx, ok := mmx[fqn]
if !ok {
return nil, fmt.Errorf("unable to locate pod metrics for pod %q", fqn)
}
return pmx, nil
}
// PodsMetrics retrieves metrics for all pods in a given namespace.
func (*MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetrics) {
if pods == nil {
return
}
// Compute all pod's containers metrics.
for i := range pods.Items {
var mx PodMetrics
for _, c := range pods.Items[i].Containers {
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
}
mmx[pods.Items[i].Namespace+"/"+pods.Items[i].Name] = mx
}
}
// ----------------------------------------------------------------------------
// Helpers...
// MegaByte represents a megabyte.
const MegaByte = 1024 * 1024
// ToMB converts bytes to megabytes.
func ToMB(v int64) int64 {
return v / MegaByte
}
// ToPercentage computes percentage as string otherwise n/aa.
func ToPercentage(v, dv int64) int {
if dv == 0 {
return 0
}
return int(math.Floor((float64(v) / float64(dv)) * 100))
}
// ToPercentageStr computes percentage, but if v2 is 0, it will return NAValue instead of 0.
func ToPercentageStr(v, dv int64) string {
if dv == 0 {
return NA
}
return strconv.Itoa(ToPercentage(v, dv))
}
================================================
FILE: internal/client/metrics_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client_test
import (
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
func TestToPercentage(t *testing.T) {
uu := []struct {
v1, v2 int64
e int
}{
{0, 0, 0},
{100, 200, 50},
{200, 100, 200},
{224, 4000, 5},
}
for _, u := range uu {
assert.Equal(t, u.e, client.ToPercentage(u.v1, u.v2))
}
}
func TestToMB(t *testing.T) {
uu := []struct {
v int64
e int64
}{
{0, 0},
{2 * client.MegaByte, 2},
{10 * client.MegaByte, 10},
}
for _, u := range uu {
assert.Equal(t, u.e, client.ToMB(u.v))
}
}
func TestPodsMetrics(t *testing.T) {
uu := map[string]struct {
metrics *v1beta1.PodMetricsList
eSize int
e client.PodsMetrics
}{
"dud": {
eSize: 0,
},
"ok": {
metrics: &v1beta1.PodMetricsList{
Items: []v1beta1.PodMetrics{
*makeMxPod("p1", "1", "4Gi"),
*makeMxPod("p2", "50m", "1Mi"),
},
},
eSize: 2,
e: client.PodsMetrics{
"default/p1": client.PodMetrics{
CurrentCPU: 3000,
CurrentMEM: 12288,
},
},
},
}
m := client.NewMetricsServer(nil)
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
mmx := make(client.PodsMetrics)
m.PodsMetrics(u.metrics, mmx)
assert.Len(t, mmx, u.eSize)
if u.eSize == 0 {
return
}
mx, ok := mmx["default/p1"]
assert.True(t, ok)
assert.Equal(t, u.e["default/p1"], mx)
})
}
}
func BenchmarkPodsMetrics(b *testing.B) {
m := client.NewMetricsServer(nil)
metrics := v1beta1.PodMetricsList{
Items: []v1beta1.PodMetrics{
*makeMxPod("p1", "1", "4Gi"),
*makeMxPod("p2", "50m", "1Mi"),
*makeMxPod("p3", "50m", "1Mi"),
},
}
mmx := make(client.PodsMetrics, 3)
b.ResetTimer()
b.ReportAllocs()
for range b.N {
m.PodsMetrics(&metrics, mmx)
}
}
func TestNodesMetrics(t *testing.T) {
uu := map[string]struct {
nodes *v1.NodeList
metrics *v1beta1.NodeMetricsList
eSize int
e client.NodesMetrics
}{
"duds": {
eSize: 0,
},
"no_nodes": {
metrics: &v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "10", "8Gi"),
*makeMxNode("n2", "50m", "1Mi"),
},
},
eSize: 0,
},
"no_metrics": {
nodes: &v1.NodeList{
Items: []v1.Node{
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "10Mi"),
},
},
eSize: 0,
},
"ok": {
nodes: &v1.NodeList{
Items: []v1.Node{
makeNode("n1", "32", "128Gi", "32", "128Gi"),
makeNode("n2", "8", "4Gi", "8", "4Gi"),
},
},
metrics: &v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "10", "8Gi"),
*makeMxNode("n2", "50m", "1Mi"),
},
},
eSize: 2,
e: client.NodesMetrics{
"n1": client.NodeMetrics{
TotalCPU: 32000,
TotalMEM: 131072,
AllocatableCPU: 32000,
AllocatableMEM: 131072,
AvailableCPU: 22000,
AvailableMEM: 122880,
CurrentMetrics: client.CurrentMetrics{
CurrentCPU: 10000,
CurrentMEM: 8192,
},
},
},
},
}
m := client.NewMetricsServer(nil)
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
mmx := make(client.NodesMetrics)
m.NodesMetrics(u.nodes, u.metrics, mmx)
assert.Len(t, mmx, u.eSize)
if u.eSize == 0 {
return
}
mx, ok := mmx["n1"]
assert.True(t, ok)
assert.Equal(t, u.e["n1"], mx)
})
}
}
func BenchmarkNodesMetrics(b *testing.B) {
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "100m", "2Mi"),
makeNode("n2", "100m", "4Mi", "100m", "2Mi"),
},
}
metrics := v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "50m", "1Mi"),
*makeMxNode("n2", "50m", "1Mi"),
},
}
m := client.NewMetricsServer(nil)
mmx := make(client.NodesMetrics)
b.ResetTimer()
b.ReportAllocs()
for range b.N {
m.NodesMetrics(&nodes, &metrics, mmx)
}
}
func TestClusterLoad(t *testing.T) {
uu := map[string]struct {
nodes *v1.NodeList
metrics *v1beta1.NodeMetricsList
eSize int
e client.ClusterMetrics
}{
"duds": {
eSize: 0,
},
"no_nodes": {
metrics: &v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "10", "8Gi"),
*makeMxNode("n2", "50m", "1Mi"),
},
},
eSize: 0,
},
"no_metrics": {
nodes: &v1.NodeList{
Items: []v1.Node{
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "10Mi"),
},
},
eSize: 0,
},
"ok": {
nodes: &v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
},
},
metrics: &v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "50m", "1Mi"),
*makeMxNode("n2", "50m", "1Mi"),
},
},
eSize: 2,
e: client.ClusterMetrics{
PercCPU: 100.0,
PercMEM: 50.0,
},
},
}
m := client.NewMetricsServer(nil)
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
var cmx client.ClusterMetrics
_ = m.ClusterLoad(u.nodes, u.metrics, &cmx)
assert.Equal(t, u.e, cmx)
})
}
}
func BenchmarkClusterLoad(b *testing.B) {
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
},
}
metrics := v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
*makeMxNode("n1", "50m", "1Mi"),
*makeMxNode("n2", "50m", "1Mi"),
},
}
m := client.NewMetricsServer(nil)
var mx client.ClusterMetrics
b.ResetTimer()
b.ReportAllocs()
for range b.N {
_ = m.ClusterLoad(&nodes, &metrics, &mx)
}
}
// ----------------------------------------------------------------------------
// Helpers...
func makeMxPod(name, cpu, mem string) *v1beta1.PodMetrics {
return &v1beta1.PodMetrics{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "default",
},
Containers: []v1beta1.ContainerMetrics{
{Usage: makeRes(cpu, mem)},
{Usage: makeRes(cpu, mem)},
{Usage: makeRes(cpu, mem)},
},
}
}
func makeNode(name, tcpu, tmem, acpu, amem string) v1.Node {
return v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Status: v1.NodeStatus{
Capacity: makeRes(tcpu, tmem),
Allocatable: makeRes(acpu, amem),
},
}
}
func makeMxNode(name, cpu, mem string) *v1beta1.NodeMetrics {
return &v1beta1.NodeMetrics{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Usage: makeRes(cpu, mem),
}
}
func makeRes(c, m string) v1.ResourceList {
cpu, _ := resource.ParseQuantity(c)
mem, _ := resource.ParseQuantity(m)
return v1.ResourceList{
v1.ResourceCPU: cpu,
v1.ResourceMemory: mem,
}
}
================================================
FILE: internal/client/switch_context_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/cache"
"k8s.io/apimachinery/pkg/version"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
const (
testContext1 = "context1"
testContext2 = "context2"
)
func newFakeK8sServer(t *testing.T) (*httptest.Server, *atomic.Int32) {
t.Helper()
versionCalls := &atomic.Int32{}
mux := http.NewServeMux()
mux.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) {
versionCalls.Add(1)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(version.Info{
Major: "1",
Minor: "28",
GitVersion: "v1.28.0",
})
})
mux.HandleFunc("/api", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["v1"]}`))
})
mux.HandleFunc("/apis", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`))
})
return httptest.NewServer(mux), versionCalls
}
func writeSwitchTestKubeconfig(t *testing.T, server1URL, server2URL string) string {
t.Helper()
dir := t.TempDir()
kubeconfig := filepath.Join(dir, "config")
content := `apiVersion: v1
kind: Config
current-context: context1
contexts:
- context:
cluster: cluster1
user: user1
namespace: default
name: context1
- context:
cluster: cluster2
user: user1
namespace: kube-system
name: context2
clusters:
- cluster:
server: ` + server1URL + `
name: cluster1
- cluster:
server: ` + server2URL + `
name: cluster2
users:
- name: user1
user:
token: test-token
`
require.NoError(t, os.WriteFile(kubeconfig, []byte(content), 0600))
return kubeconfig
}
func setupSwitchTest(t *testing.T) (*httptest.Server, *atomic.Int32, *APIClient) {
t.Helper()
srv, versionCalls := newFakeK8sServer(t)
t.Cleanup(srv.Close)
t.Setenv("HOME", t.TempDir())
kubeconfig := writeSwitchTestKubeconfig(t, srv.URL, srv.URL)
flags := genericclioptions.NewConfigFlags(false)
flags.KubeConfig = &kubeconfig
ctx := testContext1
flags.Context = &ctx
a := &APIClient{
config: NewConfig(flags),
cache: cache.NewLRUExpireCache(cacheSize),
connOK: true,
log: slog.Default(),
}
return srv, versionCalls, a
}
func TestSwitchContextSuccess(t *testing.T) {
_, _, a := setupSwitchTest(t)
err := a.SwitchContext(testContext2)
require.NoError(t, err)
ctx, err := a.config.CurrentContextName()
require.NoError(t, err)
assert.Equal(t, testContext2, ctx)
assert.True(t, a.getConnOK())
}
func TestSwitchContextReusesConnectivityClient(t *testing.T) {
_, _, a := setupSwitchTest(t)
err := a.SwitchContext(testContext2)
require.NoError(t, err)
assert.NotNil(t, a.getClient(),
"SwitchContext should store the connectivity-check client for reuse by Dial()")
}
func TestSwitchContextPreWarmsDynDial(t *testing.T) {
_, _, a := setupSwitchTest(t)
err := a.SwitchContext(testContext2)
require.NoError(t, err)
assert.NotNil(t, a.getDClient(),
"SwitchContext should pre-warm the dynamic client so gotoResource reuses it")
}
func TestSwitchContextDialAfterSwitch(t *testing.T) {
_, _, a := setupSwitchTest(t)
err := a.SwitchContext(testContext2)
require.NoError(t, err)
storedClient := a.getClient()
require.NotNil(t, storedClient, "SwitchContext should store client")
dialedClient, err := a.Dial()
require.NoError(t, err)
assert.Same(t, storedClient, dialedClient,
"Dial() should return the stored connectivity client, not create a new one")
}
func TestSwitchContextMinimalVersionCalls(t *testing.T) {
_, versionCalls, a := setupSwitchTest(t)
err := a.SwitchContext(testContext2)
require.NoError(t, err)
assert.Equal(t, int32(1), versionCalls.Load(),
"SwitchContext should call ServerVersion exactly once")
}
func TestSwitchContextInvalidContext(t *testing.T) {
_, _, a := setupSwitchTest(t)
err := a.SwitchContext("nonexistent")
assert.Error(t, err)
}
func TestInitConnectionMetricsUnsupported(t *testing.T) {
srv, _, _ := setupSwitchTest(t)
kubeconfig := writeSwitchTestKubeconfig(t, srv.URL, srv.URL)
flags := genericclioptions.NewConfigFlags(false)
flags.KubeConfig = &kubeconfig
ctx := testContext1
flags.Context = &ctx
a, err := InitConnection(NewConfig(flags), slog.Default())
require.NoError(t, err)
assert.True(t, a.ConnectionOK(),
"InitConnection should succeed when metrics-server is absent")
}
func TestInitConnectionStoresDialClient(t *testing.T) {
srv, _, _ := setupSwitchTest(t)
kubeconfig := writeSwitchTestKubeconfig(t, srv.URL, srv.URL)
flags := genericclioptions.NewConfigFlags(false)
flags.KubeConfig = &kubeconfig
ctx := testContext1
flags.Context = &ctx
a, err := InitConnection(NewConfig(flags), slog.Default())
require.NoError(t, err)
assert.NotNil(t, a.getClient(),
"InitConnection should store a Dial client for reuse")
}
================================================
FILE: internal/client/testdata/config
================================================
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3000
name: fred
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3001
name: blee
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3002
name: zorg
contexts:
- context:
cluster: zorg
user: fred
name: fred
- context:
cluster: blee
user: blee
namespace: zorg
name: blee
- context:
cluster: duh
user: duh
name: duh
current-context: fred
users:
- name: fred
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: blee
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: duh
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
================================================
FILE: internal/client/testdata/config.1
================================================
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
current-context: blee
kind: Config
users: null
================================================
FILE: internal/client/testdata/config.2
================================================
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
================================================
FILE: internal/client/types.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package client
import (
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
)
const (
// NA Not available.
NA = "n/a"
// NamespaceAll designates the fictional all namespace.
NamespaceAll = "all"
// BlankNamespace designates no namespace.
BlankNamespace = ""
// DefaultNamespace designates the default namespace.
DefaultNamespace = "default"
// ClusterScope designates a resource is not namespaced.
ClusterScope = "-"
// NotNamespaced designates a non resource namespace.
NotNamespaced = "*"
// CreateVerb represents create access on a resource.
CreateVerb = "create"
// UpdateVerb represents an update access on a resource.
UpdateVerb = "update"
// PatchVerb represents a patch access on a resource.
PatchVerb = "patch"
// DeleteVerb represents a delete access on a resource.
DeleteVerb = "delete"
// GetVerb represents a get access on a resource.
GetVerb = "get"
// ListVerb represents a list access on a resource.
ListVerb = "list"
// WatchVerb represents a watch access on a resource.
WatchVerb = "watch"
)
var (
// PatchAccess patch a resource.
PatchAccess = []string{PatchVerb}
// GetAccess reads a resource.
GetAccess = []string{GetVerb}
// ListAccess list resources.
ListAccess = []string{ListVerb}
// MonitorAccess monitors a collection of resources.
MonitorAccess = []string{ListVerb, WatchVerb}
// ReadAllAccess represents an all read access to a resource.
ReadAllAccess = []string{GetVerb, ListVerb, WatchVerb}
)
// ContainersMetrics tracks containers metrics.
type ContainersMetrics map[string]*mv1beta1.ContainerMetrics
// NodesMetricsMap tracks node metrics.
type NodesMetricsMap map[string]*mv1beta1.NodeMetrics
// PodsMetricsMap tracks pod metrics.
type PodsMetricsMap map[string]*mv1beta1.PodMetrics
// Authorizer checks what a user can or cannot do to a resource.
type Authorizer interface {
// CanI returns true if the user can use these actions for a given resource.
CanI(ns string, gvr *GVR, n string, verbs []string) (bool, error)
}
// Connection represents a Kubernetes apiserver connection.
type Connection interface {
Authorizer
// Config returns current config.
Config() *Config
// ConnectionOK checks api server connection status.
ConnectionOK() bool
// Dial connects to api server.
Dial() (kubernetes.Interface, error)
// DialLogs connects to api server for logs.
DialLogs() (kubernetes.Interface, error)
// SwitchContext switches cluster based on context.
SwitchContext(ctx string) error
// CachedDiscovery connects to discovery client.
CachedDiscovery() (*disk.CachedDiscoveryClient, error)
// RestConfig connects to rest client.
RestConfig() (*restclient.Config, error)
// MXDial connects to metrics server.
MXDial() (*versioned.Clientset, error)
// DynDial connects to dynamic client.
DynDial() (dynamic.Interface, error)
// HasMetrics checks if metrics server is available.
HasMetrics() bool
// ValidNamespaceNames 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)
// CheckConnectivity checks if api server connection is happy or not.
CheckConnectivity() bool
// ActiveContext returns the current context name.
ActiveContext() string
// ActiveNamespace returns the current namespace.
ActiveNamespace() string
// IsActiveNamespace checks if given ns is active.
IsActiveNamespace(string) bool
}
// CurrentMetrics tracks current cpu/mem.
type CurrentMetrics struct {
CurrentCPU, CurrentMEM, CurrentEphemeral int64
}
// PodMetrics represent an aggregation of all pod containers metrics.
type PodMetrics CurrentMetrics
// NodeMetrics describes raw node metrics.
type NodeMetrics struct {
CurrentMetrics
AllocatableCPU, AllocatableMEM, AllocatableEphemeral int64
AvailableCPU, AvailableMEM, AvailableEphemeral int64
TotalCPU, TotalMEM, TotalEphemeral int64
}
// ClusterMetrics summarizes total node metrics as percentages.
type ClusterMetrics struct {
PercCPU, PercMEM, PercEphemeral int
}
// NodesMetrics tracks usage metrics per nodes.
type NodesMetrics map[string]NodeMetrics
// PodsMetrics tracks usage metrics per pods.
type PodsMetrics map[string]PodMetrics
================================================
FILE: internal/color/colorize.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package color
import (
"fmt"
"strconv"
)
const colorFmt = "\x1b[%dm%s\x1b[0m"
// Paint describes a terminal color.
type Paint int
// Defines basic ANSI colors.
const (
Black Paint = iota + 30 // 30
Red // 31
Green // 32
Yellow // 33
Blue // 34
Magenta // 35
Cyan // 36
LightGray // 37
DarkGray = 90
Bold = 1
)
// Colorize returns an ASCII colored string based on given color.
func Colorize(s string, c Paint) string {
if c == 0 {
return s
}
return fmt.Sprintf(colorFmt, c, s)
}
// ANSIColorize colors a string.
func ANSIColorize(text string, color int) string {
return "\033[38;5;" + strconv.Itoa(color) + "m" + text + "\033[0m"
}
// Highlight colorize bytes at given indices.
func Highlight(bb []byte, ii []int, c int) []byte {
if len(ii) == 0 {
return bb
}
result := make([]byte, 0, len(bb)+len(ii)*20) // Extra space for color codes
// Create a map of byte positions that should be highlighted
highlightMap := make(map[int]bool)
for _, pos := range ii {
highlightMap[pos] = true
}
// Process each byte
for i := 0; i < len(bb); i++ {
if highlightMap[i] {
// Check if this is the start of a UTF-8 character
if (bb[i] & 0xC0) != 0x80 {
// This is the start of a character, find the end
charStart := i
charEnd := i + 1
for charEnd < len(bb) && (bb[charEnd]&0xC0) == 0x80 {
charEnd++
}
// Colorize the entire character
char := string(bb[charStart:charEnd])
colored := ANSIColorize(char, c)
result = append(result, []byte(colored)...)
i = charEnd - 1 // Skip the rest of the character bytes
} else {
// This is a continuation byte, skip it (already handled)
continue
}
} else {
result = append(result, bb[i])
}
}
return result
}
================================================
FILE: internal/color/colorize_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package color_test
import (
"testing"
"github.com/derailed/k9s/internal/color"
"github.com/stretchr/testify/assert"
)
func TestColorize(t *testing.T) {
uu := map[string]struct {
s string
c color.Paint
e string
}{
"white": {"blee", color.LightGray, "\x1b[37mblee\x1b[0m"},
"black": {"blee", color.Black, "\x1b[30mblee\x1b[0m"},
"default": {"blee", 0, "blee"},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, color.Colorize(u.s, u.c))
})
}
}
func TestHighlight(t *testing.T) {
uu := map[string]struct {
text []byte
indices []int
color int
e string
}{
"white": {
text: []byte("the brown fox"),
color: 209,
indices: []int{4, 5, 6, 7, 8},
e: "the \x1b[38;5;209mb\x1b[0m\x1b[38;5;209mr\x1b[0m\x1b[38;5;209mo\x1b[0m\x1b[38;5;209mw\x1b[0m\x1b[38;5;209mn\x1b[0m fox",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, string(color.Highlight(u.text, u.indices, u.color)))
})
}
}
================================================
FILE: internal/config/alias.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"errors"
"io/fs"
"log/slog"
"os"
"strings"
"sync"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/util/sets"
)
type (
// Alias tracks shortname to GVR mappings.
Alias map[string]*client.GVR
// ShortNames represents a collection of shortnames for aliases.
ShortNames map[*client.GVR][]string
// Aliases represents a collection of aliases.
Aliases struct {
Alias Alias `yaml:"aliases"`
mx sync.RWMutex
}
)
// NewAliases return a new alias.
func NewAliases() *Aliases {
return &Aliases{
Alias: make(Alias, 50),
}
}
func (a *Aliases) AliasesFor(gvr *client.GVR) sets.Set[string] {
a.mx.RLock()
defer a.mx.RUnlock()
ss := sets.New[string]()
for alias, aliasGVR := range a.Alias {
if aliasGVR == gvr {
ss.Insert(alias)
}
}
return ss
}
// ShortNames return all shortnames.
func (a *Aliases) ShortNames() ShortNames {
a.mx.RLock()
defer a.mx.RUnlock()
m := make(ShortNames, len(a.Alias))
for alias, gvr := range a.Alias {
if v, ok := m[gvr]; ok {
m[gvr] = append(v, alias)
} else {
m[gvr] = []string{alias}
}
}
return m
}
// Clear remove all aliases.
func (a *Aliases) Clear() {
a.mx.Lock()
defer a.mx.Unlock()
for k := range a.Alias {
delete(a.Alias, k)
}
}
func (a *Aliases) Resolve(p *cmd.Interpreter) (*client.GVR, bool) {
gvr, ok := a.Get(p.Cmd())
if !ok {
return nil, false
}
if gvr.IsK8sRes() {
p.Reset(strings.Replace(p.GetLine(), p.Cmd(), gvr.String(), 1), p.Cmd())
return gvr, true
}
for gvr.IsAlias() {
ap := cmd.NewInterpreter(gvr.String())
gvr, ok = a.Get(ap.Cmd())
if !ok {
return gvr, false
}
ap.Merge(p)
p.Reset(strings.Replace(ap.GetLine(), ap.Cmd(), gvr.String(), 1), ap.Cmd())
}
return gvr, true
}
// Get retrieves an alias.
func (a *Aliases) Get(alias string) (*client.GVR, bool) {
a.mx.RLock()
defer a.mx.RUnlock()
gvr, ok := a.Alias[alias]
return gvr, ok
}
// Define declares a new alias.
func (a *Aliases) Define(gvr *client.GVR, aliases ...string) {
a.mx.Lock()
defer a.mx.Unlock()
for _, alias := range aliases {
if _, ok := a.Alias[alias]; !ok && alias != "" {
a.Alias[alias] = gvr
}
}
}
// Load K9s aliases.
func (a *Aliases) Load(path string) error {
a.loadDefaultAliases()
f, err := EnsureAliasesCfgFile()
if err != nil {
slog.Error("Unable to gen config aliases", slogs.Error, err)
}
// load global alias file
if err := a.LoadFile(f); err != nil {
return err
}
// load context specific aliases if any
return a.LoadFile(path)
}
// LoadFile loads alias from a given file.
func (a *Aliases) LoadFile(path string) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
bb, err := os.ReadFile(path)
if err != nil {
return err
}
if err := data.JSONValidator.Validate(json.AliasesSchema, bb); err != nil {
slog.Warn("Aliases validation failed", slogs.Error, err)
}
a.mx.Lock()
if err := yaml.Unmarshal(bb, a); err != nil {
return err
}
for k, v := range a.Alias {
a.Alias[k] = client.NewGVR(v.String())
}
defer a.mx.Unlock()
return nil
}
func (a *Aliases) declare(gvr *client.GVR, aliases ...string) {
a.Alias[gvr.String()] = gvr
for _, alias := range aliases {
a.Alias[alias] = gvr
}
}
func (a *Aliases) loadDefaultAliases() {
a.mx.Lock()
defer a.mx.Unlock()
a.declare(client.HlpGVR, "h", "?")
a.declare(client.QGVR, "q", "q!", "qa", "Q")
a.declare(client.AliGVR, "alias", "a")
a.declare(client.HmGVR, "charts", "chart", "hm")
a.declare(client.DirGVR, "dir", "d")
a.declare(client.CtGVR, "context", "ctx")
a.declare(client.UsrGVR, "user", "usr")
a.declare(client.GrpGVR, "group", "grp")
a.declare(client.PfGVR, "portforward", "pf")
a.declare(client.BeGVR, "benchmark", "bench")
a.declare(client.SdGVR, "screendump", "sd")
a.declare(client.PuGVR, "pulse", "pu", "hz")
a.declare(client.XGVR, "xray", "x")
a.declare(client.WkGVR, "workload", "wk")
}
// Save alias to disk.
func (a *Aliases) Save() error {
slog.Debug("Saving Aliases...")
a.mx.RLock()
defer a.mx.RUnlock()
return a.saveAliases(AppAliasesFile)
}
// SaveAliases saves aliases to a given file.
func (a *Aliases) saveAliases(path string) error {
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
return err
}
return data.SaveYAML(path, a)
}
================================================
FILE: internal/config/alias_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"maps"
"os"
"path"
"slices"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAliasClear(t *testing.T) {
a := testAliases()
a.Clear()
assert.Empty(t, slices.Collect(maps.Keys(a.Alias)))
}
func TestAliasKeys(t *testing.T) {
a := testAliases()
kk := maps.Keys(a.Alias)
assert.Equal(t, []string{"a1", "a11", "a2", "a3"}, slices.Sorted(kk))
}
func TestAliasShortNames(t *testing.T) {
a := testAliases()
ess := config.ShortNames{
gvr1: []string{"a1", "a11"},
gvr2: []string{"a2"},
gvr3: []string{"a3"},
}
ss := a.ShortNames()
assert.Len(t, ss, len(ess))
for k, v := range ss {
v1, ok := ess[k]
assert.True(t, ok, "missing: %q", k)
slices.Sort(v)
assert.Equal(t, v1, v)
}
}
func TestAliasDefine(t *testing.T) {
type aliasDef struct {
gvr *client.GVR
aliases []string
}
uu := map[string]struct {
aliases []aliasDef
registeredCommands map[string]*client.GVR
}{
"simple": {
aliases: []aliasDef{
{
gvr: client.NewGVR("one"),
aliases: []string{"blee", "duh"},
},
},
registeredCommands: map[string]*client.GVR{
"blee": client.NewGVR("one"),
"duh": client.NewGVR("one"),
},
},
"duplicates": {
aliases: []aliasDef{
{
gvr: client.NewGVR("one"),
aliases: []string{"blee", "duh"},
}, {
gvr: client.NewGVR("two"),
aliases: []string{"blee", "duh", "fred", "zorg"},
},
},
registeredCommands: map[string]*client.GVR{
"blee": client.NewGVR("one"),
"duh": client.NewGVR("one"),
"fred": client.NewGVR("two"),
"zorg": client.NewGVR("two"),
},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
configAlias := config.NewAliases()
for _, aliases := range u.aliases {
for _, a := range aliases.aliases {
configAlias.Define(aliases.gvr, a)
}
}
for alias, cmd := range u.registeredCommands {
v, ok := configAlias.Get(alias)
assert.True(t, ok)
assert.Equal(t, cmd, v, "Wrong command for alias "+alias)
}
})
}
}
func TestAliasesLoad(t *testing.T) {
config.AppConfigDir = "testdata/aliases"
a := config.NewAliases()
require.NoError(t, a.Load(path.Join(config.AppConfigDir, "plain.yaml")))
assert.Len(t, a.Alias, 55)
}
func TestAliasesSave(t *testing.T) {
require.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod))
defer require.NoError(t, os.RemoveAll("/tmp/test-aliases"))
config.AppAliasesFile = "/tmp/test-aliases/aliases.yaml"
a := testAliases()
c := len(a.Alias)
assert.Len(t, a.Alias, c)
require.NoError(t, a.Save())
require.NoError(t, a.LoadFile(config.AppAliasesFile))
assert.Len(t, a.Alias, c)
}
func TestAliasResolve(t *testing.T) {
uu := map[string]struct {
exp string
ok bool
gvr *client.GVR
cmd *cmd.Interpreter
}{
"gvr": {
exp: "v1/pods",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods"),
},
"kind": {
exp: "pod",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods"),
},
"plural": {
exp: "pods",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods"),
},
"short-name": {
exp: "po",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods"),
},
"short-name-with-args": {
exp: "po 'a in (b,c)' @zorb bozo",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods 'a in (b,c)' @zorb bozo"),
},
"alias": {
exp: "pipo",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods"),
},
"toast-command": {
exp: "zorg",
},
"alias-no-args": {
exp: "wkl",
ok: true,
gvr: client.WkGVR,
cmd: cmd.NewInterpreter("workloads"),
},
"alias-ns-arg": {
exp: "pp",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods default"),
},
"multi-alias-ns-inception": {
exp: "ppo",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods 'a=b,b=c' default"),
},
"full-alias": {
exp: "ppc",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods @fred 'app=fred' default"),
},
"plain-filter": {
exp: "po /fred @bozo ns-1",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods /fred @bozo ns-1"),
},
"alias-filter": {
exp: "pipo /fred @bozo ns-1",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods /fred @bozo ns-1"),
},
"complex-filter": {
exp: "ppc /fred @bozo ns-1",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods @bozo /fred 'app=fred' ns-1"),
},
"filtered": {
exp: "pc",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods /cilium kube-system"),
},
"labels-in": {
exp: "ppp",
ok: true,
gvr: client.PodGVR,
cmd: cmd.NewInterpreter("v1/pods 'app in (be,fe)'"),
},
}
a := config.NewAliases()
a.Define(client.PodGVR, "po", "pipo", "pod")
a.Define(client.PodGVR, client.PodGVR.String())
a.Define(client.PodGVR, client.PodGVR.AsResourceName())
a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl")
a.Define(client.NewGVR("pod default"), "pp")
a.Define(client.NewGVR("pipo a=b,b=c default"), "ppo")
a.Define(client.NewGVR("pod default app=fred @fred"), "ppc")
a.Define(client.NewGVR("pod /cilium kube-system"), "pc")
a.Define(client.NewGVR("pod 'app in (be,fe)'"), "ppp")
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
p := cmd.NewInterpreter(u.exp)
gvr, ok := a.Resolve(p)
assert.Equal(t, u.ok, ok)
if ok {
assert.Equal(t, u.gvr, gvr)
assert.Equal(t, u.cmd.GetLine(), p.GetLine())
}
})
}
}
// Helpers...
var (
gvr1 = client.NewGVR("gvr1")
gvr2 = client.NewGVR("gvr2")
gvr3 = client.NewGVR("gvr3")
)
func testAliases() *config.Aliases {
a := config.NewAliases()
a.Alias["a1"] = gvr1
a.Alias["a11"] = gvr1
a.Alias["a2"] = gvr2
a.Alias["a3"] = gvr3
return a
}
================================================
FILE: internal/config/benchmark.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"net/http"
"os"
"gopkg.in/yaml.v3"
)
// K9sBench the name of the benchmarks config file.
var K9sBench = "bench"
type (
// Bench tracks K9s styling options.
Bench struct {
Benchmarks *Benchmarks `yaml:"benchmarks"`
}
// Benchmarks tracks K9s benchmarks configuration.
Benchmarks struct {
Defaults Benchmark `yaml:"defaults"`
Services map[string]BenchConfig `yam':"services"`
Containers map[string]BenchConfig `yam':"containers"`
}
// Auth basic auth creds.
Auth struct {
User string `yaml:"user"`
Password string `yaml:"password"`
}
// Benchmark represents a generic benchmark.
Benchmark struct {
C int `yaml:"concurrency"`
N int `yaml:"requests"`
}
// HTTP represents an http request.
HTTP struct {
Method string `yaml:"method"`
Host string `yaml:"host"`
Path string `yaml:"path"`
HTTP2 bool `yaml:"http2"`
Body string `yaml:"body"`
Headers http.Header `yaml:"headers"`
}
// BenchConfig represents a service benchmark.
BenchConfig struct {
Name string
C int `yaml:"concurrency"`
N int `yaml:"requests"`
Auth Auth `yaml:"auth"`
HTTP HTTP `yaml:"http"`
}
)
const (
// DefaultC default concurrency.
DefaultC = 1
// DefaultN default number of requests.
DefaultN = 200
// DefaultMethod default http verb.
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,
N: DefaultN,
}
}
// Empty checks if the benchmark is set.
func (b Benchmark) Empty() bool {
return b.C == 0 && b.N == 0
}
func newBenchmarks() *Benchmarks {
return &Benchmarks{
Defaults: newBenchmark(),
}
}
// NewBench creates a new default config.
func NewBench(path string) (*Bench, error) {
s := &Bench{Benchmarks: newBenchmarks()}
err := s.load(path)
return s, err
}
// Reload update the configuration from disk.
func (s *Bench) Reload(path string) error {
return s.load(path)
}
// Load K9s benchmark configs from file.
func (s *Bench) load(path string) error {
f, err := os.ReadFile(path)
if err != nil {
return err
}
return yaml.Unmarshal(f, &s)
}
================================================
FILE: internal/config/benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBenchEmpty(t *testing.T) {
uu := map[string]struct {
b Benchmark
e bool
}{
"empty": {Benchmark{}, true},
"notEmpty": {newBenchmark(), false},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.b.Empty())
})
}
}
func TestBenchLoad(t *testing.T) {
uu := map[string]struct {
file string
c, n int
svcCount int
coCount int
}{
"goodConfig": {
"testdata/benchmarks/b_good.yaml",
2,
1000,
2,
0,
},
"malformed": {
"testdata/benchmarks/b_toast.yaml",
1,
200,
0,
0,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench(u.file)
require.NoError(t, err)
assert.Equal(t, u.c, b.Benchmarks.Defaults.C)
assert.Equal(t, u.n, b.Benchmarks.Defaults.N)
assert.Len(t, b.Benchmarks.Services, u.svcCount)
assert.Len(t, b.Benchmarks.Containers, u.coCount)
})
}
}
func TestBenchServiceLoad(t *testing.T) {
uu := map[string]struct {
key string
c, n int
method, host, path string
http2 bool
body string
auth Auth
headers http.Header
}{
"s1": {
"default/nginx",
2,
1000,
"GET",
"10.10.10.10",
"/",
true,
`{"fred": "blee"}`,
Auth{"fred", "blee"},
http.Header{"Accept": []string{"text/html"}, "Content-Type": []string{"application/json"}},
},
"s2": {
"blee/fred",
10,
1500,
"POST",
"20.20.20.20",
"/zorg",
false,
`{"fred": "blee"}`,
Auth{"fred", "blee"},
http.Header{"Accept": []string{"text/html"}, "Content-Type": []string{"application/json"}},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_good.yaml")
require.NoError(t, err)
assert.Len(t, b.Benchmarks.Services, 2)
svc := b.Benchmarks.Services[u.key]
assert.Equal(t, u.c, svc.C)
assert.Equal(t, u.n, svc.N)
assert.Equal(t, u.method, svc.HTTP.Method)
assert.Equal(t, u.host, svc.HTTP.Host)
assert.Equal(t, u.path, svc.HTTP.Path)
assert.Equal(t, u.http2, svc.HTTP.HTTP2)
assert.Equal(t, u.body, svc.HTTP.Body)
assert.Equal(t, u.auth, svc.Auth)
assert.Equal(t, u.headers, svc.HTTP.Headers)
})
}
}
func TestBenchReLoad(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
require.NoError(t, err)
assert.Equal(t, 2, b.Benchmarks.Defaults.C)
require.NoError(t, b.Reload("testdata/benchmarks/b_containers_1.yaml"))
assert.Equal(t, 20, b.Benchmarks.Defaults.C)
}
func TestBenchLoadToast(t *testing.T) {
_, err := NewBench("testdata/toast.yaml")
assert.Error(t, err)
}
func TestBenchContainerLoad(t *testing.T) {
uu := map[string]struct {
key string
c, n int
method, host, path string
http2 bool
body string
auth Auth
headers http.Header
}{
"c1": {
"c1",
2,
1000,
"GET",
"10.10.10.10",
"/duh",
true,
`{"fred": "blee"}`,
Auth{"fred", "blee"},
http.Header{"Accept": []string{"text/html"}, "Content-Type": []string{"application/json"}},
},
"c2": {
"c2",
10,
1500,
"POST",
"20.20.20.20",
"/fred",
false,
`{"fred": "blee"}`,
Auth{"fred", "blee"},
http.Header{"Accept": []string{"text/html"}, "Content-Type": []string{"application/json"}},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("testdata/benchmarks/b_containers.yaml")
require.NoError(t, err)
assert.Len(t, b.Benchmarks.Services, 2)
co := b.Benchmarks.Containers[u.key]
assert.Equal(t, u.c, co.C)
assert.Equal(t, u.n, co.N)
assert.Equal(t, u.method, co.HTTP.Method)
assert.Equal(t, u.host, co.HTTP.Host)
assert.Equal(t, u.path, co.HTTP.Path)
assert.Equal(t, u.http2, co.HTTP.HTTP2)
assert.Equal(t, u.body, co.HTTP.Body)
assert.Equal(t, u.auth, co.Auth)
assert.Equal(t, u.headers, co.HTTP.Headers)
})
}
}
================================================
FILE: internal/config/color.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"fmt"
"github.com/derailed/tcell/v2"
"github.com/lucasb-eyer/go-colorful"
)
const (
// DefaultColor represents a default color.
DefaultColor Color = "default"
// TransparentColor represents the terminal bg color.
TransparentColor Color = "-"
)
// Colors tracks multiple colors.
type Colors []Color
// Colors converts series string colors to colors.
func (c Colors) Colors() []tcell.Color {
cc := make([]tcell.Color, 0, len(c))
for _, color := range c {
cc = append(cc, color.Color())
}
return cc
}
// Invert returns a new Colors with all colors inverted.
func (c Colors) Invert() Colors {
inverted := make(Colors, len(c))
for i, color := range c {
inverted[i] = color.InvertColor()
}
return inverted
}
// Color represents a color.
type Color string
// NewColor returns a new color.
func NewColor(c string) Color {
return Color(c)
}
// String returns color as string.
func (c Color) String() string {
if c.isHex() {
return string(c)
}
if c == DefaultColor {
return "-"
}
col := c.Color().TrueColor().Hex()
if col < 0 {
return "-"
}
return fmt.Sprintf("#%06x", col)
}
func (c Color) isHex() bool {
return len(c) == 7 && c[0] == '#'
}
// Color returns a view color.
func (c Color) Color() tcell.Color {
if c == DefaultColor {
return tcell.ColorDefault
}
return tcell.GetColor(string(c)).TrueColor()
}
// maxChromaForLH finds the maximum chroma at a given lightness and hue
// that stays within the sRGB gamut using binary search.
func maxChromaForLH(l, h float64) float64 {
lo, hi := 0.0, 0.4
for hi-lo > 0.001 {
mid := (lo + hi) / 2
col := colorful.OkLch(l, mid, h)
if col.IsValid() {
lo = mid
} else {
hi = mid
}
}
return lo
}
// chromaPreserveFactor controls how much original chroma to preserve during
// inversion. 0.5 means we try to keep 50% of the original chroma, which
// provides a good balance between color differentiation and L inversion.
const chromaPreserveFactor = 0.5
// closestLForChroma finds the L value closest to targetL that can support
// the given chroma at the given hue. It searches toward 0.5 first (where
// gamut is typically larger), then away from 0.5 if needed.
func closestLForChroma(targetL, c, h float64) float64 {
if maxChromaForLH(targetL, h) >= c {
return targetL
}
// Search toward 0.5 first (where gamut is larger)
if targetL < 0.5 {
for ll := targetL; ll <= 0.5; ll += 0.01 {
if maxChromaForLH(ll, h) >= c {
return ll
}
}
// Continue searching above 0.5 if needed
for ll := 0.51; ll <= 0.95; ll += 0.01 {
if maxChromaForLH(ll, h) >= c {
return ll
}
}
} else {
for ll := targetL; ll >= 0.5; ll -= 0.01 {
if maxChromaForLH(ll, h) >= c {
return ll
}
}
// Continue searching below 0.5 if needed
for ll := 0.49; ll >= 0.05; ll -= 0.01 {
if maxChromaForLH(ll, h) >= c {
return ll
}
}
}
return targetL
}
// InvertColor inverts the color's lightness in Oklch space while preserving
// chroma (saturation). For chromatic colors, L is adjusted toward 0.5 only
// as needed to preserve a fraction of the original chroma (set by
// chromaPreserveFactor), since the sRGB gamut has less room for chroma at
// extreme lightness values.
// Special colors (default, transparent) are returned unchanged.
func (c Color) InvertColor() Color {
if c == DefaultColor || c == TransparentColor || c == "" {
return c
}
tc := c.Color()
if tc == tcell.ColorDefault {
return c
}
hex := tc.TrueColor().Hex()
if hex < 0 {
return c
}
col, err := colorful.Hex(fmt.Sprintf("#%06x", hex))
if err != nil {
return c
}
L, C, h := col.OkLch()
// For achromatic colors, simply invert L
if C < 0.01 {
return NewColor(colorful.OkLch(1.0-L, 0, h).Clamped().Hex())
}
// For chromatic colors, find L closest to inverted that preserves
// at least chromaPreserveFactor of the original chroma
targetL := 1.0 - L
minC := C * chromaPreserveFactor
actualL := closestLForChroma(targetL, minC, h)
// Use as much of the original chroma as the gamut allows at actualL
maxC := maxChromaForLH(actualL, h)
actualC := C
if maxC < C {
actualC = maxC
}
inverted := colorful.OkLch(actualL, actualC, h).Clamped()
return NewColor(inverted.Hex())
}
================================================
FILE: internal/config/color_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"math"
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/tcell/v2"
"github.com/lucasb-eyer/go-colorful"
"github.com/stretchr/testify/assert"
)
func TestColors(t *testing.T) {
uu := map[string]struct {
cc []string
ee []tcell.Color
}{
"empty": {
ee: []tcell.Color{},
},
"default": {
cc: []string{"default"},
ee: []tcell.Color{tcell.ColorDefault},
},
"multi": {
cc: []string{
"default",
"transparent",
"blue",
"green",
},
ee: []tcell.Color{
tcell.ColorDefault,
tcell.ColorDefault,
tcell.ColorBlue.TrueColor(),
tcell.ColorGreen.TrueColor(),
},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cc := make(config.Colors, 0, len(u.cc))
for _, c := range u.cc {
cc = append(cc, config.NewColor(c))
}
assert.Equal(t, u.ee, cc.Colors())
})
}
}
func TestColorString(t *testing.T) {
uu := map[string]struct {
c string
e string
}{
"empty": {
e: "-",
},
"default": {
c: "default",
e: "-",
},
"transparent": {
c: "-",
e: "-",
},
"blue": {
c: "blue",
e: "#0000ff",
},
"lightgray": {
c: "lightgray",
e: "#d3d3d3",
},
"hex": {
c: "#00ff00",
e: "#00ff00",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := config.NewColor(u.c)
assert.Equal(t, u.e, c.String())
})
}
}
func TestColorToColor(t *testing.T) {
uu := map[string]struct {
c string
e tcell.Color
}{
"default": {
c: "default",
e: tcell.ColorDefault,
},
"transparent": {
c: "-",
e: tcell.ColorDefault,
},
"aqua": {
c: "aqua",
e: tcell.ColorAqua.TrueColor(),
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := config.NewColor(u.c)
assert.Equal(t, u.e, c.Color())
})
}
}
// getOkch returns c, h for a hex color string.
func getOkch(hex string) (c, h float64) {
col, err := colorful.Hex(hex)
if err != nil {
return 0, 0
}
_, c, h = col.OkLch()
return c, h
}
// huesEqual checks if two hues are equal within tolerance, handling wraparound.
func huesEqual(h1, h2, tolerance float64) bool {
diff := math.Abs(h1 - h2)
if diff > 180 {
diff = 360 - diff
}
return diff < tolerance
}
func TestInvertColor(t *testing.T) {
uu := map[string]struct {
c string
expect string
}{
"default": {
c: "default",
expect: "default",
},
"transparent": {
c: "-",
expect: "-",
},
"empty": {
c: "",
expect: "",
},
"black_to_white": {
c: "#000000",
expect: "#ffffff",
},
"white_to_black": {
c: "#ffffff",
expect: "#000000",
},
"red_to_dark": {
// L=0.628, C=0.258, h=29.2
c: "#ff0000",
expect: "#7e0000",
},
"blue_to_light": {
// L=0.452, C=0.313, h=264.1 -> L adjusted to 0.55 to preserve chroma
c: "#0000ff",
expect: "#1f5bff",
},
"green_to_dark": {
// L=0.866, C=0.295, h=142.5 -> L adjusted to 0.44 to preserve chroma
c: "#00ff00",
expect: "#006600",
},
"yellow_to_dark": {
// L=0.968, C=0.211, h=109.8 -> L adjusted to 0.49 to preserve chroma
c: "#ffff00",
expect: "#656501",
},
"cyan_to_dark": {
// L=0.905, C=0.155, h=194.8 -> L adjusted to 0.46 to preserve chroma
c: "#00ffff",
expect: "#016464",
},
"dark_gray_to_light": {
c: "#333333",
expect: "#989898",
},
"light_gray_to_dark": {
c: "#cccccc",
expect: "#0c0c0c",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := config.NewColor(u.c)
inverted := c.InvertColor()
assert.Equal(t, u.expect, string(inverted))
})
}
}
func TestInvertColorPreservesHue(t *testing.T) {
// Verify that hue is preserved during inversion for chromatic colors.
// Note: Hue preservation depends on the inverted color having sufficient chroma
// and not being clamped by go-colorful's Clamped() method. Colors near the
// gamut boundary may have hue shifts after clamping.
uu := map[string]struct {
c string
h float64 // expected hue
}{
"red": {
// L=0.628, C=0.258, h=29.2 -> inverted to L=0.372 with good chroma
c: "#ff0000",
h: 29.2,
},
"blue": {
// L=0.452, C=0.313, h=264.1 -> inverted to L=0.548 with good chroma
c: "#0000ff",
h: 264.1,
},
"purple": {
// L=0.420, C=0.161, h=328.4 -> mid-lightness, stable hue
c: "#800080",
h: 328.4,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
original := config.NewColor(u.c)
inverted := original.InvertColor()
_, hOrig := getOkch(u.c)
cInv, hInv := getOkch(string(inverted))
// Only check hue if inverted color has meaningful chroma (C > 0.05)
// Below this threshold, sRGB quantization causes hue instability
if cInv > 0.05 {
assert.True(t, huesEqual(hOrig, hInv, 1.0),
"hue should be preserved: original h=%.1f, inverted h=%.1f", hOrig, hInv)
}
})
}
}
func TestInvertGrayRoundTrip(t *testing.T) {
// Achromatic colors (grays) round-trip perfectly because they have no chroma
// to lose during gamut-constrained scaling.
colors := []string{
"#000000",
"#ffffff",
"#808080",
"#333333",
"#cccccc",
"#555555",
"#636363", // L=0.5 in Oklch
}
for _, c := range colors {
t.Run(c, func(t *testing.T) {
original := config.NewColor(c)
inverted := original.InvertColor()
reinverted := inverted.InvertColor()
assert.Equal(t, original.String(), string(reinverted),
"double inversion should return to original for achromatic colors")
})
}
}
func TestInvertColorSelfInverting(t *testing.T) {
// Colors with L=0.5 in Oklch invert to themselves.
// For achromatic grays, L=0.5 corresponds to approximately #636363 in sRGB.
selfInverting := []string{
"#636363",
}
for _, c := range selfInverting {
t.Run(c, func(t *testing.T) {
original := config.NewColor(c)
inverted := original.InvertColor()
assert.Equal(t, original.String(), string(inverted),
"color with L=0.5 should invert to itself")
})
}
}
func TestInvertColorOutOfGamut(t *testing.T) {
// These highly saturated colors would produce out-of-gamut results if we
// simply inverted L without adjustment. The chroma-preserving approach
// finds an L closer to 0.5 where sufficient chroma is available.
//
// For colors with very high L (yellow, cyan), the ideal inverted L would
// be very low where max chroma is tiny. Instead, L is adjusted toward 0.5
// to preserve chromaPreserveFactor (0.5) of the original chroma.
uu := map[string]struct {
c string
expect string
}{
"saturated_yellow": {
// L=0.968, C=0.211 -> L adjusted to 0.49 to preserve 50% chroma
c: "#ffff00",
expect: "#656501",
},
"saturated_cyan": {
// L=0.905, C=0.155 -> L adjusted to 0.46 to preserve 50% chroma
c: "#00ffff",
expect: "#016464",
},
"saturated_blue": {
// L=0.452, C=0.313 -> L adjusted to 0.55 to preserve chroma
c: "#0000ff",
expect: "#1f5bff",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
original := config.NewColor(u.c)
inverted := original.InvertColor()
// Verify the inverted color matches expected
invertedStr := string(inverted)
assert.Equal(t, u.expect, invertedStr)
// Verify the inverted color is valid hex
assert.Regexp(t, `^#[0-9a-f]{6}$`, invertedStr,
"inverted color should be valid hex")
// Verify it differs from original (these are not L=0.5 colors)
assert.NotEqual(t, original.String(), invertedStr,
"saturated color should not invert to itself")
// Only check hue preservation for colors with meaningful inverted chroma (C > 0.05)
// Below this threshold, sRGB quantization causes hue instability
cInv, hInv := getOkch(invertedStr)
if cInv > 0.05 {
_, hOrig := getOkch(u.c)
assert.True(t, huesEqual(hOrig, hInv, 1.0),
"hue should be preserved: original h=%.1f, inverted h=%.1f", hOrig, hInv)
}
})
}
}
================================================
FILE: internal/config/config.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd"
"gopkg.in/yaml.v3"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
// Config tracks K9s configuration options.
type Config struct {
K9s *K9s `yaml:"k9s" json:"k9s"`
conn client.Connection
settings data.KubeSettings
}
// NewConfig creates a new default config.
func NewConfig(ks data.KubeSettings) *Config {
return &Config{
settings: ks,
K9s: NewK9s(nil, ks),
}
}
// IsReadOnly returns true if K9s is running in read-only mode.
func (c *Config) IsReadOnly() bool {
return c.K9s.IsReadOnly()
}
// ActiveClusterName returns the corresponding cluster name.
func (c *Config) ActiveClusterName(contextName string) (string, error) {
ct, err := c.settings.GetContext(contextName)
if err != nil {
return "", err
}
return ct.Cluster, nil
}
// ContextHotkeysPath returns a context specific hotkeys file spec.
func (c *Config) ContextHotkeysPath() string {
ct, err := c.K9s.ActiveContext()
if err != nil {
return ""
}
return AppContextHotkeysFile(ct.ClusterName, c.K9s.activeContextName)
}
// 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.GetClusterName(), c.K9s.activeContextName)
}
// ContextPluginsPath returns a context specific plugins file spec.
func (c *Config) ContextPluginsPath() (string, error) {
ct, err := c.K9s.ActiveContext()
if err != nil {
return "", err
}
return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName), nil
}
func setK8sTimeout(flags *genericclioptions.ConfigFlags, d time.Duration) {
v := d.String()
flags.Timeout = &v
}
// Refine the configuration based on cli args.
func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error {
if flags == nil {
return nil
}
if !isStringSet(flags.Timeout) {
if d, err := time.ParseDuration(c.K9s.APIServerTimeout); err == nil {
setK8sTimeout(flags, d)
} else {
setK8sTimeout(flags, client.DefaultCallTimeoutDuration)
}
}
if isStringSet(flags.Context) {
if _, err := c.K9s.ActivateContext(*flags.Context); err != nil {
return fmt.Errorf("k8sflags. unable to activate context %q: %w", *flags.Context, err)
}
} else {
n, err := cfg.CurrentContextName()
if err != nil {
return fmt.Errorf("unable to retrieve kubeconfig current context %q: %w", n, err)
}
_, err = c.K9s.ActivateContext(n)
if err != nil {
return fmt.Errorf("unable to activate context %q: %w", n, err)
}
}
slog.Debug("Using active context", slogs.Context, c.K9s.ActiveContextName())
var ns string
switch {
case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces):
ns = client.NamespaceAll
c.ResetActiveView()
case isStringSet(flags.Namespace):
ns = *flags.Namespace
c.ResetActiveView()
default:
nss, err := c.K9s.ActiveContextNamespace()
if err != nil {
return err
}
ns = nss
}
if ns == "" {
ns = client.DefaultNamespace
}
if err := c.SetActiveNamespace(ns); err != nil {
return err
}
return data.EnsureDirPath(c.K9s.AppScreenDumpDir(), data.DefaultDirMod)
}
// Reset resets the context to the new current context/cluster.
func (c *Config) Reset() {
c.K9s.Reset()
}
func (c *Config) ActivateContext(n string) (*data.Context, error) {
ct, err := c.K9s.ActivateContext(n)
if err != nil {
return nil, fmt.Errorf("set current context failed. %w", err)
}
return ct, nil
}
// 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 {
ns, err := c.K9s.ActiveContextNamespace()
if err != nil {
slog.Error("Unable to assert active namespace. Using default", slogs.Error, err)
ns = client.DefaultNamespace
}
return ns
}
// FavNamespaces returns fav namespaces in the current context.
func (c *Config) FavNamespaces() []string {
ct, err := c.K9s.ActiveContext()
if err != nil {
return nil
}
ct.Validate(c.conn, c.K9s.getActiveContextName(), ct.ClusterName)
return ct.Namespace.Favorites
}
// SetActiveNamespace set the active namespace in the current context.
func (c *Config) SetActiveNamespace(ns string) error {
if ns == client.NotNamespaced {
slog.Debug("No namespace given. skipping!", slogs.Namespace, ns)
return nil
}
ct, err := c.K9s.ActiveContext()
if err != nil {
return err
}
return ct.Namespace.SetActive(ns, c.settings)
}
// ActiveView returns the active view in the current context.
func (c *Config) ActiveView() string {
ct, err := c.K9s.ActiveContext()
if err != nil {
return data.DefaultView
}
v := ct.View.Active
if c.K9s.manualCommand != nil && *c.K9s.manualCommand != "" {
v = *c.K9s.manualCommand
// We reset the manualCommand property because
// the command-line switch should only be considered once,
// on startup.
*c.K9s.manualCommand = ""
}
return v
}
func (c *Config) ResetActiveView() {
if isStringSet(c.K9s.manualCommand) {
return
}
v := c.ActiveView()
if v == "" {
return
}
p := cmd.NewInterpreter(v)
if p.HasNS() {
c.SetActiveView(p.Cmd())
}
}
// SetActiveView sets current context active view.
func (c *Config) SetActiveView(view string) {
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.conn
}
// SetConnection set an api server connection.
func (c *Config) SetConnection(conn client.Connection) {
c.conn = conn
if conn != nil {
c.K9s.resetConnection(conn)
}
}
func (c *Config) ActiveContextName() string {
return c.K9s.activeContextName
}
func (c *Config) Merge(c1 *Config) {
c.K9s.Merge(c1.K9s)
}
// Load loads K9s configuration from file.
func (c *Config) Load(path string, force bool) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
if err := c.Save(force); err != nil {
return err
}
}
bb, err := os.ReadFile(path)
if err != nil {
return err
}
var errs error
if err := data.JSONValidator.Validate(json.K9sSchema, bb); err != nil {
errs = errors.Join(errs, fmt.Errorf("k9s config file %q load failed:\n%w", path, err))
}
var cfg Config
if err := yaml.Unmarshal(bb, &cfg); err != nil {
errs = errors.Join(errs, fmt.Errorf("main config.yaml load failed: %w", err))
}
c.Merge(&cfg)
return errs
}
// Save configuration to disk.
func (c *Config) Save(force bool) error {
contextName := c.K9s.ActiveContextName()
clusterName, err := c.ActiveClusterName(contextName)
if err != nil {
return fmt.Errorf("unable to locate associated cluster for context %q: %w", contextName, err)
}
c.Validate(contextName, clusterName)
if err := c.K9s.Save(contextName, clusterName, force); err != nil {
return err
}
if _, err := os.Stat(AppConfigFile); errors.Is(err, fs.ErrNotExist) {
return c.SaveFile(AppConfigFile)
}
return nil
}
// SaveFile K9s configuration to disk.
func (c *Config) SaveFile(path string) error {
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
return err
}
if err := data.SaveYAML(path, c); err != nil {
slog.Error("Unable to save K9s config file", slogs.Error, err)
return err
}
slog.Info("[CONFIG] Saving K9s config to disk", slogs.Path, path)
return nil
}
// Validate the configuration.
func (c *Config) Validate(contextName, clusterName string) {
if c.K9s == nil {
c.K9s = NewK9s(c.conn, c.settings)
}
c.K9s.Validate(c.conn, contextName, clusterName)
}
// Dump for debug...
func (c *Config) Dump(msg string) {
ct, err := c.K9s.ActiveContext()
if err == nil {
bb, _ := yaml.Marshal(ct)
fmt.Printf("Dump: %q\n%s\n", msg, string(bb))
} else {
fmt.Println("BOOM!", err)
}
}
================================================
FILE: internal/config/config_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"testing"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
func TestConfigSave(t *testing.T) {
config.AppConfigFile = "/tmp/k9s-test/k9s.yaml"
sd := "/tmp/k9s-test/screen-dumps"
cl, ct := "cl-1", "ct-1-1"
_ = os.RemoveAll(("/tmp/k9s-test"))
uu := map[string]struct {
ct string
flags *genericclioptions.ConfigFlags
k9sFlags *config.Flags
}{
"happy": {
ct: "ct-1-1",
flags: &genericclioptions.ConfigFlags{
ClusterName: &cl,
Context: &ct,
},
k9sFlags: &config.Flags{
ScreenDumpDir: &sd,
},
},
}
for k, u := range uu {
xdg.Reload()
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, err := c.K9s.ActivateContext(u.ct)
require.NoError(t, err)
if u.flags != nil {
c.K9s.Override(u.k9sFlags)
require.NoError(t, c.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags)))
}
require.NoError(t, c.Save(true))
bb, err := os.ReadFile(config.AppConfigFile)
require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/default.yaml")
require.NoError(t, err)
assert.Equal(t, string(ee), string(bb))
})
}
}
func TestSetActiveView(t *testing.T) {
var (
cfgFile = "testdata/kubes/test.yaml"
view = "dp"
)
uu := map[string]struct {
ct string
flags *genericclioptions.ConfigFlags
k9sFlags *config.Flags
view string
e string
}{
"empty": {
view: data.DefaultView,
e: data.DefaultView,
},
"not-exists": {
ct: "fred",
view: data.DefaultView,
e: data.DefaultView,
},
"happy": {
ct: "ct-1-1",
view: "xray",
e: "xray",
},
"cli-override": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
},
k9sFlags: &config.Flags{
Command: &view,
},
ct: "ct-1-1",
view: "xray",
e: "dp",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
c.SetActiveView(u.view)
assert.Equal(t, u.e, c.ActiveView())
})
}
}
func TestActiveContextName(t *testing.T) {
var (
cfgFile = "testdata/kubes/test.yaml"
ct2 = "ct-1-2"
)
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
k9sFlags *config.Flags
ct string
e string
}{
"empty": {},
"happy": {
ct: "ct-1-1",
e: "ct-1-1",
},
"cli-override": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Context: &ct2,
},
k9sFlags: &config.Flags{},
ct: "ct-1-1",
e: "ct-1-2",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
assert.Equal(t, u.e, c.ActiveContextName())
})
}
}
func TestActiveView(t *testing.T) {
var (
cfgFile = "testdata/kubes/test.yaml"
view = "dp"
)
uu := map[string]struct {
ct string
flags *genericclioptions.ConfigFlags
k9sFlags *config.Flags
e string
}{
"empty": {
e: data.DefaultView,
},
"not-exists": {
ct: "fred",
e: data.DefaultView,
},
"happy": {
ct: "ct-1-1",
e: data.DefaultView,
},
"happy1": {
ct: "ct-1-2",
e: data.DefaultView,
},
"cli-override": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
},
k9sFlags: &config.Flags{
Command: &view,
},
e: "dp",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
if u.flags != nil {
require.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags)))
c.K9s.Override(u.k9sFlags)
}
assert.Equal(t, u.e, c.ActiveView())
})
}
}
func TestFavNamespaces(t *testing.T) {
uu := map[string]struct {
ct string
e []string
}{
"empty": {},
"not-exists": {
ct: "fred",
},
"happy": {
ct: "ct-1-1",
e: []string{client.DefaultNamespace},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
assert.Equal(t, u.e, c.FavNamespaces())
})
}
}
func TestContextAliasesPath(t *testing.T) {
uu := map[string]struct {
ct string
e string
}{
"empty": {},
"not-exists": {
ct: "fred",
},
"happy": {
ct: "ct-1-1",
e: "/tmp/test/cl-1/ct-1-1/aliases.yaml",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
assert.Equal(t, u.e, c.ContextAliasesPath())
})
}
}
func TestContextPluginsPath(t *testing.T) {
uu := map[string]struct {
ct, e string
err error
}{
"empty": {
err: errors.New(`no context found for: ""`),
},
"happy": {
ct: "ct-1-1",
e: "/tmp/test/cl-1/ct-1-1/plugins.yaml",
},
"not-exists": {
ct: "fred",
err: errors.New(`no context found for: "fred"`),
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t)
_, _ = c.K9s.ActivateContext(u.ct)
s, err := c.ContextPluginsPath()
if err != nil {
assert.Equal(t, u.err, err)
}
assert.Equal(t, u.e, s)
})
}
}
func TestConfigLoader(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/configs/k9s.yaml",
},
"toast": {
f: "testdata/configs/k9s_toast.yaml",
err: `k9s config file "testdata/configs/k9s_toast.yaml" load failed:
Additional property disablePodCounts is not allowed
Additional property shellPods is not allowed
Invalid type. Expected: boolean, given: string`,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := config.NewConfig(nil)
if err := cfg.Load(u.f, true); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestConfigActivateContext(t *testing.T) {
uu := map[string]struct {
cl, ct string
err string
}{
"happy": {
ct: "ct-1-2",
cl: "cl-1",
},
"toast": {
ct: "fred",
cl: "cl-1",
err: `set current context failed. no context found for: "fred"`,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := mock.NewMockConfig(t)
ct, err := cfg.ActivateContext(u.ct)
if err != nil {
assert.Equal(t, u.err, err.Error())
return
}
require.NoError(t, err)
assert.Equal(t, u.cl, ct.ClusterName)
})
}
}
func TestConfigCurrentContext(t *testing.T) {
var (
cfgFile = "testdata/kubes/test.yaml"
ct2 = "ct-1-2"
)
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
err error
context string
cluster string
namespace string
}{
"override-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Context: &ct2,
},
cluster: "cl-1",
context: "ct-1-2",
namespace: "ns-2",
},
"use-current-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: client.DefaultNamespace,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := mock.NewMockConfig(t)
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
require.NoError(t, err)
ct, err := cfg.CurrentContext()
require.NoError(t, err)
assert.Equal(t, u.cluster, ct.ClusterName)
assert.Equal(t, u.namespace, ct.Namespace.Active)
})
}
}
func TestConfigRefine(t *testing.T) {
var (
cfgFile = "testdata/kubes/test.yaml"
cl1 = "cl-1"
ct2 = "ct-1-2"
ns1, ns2, nsx = "ns-1", "ns-2", "ns-x"
trueVal = true
)
uu := map[string]struct {
flags *genericclioptions.ConfigFlags
k9sFlags *config.Flags
err string
context string
cluster string
namespace string
}{
"no-override": {
namespace: "default",
},
"override-cluster": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
ClusterName: &cl1,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: client.DefaultNamespace,
},
"override-cluster-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
ClusterName: &cl1,
Context: &ct2,
},
cluster: "cl-1",
context: "ct-1-2",
namespace: "ns-2",
},
"override-bad-cluster": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
ClusterName: &ns1,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: client.DefaultNamespace,
},
"override-ns": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Namespace: &ns2,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: "ns-2",
},
"all-ns": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Namespace: &ns2,
},
k9sFlags: &config.Flags{
AllNamespaces: &trueVal,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: client.NamespaceAll,
},
"override-bad-ns": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Namespace: &nsx,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: "ns-x",
},
"override-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Context: &ct2,
},
cluster: "cl-1",
context: "ct-1-2",
namespace: "ns-2",
},
"override-bad-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
Context: &ns1,
},
err: `k8sflags. unable to activate context "ns-1": no context found for: "ns-1"`,
},
"use-current-context": {
flags: &genericclioptions.ConfigFlags{
KubeConfig: &cfgFile,
},
cluster: "cl-1",
context: "ct-1-1",
namespace: client.DefaultNamespace,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := mock.NewMockConfig(t)
err := cfg.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags))
if err != nil {
assert.Equal(t, u.err, err.Error())
} else {
require.NoError(t, err)
assert.Equal(t, u.context, cfg.K9s.ActiveContextName())
assert.Equal(t, u.namespace, cfg.ActiveNamespace())
}
})
}
}
func TestConfigValidate(t *testing.T) {
cfg := mock.NewMockConfig(t)
cfg.SetConnection(mock.NewMockConnection())
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Validate("ct-1-1", "cl-1")
}
func TestConfigLoad(t *testing.T) {
cfg := mock.NewMockConfig(t)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.InDelta(t, 2.0, cfg.K9s.RefreshRate, 0.001)
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
}
func TestConfigLoadCrap(t *testing.T) {
cfg := mock.NewMockConfig(t)
assert.Error(t, cfg.Load("testdata/configs/k9s_not_there.yaml", true))
}
func TestConfigSaveFile(t *testing.T) {
cfg := mock.NewMockConfig(t)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.K9s.RefreshRate = 100
cfg.K9s.GPUVendors = map[string]string{
"bozo": "bozo/gpu.com",
}
cfg.K9s.APIServerTimeout = "30s"
cfg.K9s.ReadOnly = true
cfg.K9s.Logger.TailCount = 500
cfg.K9s.Logger.BufferSize = 800
cfg.K9s.UI.UseFullGVRTitle = true
cfg.Validate("ct-1-1", "cl-1")
path := filepath.Join(os.TempDir(), "k9s.yaml")
require.NoError(t, cfg.SaveFile(path))
raw, err := os.ReadFile(path)
require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/expected.yaml")
require.NoError(t, err)
assert.Equal(t, string(ee), string(raw))
}
func TestConfigReset(t *testing.T) {
cfg := mock.NewMockConfig(t)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Reset()
cfg.Validate("ct-1-1", "cl-1")
path := filepath.Join(os.TempDir(), "k9s.yaml")
require.NoError(t, cfg.SaveFile(path))
bb, err := os.ReadFile(path)
require.NoError(t, err)
ee, err := os.ReadFile("testdata/configs/k9s.yaml")
require.NoError(t, err)
assert.Equal(t, string(ee), string(bb))
}
// Helpers...
func TestSetup(t *testing.T) {
m.RegisterMockTestingT(t)
m.RegisterMockFailHandler(func(m string, i ...int) {
fmt.Println("Boom!", m, i)
})
}
================================================
FILE: internal/config/data/config.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"fmt"
"io"
"sync"
"github.com/derailed/k9s/internal/client"
"gopkg.in/yaml.v3"
"k8s.io/client-go/tools/clientcmd/api"
)
// Config tracks a context configuration.
type Config struct {
Context *Context `yaml:"k9s"`
mx sync.RWMutex
}
// NewConfig returns a new config.
func NewConfig(ct *api.Context) *Config {
return &Config{
Context: NewContextFromConfig(ct),
}
}
// Merge merges configs and updates receiver.
func (c *Config) Merge(c1 *Config) {
if c1 == nil {
return
}
if c.Context != nil && c1.Context != nil {
c.Context.merge(c1.Context)
}
}
// Validate ensures config is in norms.
func (c *Config) Validate(conn client.Connection, contextName, clusterName string) {
c.mx.Lock()
defer c.mx.Unlock()
if c.Context == nil {
c.Context = NewContext()
}
c.Context.Validate(conn, contextName, clusterName)
}
// Dump used for debugging.
func (c *Config) Dump(w io.Writer) {
bb, _ := yaml.Marshal(&c)
_, _ = fmt.Fprintf(w, "%s\n", string(bb))
}
================================================
FILE: internal/config/data/context.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"os"
"sync"
"github.com/derailed/k9s/internal/client"
"k8s.io/client-go/tools/clientcmd/api"
)
// Context tracks K9s context configuration.
type Context struct {
ClusterName string `yaml:"cluster,omitempty"`
ReadOnly *bool `yaml:"readOnly,omitempty"`
Skin string `yaml:"skin,omitempty"`
Namespace *Namespace `yaml:"namespace"`
View *View `yaml:"view"`
FeatureGates FeatureGates `yaml:"featureGates"`
Proxy *Proxy `yaml:"proxy"`
mx sync.RWMutex
}
// NewContext creates a new cluster configuration.
func NewContext() *Context {
return &Context{
Namespace: NewNamespace(),
View: NewView(),
FeatureGates: NewFeatureGates(),
}
}
// NewContextFromConfig returns a config based on a kubecontext.
func NewContextFromConfig(cfg *api.Context) *Context {
ct := NewContext()
ct.Namespace, ct.ClusterName = NewActiveNamespace(cfg.Namespace), cfg.Cluster
return ct
}
// NewContextFromKubeConfig returns a new instance based on kubesettings or an error.
func NewContextFromKubeConfig(ks KubeSettings) (*Context, error) {
ct, err := ks.CurrentContext()
if err != nil {
return nil, err
}
return NewContextFromConfig(ct), nil
}
func (c *Context) merge(old *Context) {
if old == nil || old.Namespace == nil {
return
}
if c.Namespace == nil {
c.Namespace = NewNamespace()
}
c.Namespace.merge(old.Namespace)
}
func (c *Context) GetClusterName() string {
c.mx.RLock()
defer c.mx.RUnlock()
return c.ClusterName
}
// Validate ensures a context config is tip top.
func (c *Context) Validate(conn client.Connection, _, clusterName string) {
c.mx.Lock()
defer c.mx.Unlock()
c.ClusterName = clusterName
if b := os.Getenv(envFGNodeShell); b != "" {
c.FeatureGates.NodeShell = defaultFGNodeShell()
}
if c.Namespace == nil {
c.Namespace = NewNamespace()
}
c.Namespace.Validate(conn)
if c.View == nil {
c.View = NewView()
}
c.View.Validate()
}
================================================
FILE: internal/config/data/context_int_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_contextMerge(t *testing.T) {
uu := map[string]struct {
c1, c2, e *Context
}{
"empty": {},
"nil": {
c1: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3"},
},
},
e: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3"},
},
},
},
"deltas": {
c1: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3"},
},
},
c2: &Context{
Namespace: &Namespace{
Active: "ns10",
Favorites: []string{"ns10", "ns11", "ns12"},
},
},
e: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3", "ns10", "ns11", "ns12"},
},
},
},
"deltas-locked": {
c1: &Context{
Namespace: &Namespace{
Active: "ns1",
LockFavorites: true,
Favorites: []string{"ns1", "ns2", "ns3"},
},
},
c2: &Context{
Namespace: &Namespace{
Active: "ns10",
Favorites: []string{"ns10", "ns11", "ns12"},
},
},
e: &Context{
Namespace: &Namespace{
Active: "ns1",
LockFavorites: true,
Favorites: []string{"ns1", "ns2", "ns3"},
},
},
},
"no-namespace": {
c1: NewContext(),
c2: &Context{},
e: NewContext(),
},
"too-many-favs": {
c1: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3", "ns4", "ns5", "ns6", "ns7", "ns8", "ns9"},
},
},
c2: &Context{
Namespace: &Namespace{
Active: "ns10",
Favorites: []string{"ns10", "ns11", "ns12"},
},
},
e: &Context{
Namespace: &Namespace{
Active: "ns1",
Favorites: []string{"ns1", "ns2", "ns3", "ns4", "ns5", "ns6", "ns7", "ns8", "ns9"},
},
},
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
u.c1.merge(u.c2)
assert.Equal(t, u.e, u.c1)
})
}
}
================================================
FILE: internal/config/data/context_test.go
================================================
// 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(), "ct-1", "cl-1")
assert.Equal(t, data.DefaultView, c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
assert.Len(t, c.Namespace.Favorites, 1)
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}
func TestClusterValidateEmpty(t *testing.T) {
c := data.NewContext()
c.Validate(mock.NewMockConnection(), "ct-1", "cl-1")
assert.Equal(t, data.DefaultView, c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
assert.Len(t, c.Namespace.Favorites, 1)
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}
================================================
FILE: internal/config/data/dir.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"sync"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v3"
"k8s.io/client-go/tools/clientcmd/api"
)
// Dir tracks context configurations.
type Dir struct {
root string
mx sync.Mutex
}
// NewDir returns a new instance.
func NewDir(root string) *Dir {
return &Dir{
root: root,
}
}
// Load loads context configuration.
func (d *Dir) Load(contextName string, ct *api.Context) (*Config, error) {
if ct == nil {
return nil, errors.New("api.Context must not be nil")
}
path := filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, contextName), MainConfigFile)
slog.Debug("[CONFIG] Loading context config from disk", slogs.Path, path, slogs.Cluster, ct.Cluster, slogs.Context, contextName)
f, err := os.Stat(path)
if errors.Is(err, fs.ErrPermission) {
return nil, err
}
if errors.Is(err, fs.ErrNotExist) || (f != nil && f.Size() == 0) {
slog.Debug("Context config not found! Generating..", slogs.Path, path)
return d.genConfig(path, ct)
}
if err != nil {
return nil, err
}
return d.loadConfig(path)
}
func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) {
cfg := NewConfig(ct)
if err := d.Save(path, cfg); err != nil {
return nil, err
}
return cfg, nil
}
func (d *Dir) Save(path string, c *Config) error {
if cfg, err := d.loadConfig(path); err == nil {
c.Merge(cfg)
}
d.mx.Lock()
defer d.mx.Unlock()
if err := EnsureDirPath(path, DefaultDirMod); err != nil {
return err
}
return SaveYAML(path, c)
}
func (d *Dir) loadConfig(path string) (*Config, error) {
d.mx.Lock()
defer d.mx.Unlock()
bb, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if err := JSONValidator.Validate(json.ContextSchema, bb); err != nil {
slog.Warn("Validation failed. Please update your config and restart!",
slogs.Path, path,
slogs.Error, err,
)
}
var cfg Config
if err := yaml.Unmarshal(bb, &cfg); err != nil {
return nil, fmt.Errorf("context-config yaml load failed: %w\n%s", err, string(bb))
}
return &cfg, nil
}
================================================
FILE: internal/config/data/dir_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data_test
import (
"log/slog"
"os"
"strings"
"testing"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
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"),
},
"non-sanitized-path": {
dir: "/tmp/data/k9s",
flags: makeFlags("arn:aws:eks:eu-central-1:xxx:cluster/fred-blee", "fred-blee"),
cfg: mustLoadConfig("testdata/configs/aws_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 {
require.NoError(t, mock.EnsureDir(u.dir))
}
d := data.NewDir(u.dir)
ct, err := ks.CurrentContext()
require.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
}
================================================
FILE: internal/config/data/feature_gate.go
================================================
// 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{}
}
================================================
FILE: internal/config/data/helpers.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"bytes"
"errors"
"io/fs"
"os"
"path/filepath"
"regexp"
"gopkg.in/yaml.v3"
)
const envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL"
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
// SanitizeContextSubpath ensure cluster/context produces a valid path.
func SanitizeContextSubpath(cluster, context string) string {
return filepath.Join(SanitizeFileName(cluster), SanitizeFileName(context))
}
// SanitizeFileName ensure file spec is valid.
func SanitizeFileName(name string) string {
return invalidPathCharsRX.ReplaceAllString(name, "-")
}
func defaultFGNodeShell() bool {
if a := os.Getenv(envFGNodeShell); a != "" {
return a == "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); errors.Is(err, fs.ErrNotExist) {
if e := os.MkdirAll(path, mod); e != nil {
return e
}
}
return nil
}
// WriteYAML writes a yaml file to bytes.
func WriteYAML(content any) ([]byte, error) {
buff := bytes.NewBuffer(nil)
ec := yaml.NewEncoder(buff)
ec.SetIndent(2)
if err := ec.Encode(content); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
// SaveYAML writes a yaml file to disk.
func SaveYAML(path string, content any) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, DefaultFileMod)
if err != nil {
return err
}
ec := yaml.NewEncoder(f)
ec.SetIndent(2)
return ec.Encode(content)
}
================================================
FILE: internal/config/data/helpers_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data_test
import (
"os"
"path/filepath"
"slices"
"testing"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSanitizeFileName(t *testing.T) {
uu := map[string]struct {
file, e string
}{
"empty": {},
"plain": {
file: "bumble-bee-tuna",
e: "bumble-bee-tuna",
},
"slash": {
file: "bumble/bee/tuna",
e: "bumble-bee-tuna",
},
"column": {
file: "bumble::bee:tuna",
e: "bumble-bee-tuna",
},
"eks": {
file: "arn:aws:eks:us-east-1:123456789:cluster/us-east-1-app-dev-common-eks",
e: "arn-aws-eks-us-east-1-123456789-cluster-us-east-1-app-dev-common-eks",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, data.SanitizeFileName(u.file))
})
}
}
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, slices.Contains(u.list, u.item))
}
}
func TestEnsureDirPathNone(t *testing.T) {
const mod = 0744
dir := filepath.Join(os.TempDir(), "k9s-test")
_ = os.Remove(dir)
path := filepath.Join(dir, "duh.yaml")
require.NoError(t, data.EnsureDirPath(path, mod))
p, err := os.Stat(dir)
require.NoError(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}
func TestEnsureDirPathNoOpt(t *testing.T) {
var mod os.FileMode = 0744
dir := filepath.Join(os.TempDir(), "k9s-test")
require.NoError(t, os.RemoveAll(dir))
require.NoError(t, os.Mkdir(dir, mod))
path := filepath.Join(dir, "duh.yaml")
require.NoError(t, data.EnsureDirPath(path, mod))
p, err := os.Stat(dir)
require.NoError(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}
================================================
FILE: internal/config/data/ns.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"log/slog"
"slices"
"sync"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
)
const (
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
MaxFavoritesNS = 9
)
// Namespace tracks active and favorites namespaces.
type Namespace struct {
Active string `yaml:"active"`
LockFavorites bool `yaml:"lockFavorites"`
Favorites []string `yaml:"favorites"`
mx sync.RWMutex
}
// NewNamespace create a new namespace configuration.
func NewNamespace() *Namespace {
return NewActiveNamespace(client.DefaultNamespace)
}
func NewActiveNamespace(n string) *Namespace {
if n == client.BlankNamespace {
n = client.DefaultNamespace
}
return &Namespace{
Active: n,
Favorites: []string{client.DefaultNamespace},
}
}
func (n *Namespace) merge(old *Namespace) {
n.mx.Lock()
defer n.mx.Unlock()
if n.LockFavorites {
return
}
for _, fav := range old.Favorites {
if slices.Contains(n.Favorites, fav) {
continue
}
n.Favorites = append(n.Favorites, fav)
}
n.trimFavNs()
}
// Validate validates a namespace is setup correctly.
func (n *Namespace) Validate(conn client.Connection) {
n.mx.RLock()
defer n.mx.RUnlock()
if conn == nil || !conn.IsValidNamespace(n.Active) {
return
}
for _, ns := range n.Favorites {
if !conn.IsValidNamespace(ns) {
slog.Debug("Invalid favorite found",
slogs.Namespace, ns,
slogs.AllNS, n.isAllNamespaces(),
)
n.rmFavNS(ns)
}
}
n.trimFavNs()
}
// SetActive set the active namespace.
func (n *Namespace) SetActive(ns string, _ KubeSettings) error {
if n == nil {
n = NewActiveNamespace(ns)
}
n.mx.Lock()
defer n.mx.Unlock()
if ns == client.BlankNamespace {
ns = client.NamespaceAll
}
n.Active = ns
if ns != "" && !n.LockFavorites {
n.addFavNS(ns)
}
return nil
}
func (n *Namespace) isAllNamespaces() bool {
return n.Active == client.NamespaceAll || n.Active == ""
}
func (n *Namespace) addFavNS(ns string) {
if slices.Contains(n.Favorites, ns) {
return
}
nfv := make([]string, 0, MaxFavoritesNS)
nfv = append(nfv, ns)
for i := range n.Favorites {
if i+1 < MaxFavoritesNS {
nfv = append(nfv, n.Favorites[i])
}
}
n.Favorites = nfv
}
func (n *Namespace) rmFavNS(ns string) {
if n.LockFavorites {
return
}
victim := -1
for i, f := range n.Favorites {
if f == ns {
victim = i
break
}
}
if victim < 0 {
return
}
n.Favorites = append(n.Favorites[:victim], n.Favorites[victim+1:]...)
}
func (n *Namespace) trimFavNs() {
if len(n.Favorites) > MaxFavoritesNS {
slog.Debug("Number of favorite exceeds hard limit. Trimming.", slogs.Max, MaxFavoritesNS)
n.Favorites = n.Favorites[:MaxFavoritesNS]
}
}
================================================
FILE: internal/config/data/ns_test.go
================================================
// 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"
"github.com/stretchr/testify/require"
)
func TestNSValidate(t *testing.T) {
ns := data.NewNamespace()
ns.Validate(mock.NewMockConnection())
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())
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())
assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"default"}, ns.Favorites)
}
func TestNsValidateMaxNS(t *testing.T) {
allNS := []string{"ns9", "ns8", "ns7", "ns6", "ns5", "ns4", "ns3", "ns2", "ns1", "all", "default"}
ns := data.NewNamespace()
ns.Favorites = allNS
ns.Validate(mock.NewMockConnection())
assert.Len(t, ns.Favorites, data.MaxFavoritesNS)
}
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)
require.NoError(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())
assert.Equal(t, []string{"default", "fred"}, ns.Favorites)
}
================================================
FILE: internal/config/data/proxy.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
// Proxy tracks a context's proxy configuration.
type Proxy struct {
Address string `yaml:"address"`
}
================================================
FILE: internal/config/data/testdata/configs/aws_ct.yaml
================================================
k9s:
cluster: arn:aws:eks:eu-central-1:xxx:cluster/fred-blee
namespace:
active: default
lockFavorites: false
favorites:
- default
view:
active: po
featureGates:
nodeShell: false
================================================
FILE: internal/config/data/testdata/configs/ct-1-1.yaml
================================================
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
================================================
FILE: internal/config/data/testdata/configs/ct-1-2.yaml
================================================
k9s:
cluster: cl-1
skin: in_the_navy
readOnly: true
namespace:
active: default
lockFavorites: false
favorites:
- default
view:
active: po
featureGates:
nodeShell: false
================================================
FILE: internal/config/data/testdata/configs/ct-2-1.yaml
================================================
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
================================================
FILE: internal/config/data/testdata/configs/def_ct.yaml
================================================
k9s:
cluster: cl-test
namespace:
active: default
lockFavorites: false
favorites:
- default
view:
active: po
featureGates:
nodeShell: false
================================================
FILE: internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml
================================================
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
================================================
FILE: internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml
================================================
k9s:
cluster: cl-1
skin: in_the_navy
readOnly: true
namespace:
active: default
lockFavorites: false
favorites:
- default
view:
active: po
featureGates:
nodeShell: false
================================================
FILE: internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml
================================================
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
================================================
FILE: internal/config/data/types.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
import (
"net/http"
"net/url"
"os"
"github.com/derailed/k9s/internal/config/json"
"k8s.io/client-go/tools/clientcmd/api"
)
// JSONValidator validate yaml configurations.
var JSONValidator = json.NewValidator()
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)
// CurrentNamespaceName 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)
// SetProxy sets the proxy for the active context, if present
SetProxy(proxy func(*http.Request) (*url.URL, error))
}
================================================
FILE: internal/config/data/view.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data
const DefaultView = "po"
// View tracks view configuration options.
type View struct {
Active string `yaml:"active"`
}
// NewView creates a new view configuration.
func NewView() *View {
return &View{Active: DefaultView}
}
// Validate a view configuration.
func (v *View) Validate() {
if v.Active == "" {
v.Active = DefaultView
}
}
================================================
FILE: internal/config/data/view_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package data_test
import (
"testing"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
)
func TestViewValidate(t *testing.T) {
v := data.NewView()
v.Validate()
assert.Equal(t, "po", v.Active)
v.Active = "fred"
v.Validate()
assert.Equal(t, "fred", v.Active)
}
func TestViewValidateBlank(t *testing.T) {
var v data.View
v.Validate()
assert.Equal(t, "po", v.Active)
}
================================================
FILE: internal/config/files.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
_ "embed"
"errors"
"io/fs"
"log/slog"
"os"
"path/filepath"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs"
)
const (
// K9sEnvConfigDir represents k9s configuration dir env var.
K9sEnvConfigDir = "K9S_CONFIG_DIR"
// K9sEnvLogsDir represents k9s logs dir env var.
K9sEnvLogsDir = "K9S_LOGS_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
)
// InitLogLoc initializes K9s logs location.
func InitLogLoc() error {
var appLogDir string
switch {
case isEnvSet(K9sEnvLogsDir):
appLogDir = os.Getenv(K9sEnvLogsDir)
case isEnvSet(K9sEnvConfigDir):
tmpDir, err := UserTmpDir()
if err != nil {
return err
}
appLogDir = tmpDir
default:
var err error
appLogDir, err = xdg.StateFile(AppName)
if err != nil {
return err
}
}
if err := data.EnsureFullPath(appLogDir, data.DefaultDirMod); err != nil {
return err
}
AppLogFile = filepath.Join(appLogDir, K9sLogsFile)
return nil
}
// InitLocs initializes k9s artifacts locations.
func InitLocs() error {
if isEnvSet(K9sEnvConfigDir) {
return initK9sEnvLocs()
}
return initXDGLocs()
}
func initK9sEnvLocs() error {
AppConfigDir = os.Getenv(K9sEnvConfigDir)
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 {
slog.Warn("Unable to create screen-dumps dir", slogs.Dir, AppDumpsDir, slogs.Error, err)
}
AppBenchmarksDir = filepath.Join(AppConfigDir, "benchmarks")
if err := data.EnsureFullPath(AppBenchmarksDir, data.DefaultDirMod); err != nil {
slog.Warn("Unable to create benchmarks dir",
slogs.Dir, AppBenchmarksDir,
slogs.Error, err,
)
}
AppSkinsDir = filepath.Join(AppConfigDir, "skins")
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil {
slog.Warn("Unable to create skins dir",
slogs.Dir, AppSkinsDir,
slogs.Error, err,
)
}
AppContextsDir = filepath.Join(AppConfigDir, "clusters")
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil {
slog.Warn("Unable to create clusters dir",
slogs.Dir, AppContextsDir,
slogs.Error, err,
)
}
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 e := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); e != nil {
slog.Warn("No skins dir detected", slogs.Error, e)
}
AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
if err != nil {
return err
}
AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks"))
if err != nil {
slog.Warn("No benchmarks dir detected",
slogs.Dir, AppBenchmarksDir,
slogs.Error, err,
)
}
dataDir, err := xdg.DataFile(AppName)
if err != nil {
return err
}
AppContextsDir = filepath.Join(dataDir, "clusters")
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil {
slog.Warn("No context dir detected",
slogs.Dir, AppContextsDir,
slogs.Error, err,
)
}
return nil
}
// AppContextDir generates a valid context config dir.
func AppContextDir(cluster, context string) string {
return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context))
}
// AppContextAliasesFile generates a valid context specific aliases file path.
func AppContextAliasesFile(cluster, context string) string {
return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "aliases.yaml")
}
// AppContextPluginsFile generates a valid context specific plugins file path.
func AppContextPluginsFile(cluster, context string) string {
return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "plugins.yaml")
}
// AppContextHotkeysFile generates a valid context specific hotkeys file path.
func AppContextHotkeysFile(cluster, context string) string {
return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(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, data.SanitizeContextSubpath(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, data.SanitizeContextSubpath(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); errors.Is(err, fs.ErrNotExist) {
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); errors.Is(err, fs.ErrNotExist) {
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); errors.Is(err, fs.ErrNotExist) {
return f, os.WriteFile(f, hotkeysTpl, data.DefaultFileMod)
}
return f, nil
}
// SkinFileFromName generate skin file path from spec.
func SkinFileFromName(n string) string {
if n == "" {
n = "stock"
}
return filepath.Join(AppSkinsDir, n+".yaml")
}
================================================
FILE: internal/config/files_int_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"os"
"path/filepath"
"testing"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_initXDGLocs(t *testing.T) {
tmp, err := UserTmpDir()
require.NoError(t, err)
require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME"))
require.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
require.NoError(t, os.Unsetenv("XDG_DATA_HOME"))
require.NoError(t, os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "k9s-xdg", "config")))
require.NoError(t, os.Setenv("XDG_CACHE_HOME", filepath.Join(tmp, "k9s-xdg", "cache")))
require.NoError(t, os.Setenv("XDG_STATE_HOME", filepath.Join(tmp, "k9s-xdg", "state")))
require.NoError(t, os.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "k9s-xdg", "data")))
xdg.Reload()
uu := map[string]struct {
configDir string
configFile string
benchmarksDir string
contextsDir string
contextHotkeysFile string
contextConfig string
dumpsDir string
benchDir string
hkFile string
}{
"check-env": {
configDir: filepath.Join(tmp, "k9s-xdg", "config", "k9s"),
configFile: filepath.Join(tmp, "k9s-xdg", "config", "k9s", data.MainConfigFile),
benchmarksDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "benchmarks"),
contextsDir: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters"),
contextHotkeysFile: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters", "cl-1", "ct-1-1", "hotkeys.yaml"),
contextConfig: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters", "cl-1", "ct-1-1", data.MainConfigFile),
dumpsDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "screen-dumps", "cl-1", "ct-1-1"),
benchDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "benchmarks", "cl-1", "ct-1-1"),
hkFile: filepath.Join(tmp, "k9s-xdg", "config", "k9s", "hotkeys.yaml"),
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
require.NoError(t, initXDGLocs())
assert.Equal(t, u.configDir, AppConfigDir)
assert.Equal(t, u.configFile, AppConfigFile)
assert.Equal(t, u.benchmarksDir, AppBenchmarksDir)
assert.Equal(t, u.contextsDir, AppContextsDir)
assert.Equal(t, u.contextHotkeysFile, AppContextHotkeysFile("cl-1", "ct-1-1"))
assert.Equal(t, u.contextConfig, AppContextConfig("cl-1", "ct-1-1"))
dir, err := DumpsDir("cl-1", "ct-1-1")
require.NoError(t, err)
assert.Equal(t, u.dumpsDir, dir)
bdir, err := EnsureBenchmarksDir("cl-1", "ct-1-1")
require.NoError(t, err)
assert.Equal(t, u.benchDir, bdir)
hk, err := EnsureHotkeysCfgFile()
require.NoError(t, err)
assert.Equal(t, u.hkFile, hk)
})
}
}
================================================
FILE: internal/config/files_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"os"
"path/filepath"
"testing"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInitLogLoc(t *testing.T) {
tmp, err := config.UserTmpDir()
require.NoError(t, err)
uu := map[string]struct {
dir string
e string
}{
"log-env": {
dir: "/tmp/test/k9s/logs",
e: "/tmp/test/k9s/logs/k9s.log",
},
"xdg-env": {
dir: "/tmp/test/xdg-state",
e: "/tmp/test/xdg-state/k9s/k9s.log",
},
"cfg-env": {
dir: "/tmp/test/k9s-test",
e: filepath.Join(tmp, "k9s.log"),
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
require.NoError(t, os.Unsetenv(config.K9sEnvLogsDir))
require.NoError(t, os.Unsetenv("XDG_STATE_HOME"))
require.NoError(t, os.Unsetenv(config.K9sEnvConfigDir))
switch k {
case "log-env":
require.NoError(t, os.Setenv(config.K9sEnvLogsDir, u.dir))
case "xdg-env":
require.NoError(t, os.Setenv("XDG_STATE_HOME", u.dir))
xdg.Reload()
case "cfg-env":
require.NoError(t, os.Setenv(config.K9sEnvConfigDir, u.dir))
}
err := config.InitLogLoc()
require.NoError(t, err)
assert.Equal(t, u.e, config.AppLogFile)
require.NoError(t, os.RemoveAll(config.AppLogFile))
})
}
}
func TestEnsureBenchmarkCfg(t *testing.T) {
require.NoError(t, os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config"))
require.NoError(t, config.InitLocs())
defer require.NoError(t, os.RemoveAll("/tmp/test-config"))
require.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod))
require.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
uu := map[string]struct {
cluster, context string
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)
require.NoError(t, err)
assert.Equal(t, u.f, f)
bb, err := os.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, u.e, string(bb))
})
}
}
func TestSkinFileFromName(t *testing.T) {
config.AppSkinsDir = "/tmp/k9s-test/skins"
defer require.NoError(t, os.RemoveAll("/tmp/k9s-test/skins"))
uu := map[string]struct {
n string
e string
}{
"empty": {
e: "/tmp/k9s-test/skins/stock.yaml",
},
"happy": {
n: "fred-blee",
e: "/tmp/k9s-test/skins/fred-blee.yaml",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, config.SkinFileFromName(u.n))
})
}
}
================================================
FILE: internal/config/flags.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
const (
// DefaultRefreshRate represents the refresh interval.
DefaultRefreshRate float32 = 2.0 // secs
// DefaultLogLevel represents the default log level.
DefaultLogLevel = "info"
// DefaultCommand represents the default command to run.
DefaultCommand = ""
)
// Flags represents K9s configuration flags.
type Flags struct {
RefreshRate *float32
LogLevel *string
LogFile *string
Headless *bool
Logoless *bool
Command *string
AllNamespaces *bool
ReadOnly *bool
Write *bool
Crumbsless *bool
Splashless *bool
Invert *bool
ScreenDumpDir *string
}
// NewFlags returns new configuration flags.
func NewFlags() *Flags {
return &Flags{
RefreshRate: float32Ptr(DefaultRefreshRate),
LogLevel: strPtr(DefaultLogLevel),
LogFile: strPtr(AppLogFile),
Headless: boolPtr(false),
Logoless: boolPtr(false),
Command: strPtr(DefaultCommand),
AllNamespaces: boolPtr(false),
ReadOnly: boolPtr(false),
Write: boolPtr(false),
Crumbsless: boolPtr(false),
Splashless: boolPtr(false),
Invert: boolPtr(false),
ScreenDumpDir: strPtr(AppDumpsDir),
}
}
func boolPtr(b bool) *bool {
return &b
}
func float32Ptr(f float32) *float32 {
return &f
}
func strPtr(s string) *string {
return &s
}
================================================
FILE: internal/config/flags_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestNewFlags(t *testing.T) {
config.AppDumpsDir = "/tmp/k9s-test/screen-dumps"
config.AppLogFile = "/tmp/k9s-test/k9s.log"
f := config.NewFlags()
assert.InDelta(t, 2.0, *f.RefreshRate, 0.001)
assert.Equal(t, "info", *f.LogLevel)
assert.Equal(t, "/tmp/k9s-test/k9s.log", *f.LogFile)
assert.Equal(t, config.AppDumpsDir, *f.ScreenDumpDir)
assert.Empty(t, *f.Command)
assert.False(t, *f.Headless)
assert.False(t, *f.Logoless)
assert.False(t, *f.AllNamespaces)
assert.False(t, *f.ReadOnly)
assert.False(t, *f.Write)
assert.False(t, *f.Crumbsless)
assert.False(t, *f.Splashless)
}
================================================
FILE: internal/config/helpers.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"log/slog"
"os"
"os/user"
"path/filepath"
"github.com/derailed/k9s/internal/slogs"
)
const (
envPFAddress = "K9S_DEFAULT_PF_ADDRESS"
defaultPortFwdAddress = "localhost"
)
// IsBoolSet checks if a bool ptr is set.
func IsBoolSet(b *bool) bool {
return b != nil && *b
}
func isStringSet(s *string) bool {
return s != nil && *s != ""
}
func isYamlFile(file string) bool {
ext := filepath.Ext(file)
return ext == ".yml" || ext == ".yaml"
}
// isEnvSet checks if env var is set.
func isEnvSet(env string) bool {
return os.Getenv(env) != ""
}
// UserTmpDir returns the temp dir with the current user name.
func UserTmpDir() (string, error) {
u, err := user.Current()
if err != nil {
return "", err
}
dir := filepath.Join(os.TempDir(), u.Username, AppName)
return dir, nil
}
// MustK9sUser establishes current user identity or fail.
func MustK9sUser() string {
usr, err := user.Current()
if err != nil {
envUsr := os.Getenv("USER")
if envUsr != "" {
return envUsr
}
envUsr = os.Getenv("LOGNAME")
if envUsr != "" {
return envUsr
}
slog.Error("Die on retrieving user info", slogs.Error, err)
os.Exit(1)
}
return usr.Username
}
func defaultPFAddress() string {
if a := os.Getenv(envPFAddress); a != "" {
return a
}
return defaultPortFwdAddress
}
================================================
FILE: internal/config/hotkey.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"errors"
"io/fs"
"log/slog"
"os"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v3"
)
// HotKeys represents a collection of plugins.
type HotKeys struct {
HotKey map[string]HotKey `yaml:"hotKeys"`
}
// HotKey describes a K9s hotkey.
type HotKey struct {
ShortCut string `yaml:"shortCut"`
Override bool `yaml:"override"`
Description string `yaml:"description"`
Command string `yaml:"command"`
KeepHistory bool `yaml:"keepHistory"`
}
// NewHotKeys returns a new plugin.
func NewHotKeys() HotKeys {
return HotKeys{
HotKey: make(map[string]HotKey),
}
}
// Load K9s plugins.
func (h HotKeys) Load(path string) error {
if err := h.LoadHotKeys(AppHotKeysFile); err != nil {
return err
}
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
return h.LoadHotKeys(path)
}
// LoadHotKeys loads plugins from a given file.
func (h HotKeys) LoadHotKeys(path string) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
bb, err := os.ReadFile(path)
if err != nil {
return err
}
if err := data.JSONValidator.Validate(json.HotkeysSchema, bb); err != nil {
slog.Warn("Validation failed. Please update your config and restart.",
slogs.Path, path,
slogs.Error, err,
)
}
var hh HotKeys
if err := yaml.Unmarshal(bb, &hh); err != nil {
return err
}
for k, v := range hh.HotKey {
h.HotKey[k] = v
}
return nil
}
================================================
FILE: internal/config/hotkey_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHotKeyLoad(t *testing.T) {
h := config.NewHotKeys()
require.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml"))
assert.Len(t, h.HotKey, 1)
k, ok := h.HotKey["pods"]
assert.True(t, ok)
assert.Equal(t, "shift-0", k.ShortCut)
assert.Equal(t, "Launch pod view", k.Description)
assert.Equal(t, "pods", k.Command)
assert.True(t, k.KeepHistory)
}
================================================
FILE: internal/config/json/schemas/aliases.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s aliases schema",
"type": "object",
"additionalProperties": false,
"properties": {
"aliases": {
"type": "object",
"additionalProperties": { "type": "string" },
"required": []
}
},
"required": ["aliases"]
}
================================================
FILE: internal/config/json/schemas/context.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s context config schema",
"type": "object",
"additionalProperties": false,
"properties": {
"k9s": {
"additionalProperties": false,
"properties": {
"cluster": { "type": "string" },
"readOnly": {"type": "boolean"},
"skin": { "type": "string" },
"proxy": {
"oneOf": [
{ "type": "null" },
{
"type": "object",
"additionalProperties": false,
"properties":
{
"address": {"type": "string"}
}
}
]
},
"namespace": {
"type": "object",
"additionalProperties": false,
"properties": {
"active": {"type": "string"},
"lockFavorites": {"type": "boolean"},
"favorites": {
"type": "array",
"items": {"type": "string"}
}
}
},
"view": {
"type": "object",
"additionalProperties": false,
"properties": {
"active": { "type": "string" }
}
},
"featureGates": {
"type": "object",
"additionalProperties": false,
"properties": {
"nodeShell": { "type": "boolean" }
}
}
}
}
},
"required": ["k9s"]
}
================================================
FILE: internal/config/json/schemas/hotkeys.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s hotkeys schema",
"type": "object",
"additionalProperties": false,
"properties": {
"hotKeys": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"shortCut": {"type": "string"},
"override": { "type": "boolean" },
"description": {"type": "string"},
"command": {"type": "string"},
"keepHistory": {"type": "boolean"}
}
}
}
},
"required": ["hotKeys"]
}
================================================
FILE: internal/config/json/schemas/k9s.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s config schema",
"type": "object",
"additionalProperties": false,
"properties": {
"k9s": {
"additionalProperties": false,
"properties": {
"liveViewAutoRefresh": { "type": "boolean" },
"gpuVendors": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"vendor": { "type": "string" },
"model": { "type": "string" }
},
"required": ["vendor", "model"]
}
},
"screenDumpDir": {"type": "string"},
"refreshRate": { "type": "number" },
"apiServerTimeout": { "type": "string" },
"maxConnRetry": { "type": "integer" },
"readOnly": { "type": "boolean" },
"noExitOnCtrlC": { "type": "boolean" },
"skipLatestRevCheck": { "type": "boolean" },
"disablePodCounting": { "type": "boolean" },
"defaultView": { "type": "string" },
"portForwardAddress": { "type": "string" },
"ui": {
"type": "object",
"additionalProperties": false,
"properties": {
"enableMouse": {"type": "boolean"},
"headless": {"type": "boolean"},
"logoless": {"type": "boolean"},
"crumbsless": {"type": "boolean"},
"splashless": {"type": "boolean"},
"noIcons": {"type": "boolean"},
"reactive": {"type": "boolean"},
"skin": {"type": "string"},
"defaultsToFullScreen": {"type": "boolean"},
"useFullGVRTitle": {"type": "boolean"},
"invert": {"type": "boolean"}
}
},
"shellPod": {
"type": "object",
"additionalProperties": true,
"properties": {
"image": { "type": "string" },
"command": {
"type": "array",
"items": { "type": "string"}
},
"args": {
"type": "array",
"items": { "type": "string"}
},
"namespace": { "type": "string" },
"limits": {
"type": "object",
"properties": {
"cpu": { "type": "string" },
"memory": { "type": "string" }
},
"required": ["cpu", "memory"]
},
"labels": {
"type": "object",
"additionalProperties": { "type": "string" },
"required": []
},
"tty": { "type": "boolean" },
"imagePullPolicy": { "type": "string" },
"imagePullSecrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" }
}
}
}
},
"required": ["image", "namespace", "limits"]
},
"imageScans": {
"type": "object",
"additionalProperties": false,
"properties": {
"enable": { "type": "boolean" },
"namespace": { "type": "string" },
"exclusions": {
"type": "object",
"properties": {
"namespaces": {
"type": "array",
"items": { "type": "string" }
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
},
"required": ["enable"]
},
"logger": {
"type": "object",
"additionalProperties": false,
"properties": {
"tail": {"type": "integer"},
"buffer": {"type": "integer"},
"sinceSeconds": {"type": "integer"},
"textWrap": {"type": "boolean"},
"disableAutoscroll": {"type": "boolean"},
"columnLock": {"type": "boolean"},
"showTime": {"type": "boolean"}
}
},
"thresholds": {
"type": "object",
"additionalProperties": false,
"properties": {
"cpu": {
"type": "object",
"properties": {
"critical": {"type": "integer"},
"warn": {"type": "integer"}
}
},
"memory": {
"type": "object",
"properties": {
"critical": {"type": "integer"},
"warn": {"type": "integer"}
}
}
}
}
}
}
},
"required": ["k9s"]
}
================================================
FILE: internal/config/json/schemas/plugin-multi.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s plugin-multi schema",
"type": "object",
"additionalProperties": {
"properties": {
"shortCut": { "type": "string" },
"override": { "type": "boolean" },
"description": { "type": "string" },
"confirm": { "type": "boolean" },
"dangerous": { "type": "boolean" },
"scopes": {
"type": "array",
"items": { "type": "string" }
},
"command": { "type": "string" },
"background": { "type": "boolean" },
"overwriteOutput": { "type": "boolean" },
"args": {
"type": "array",
"items": { "type": ["string", "number"] }
},
"inputs": {
"type": "array",
"maxItems": 5,
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"type": { "type": "string", "enum": ["string", "number", "bool", "dropdown"] },
"required": { "type": "boolean" },
"default": { "type": ["string", "number", "boolean"] },
"options": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["name", "type"],
"if": { "required": ["default"] },
"then": {
"properties": { "required": { "const": true } },
"required": ["required"]
}
}
}
},
"required": ["shortCut", "description", "scopes", "command"]
}
}
================================================
FILE: internal/config/json/schemas/plugin.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s plugin schema",
"type": "object",
"additionalProperties": false,
"properties": {
"shortCut": { "type": "string" },
"override": { "type": "boolean" },
"description": { "type": "string" },
"confirm": { "type": "boolean" },
"dangerous": { "type": "boolean" },
"scopes": {
"type": "array",
"items": { "type": "string" }
},
"command": { "type": "string" },
"background": { "type": "boolean" },
"overwriteOutput": { "type": "boolean" },
"args": {
"type": "array",
"items": { "type": ["string", "number"] }
},
"inputs": {
"type": "array",
"maxItems": 5,
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"type": { "type": "string", "enum": ["string", "number", "bool", "dropdown"] },
"required": { "type": "boolean" },
"default": { "type": ["string", "number", "boolean"] },
"options": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["name", "type"],
"if": { "required": ["default"] },
"then": {
"properties": { "required": { "const": true } },
"required": ["required"]
}
}
}
},
"required": ["shortCut", "description", "scopes", "command"]
}
================================================
FILE: internal/config/json/schemas/plugins.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s plugins schema",
"type": "object",
"additionalProperties": false,
"properties": {
"plugins": {
"type": "object",
"additionalProperties": {
"properties": {
"shortCut": { "type": "string" },
"override": { "type": "boolean" },
"description": { "type": "string" },
"confirm": { "type": "boolean" },
"dangerous": { "type": "boolean" },
"scopes": {
"type": "array",
"items": { "type": "string" }
},
"command": { "type": "string" },
"background": { "type": "boolean" },
"overwriteOutput": { "type": "boolean" },
"args": {
"type": "array",
"items": { "type": ["string", "number"] }
},
"inputs": {
"type": "array",
"maxItems": 5,
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"type": { "type": "string", "enum": ["string", "number", "bool", "dropdown"] },
"required": { "type": "boolean" },
"default": { "type": ["string", "number", "boolean"] },
"options": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["name", "type"],
"if": { "required": ["default"] },
"then": {
"properties": { "required": { "const": true } },
"required": ["required"]
}
}
}
},
"required": ["shortCut", "description", "scopes", "command"]
},
"required": []
}
},
"required": ["plugins"]
}
================================================
FILE: internal/config/json/schemas/skin.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s skin schema",
"type": "object",
"additionalProperties": true,
"properties": {
"k9s": {
"type": "object",
"additionalProperties": false,
"properties": {
"body": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"logoColor": {"type": "string"}
}
},
"prompt": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"suggestColor": {"type": "string"}
}
},
"info": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"sectionColor": {"type": "string"},
"k9sRevColor": {"type": "string"},
"cpuColor": {"type": "string"},
"memColor": {"type": "string"}
}
},
"help": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"keyColor": {"type": "string"},
"numKeyColor": {"type": "string"},
"sectionColor": {"type": "string"}
}
},
"dialog": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"buttonFgColor": {"type": "string"},
"buttonBgColor": {"type": "string"},
"buttonFocusFgColor": {"type": "string"},
"buttonFocusBgColor": {"type": "string"},
"labelFgColor": {"type": "string"},
"fieldFgColor": {"type": "string"}
}
},
"frame": {
"type": "object",
"properties": {
"border": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"}
}
},
"menu": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"keyColor": {"type": "string"},
"numKeyColor": {"type": "string"}
}
},
"crumbs": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"keyColor": {"type": "string"},
"activeColor": {"type": "string"}
}
},
"status": {
"type": "object",
"properties": {
"newColor": {"type": "string"},
"modifyColor": {"type": "string"},
"addColor:": {"type": "string"},
"errorColor": {"type": "string"},
"highlightColor": {"type": "string"},
"killColor": {"type": "string"},
"completedColor": {"type": "string"}
}
},
"title": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor":{"type": "string"},
"highlightColor": {"type": "string"},
"counterColor":{"type": "string"},
"filterColor": {"type": "string"}
}
}
}
},
"views": {
"type": "object",
"properties": {
"charts": {
"type": "object",
"properties": {
"bgColor": {"type": "string"},
"defaultDialColors": {
"type": "array",
"items": {"type": "string"}
},
"defaultChartColors": {
"type": "array",
"items": {"type": "string"}
}
},
"table": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"cursorFgColor": {"type": "string"},
"cursorBgColor": {"type": "string"},
"header": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"}
}
}
}
}
},
"xray": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"cursorFgColor": {"type": "string"},
"graphicColor": {"type": "string"},
"showIcons": {"type": "boolean"}
}
},
"yaml": {
"type": "object",
"properties": {
"keyColor": {"type": "string"},
"colonColor": {"type": "string"},
"valueColor": {"type": "string"}
}
},
"logs": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"indicator": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"fgColor": {"type": "string"},
"bgColor": {"type": "string"},
"toggleOnColor": {"type": "string"},
"toggleOffColor": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
}
}
================================================
FILE: internal/config/json/schemas/views.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "K9s views schema",
"type": "object",
"additionalProperties": false,
"properties": {
"views": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"sortColumn": { "type": "string" },
"columns": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["columns"]
}
}
},
"required": ["views"]
}
================================================
FILE: internal/config/json/testdata/aliases/cool.yaml
================================================
aliases:
blee: duh
fred: zorg
================================================
FILE: internal/config/json/testdata/aliases/toast.yaml
================================================
alias:
blee: duh
fred: zorg
================================================
FILE: internal/config/json/testdata/context/cool.yaml
================================================
k9s:
cluster: kind-dashb
readOnly: false
skin: nightfox
namespace:
active: default
lockFavorites: false
favorites:
- kube-system
- default
view:
active: pod
featureGates:
nodeShell: false
================================================
FILE: internal/config/json/testdata/context/toast.yaml
================================================
k9s:
cluster: kind-dashb
readOnly: false
skin: nightfox
namespaces:
active: default
lockFavorites: false
favorites:
- kube-system
- default
view:
active: pod
fred: blee
featureGates:
nodeShell: false
================================================
FILE: internal/config/json/testdata/hotkeys/cool.yaml
================================================
hotKey:
shift-0:
shortCut: Shift-0
description: Popeye
command: popeye
shift-1:
shortCut: Shift-1
description: View deployments
command: dp
shift-2:
shortCut: Shift-2
description: View services
command: service
shift-3:
shortCut: Shift-3
description: View statefulsets
command: sts
shift-4:
shortCut: Shift-4
description: Xray Deployments
command: xray dp
shift-5:
shortCut: Shift-5
description: Xray StatefulSets
command: xray sts
shift-6:
shortCut: Shift-6
description: Xray DaemonSets
command: xray ds
shift-7:
shortCut: Shift-7
description: Xray Services
command: xray svc
================================================
FILE: internal/config/json/testdata/k9s/cool.yaml
================================================
k9s:
liveViewAutoRefresh: false
screenDumpDir: /Users/fernand/.local/state/k9s/screen-dumps
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
noIcons: false
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 100
buffer: 5000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
================================================
FILE: internal/config/json/testdata/k9s/toast.yaml
================================================
k9s:
liveViewAutoRefresh: false
screenDumpDir: /Users/fernand/.local/state/k9s/screen-dumps
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
skipLatestRevCheck: false
disablePodCounting: false
shellPods:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 100
buffer: 5000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
================================================
FILE: internal/config/json/testdata/plugins/cool.yaml
================================================
plugins:
blee:
shortCut: g
confirm: false
description: blee
scopes:
- namespaces
command: sh
background: false
args:
- -c
- "blee bla"
duh:
shortCut: h
confirm: true
description: duh
scopes:
- all
command: sh
background: true
args:
- -c
- "duh fred"
================================================
FILE: internal/config/json/testdata/plugins/snippet.yaml
================================================
shortCut: g
confirm: false
description: blee
scopes:
- namespaces
command: sh
background: false
args:
- -c
- "blee bla"
================================================
FILE: internal/config/json/testdata/plugins/snippets.yaml
================================================
blee:
shortCut: g
confirm: false
description: blee
scopes:
- namespaces
command: sh
background: false
args:
- -c
- "blee bla"
duh:
shortCut: h
confirm: true
description: duh
scopes:
- all
command: sh
background: true
args:
- -c
- "duh fred"
================================================
FILE: internal/config/json/testdata/plugins/toast.yaml
================================================
plugins:
blee:
shortCuts: g
confirm: false
description: blee
scopes:
- namespaces
command: sh
background: false
args:
- -c
- "blee bla"
duh:
shortCut: h
confirm: true
description: duh
command: sh
background: true
args:
- -c
- "duh fred"
================================================
FILE: internal/config/json/testdata/skins/cool.yaml
================================================
# -----------------------------------------------------------------------------
# K9s Nightfox Theme
# Based on the Nightfox.nvim color scheme:
# https://github.com/EdenEast/nightfox.nvim
# -----------------------------------------------------------------------------
# Styles...
foreground: &foreground "#cdcecf"
background: &background "#192330"
current_line: ¤t_line "#2b3b51"
selection: &selection "#2b3b51"
comment: &comment "#738091"
cyan: &cyan "#63cdcf"
green: &green "#81b29a"
orange: &orange "#f4a261"
magenta: &magenta "#9d79d6"
blue: &blue "#719cd6"
red: &red "#c94f6d"
# Skin...
k9s:
body:
fgColor: *foreground
bgColor: *background
logoColor: *blue
prompt:
fgColor: *foreground
bgColor: *background
suggestColor: *orange
info:
fgColor: *magenta
sectionColor: *foreground
help:
fgColor: *foreground
bgColor: *background
keyColor: *magenta
numKeyColor: *magenta
sectionColor: *foreground
dialog:
fgColor: *foreground
bgColor: *background
buttonFgColor: *foreground
buttonBgColor: *magenta
buttonFocusFgColor: white
buttonFocusBgColor: *cyan
labelFgColor: *orange
fieldFgColor: *foreground
frame:
border:
fgColor: *selection
focusColor: *current_line
menu:
fgColor: *foreground
keyColor: *magenta
numKeyColor: *magenta
crumbs:
fgColor: *foreground
bgColor: *current_line
activeColor: *current_line
status:
newColor: *cyan
modifyColor: *blue
addColor: *green
errorColor: *red
highlightColor: *orange
killColor: *comment
completedColor: *comment
title:
fgColor: *foreground
bgColor: *current_line
highlightColor: *orange
counterColor: *blue
filterColor: *magenta
views:
charts:
bgColor: default
defaultDialColors:
- *blue
- *red
defaultChartColors:
- *blue
- *red
table:
fgColor: *foreground
bgColor: *background
cursorFgColor: *selection
cursorBgColor: *current_line
header:
fgColor: *foreground
bgColor: *background
sorterColor: *cyan
xray:
fgColor: *foreground
bgColor: *background
cursorColor: *current_line
graphicColor: *blue
showIcons: false
yaml:
keyColor: *magenta
colonColor: *blue
valueColor: *foreground
logs:
fgColor: *foreground
bgColor: *background
indicator:
fgColor: *foreground
bgColor: *selection
toggleOnColor: *magenta
toggleOffColor: *blue
================================================
FILE: internal/config/json/testdata/skins/toast.yaml
================================================
# -----------------------------------------------------------------------------
# K9s Nightfox Theme
# Based on the Nightfox.nvim color scheme:
# https://github.com/EdenEast/nightfox.nvim
# -----------------------------------------------------------------------------
# Styles...
foreground: &foreground "#cdcecf"
background: &background "#192330"
current_line: ¤t_line "#2b3b51"
selection: &selection "#2b3b51"
comment: &comment "#738091"
cyan: &cyan "#63cdcf"
green: &green "#81b29a"
orange: &orange "#f4a261"
magenta: &magenta "#9d79d6"
blue: &blue "#719cd6"
red: &red "#c94f6d"
# Skin...
k9s:
bodys:
fgColor: *foreground
bgColor: *background
logoColor: *blue
prompt:
fgColor: *foreground
bgColor: *background
suggestColor: *orange
info:
fgColor: *magenta
sectionColor: *foreground
dialog:
fgColor: *foreground
bgColor: *background
buttonFgColor: *foreground
buttonBgColor: *magenta
buttonFocusFgColor: white
buttonFocusBgColor: *cyan
labelFgColor: *orange
fieldFgColor: *foreground
frame:
border:
fgColor: *selection
focusColor: *current_line
menu:
fgColor: *foreground
keyColor: *magenta
numKeyColor: *magenta
crumbs:
fgColor: *foreground
bgColor: *current_line
activeColor: *current_line
status:
newColor: *cyan
modifyColor: *blue
addColor: *green
errorColor: *red
highlightColor: *orange
killColor: *comment
completedColor: *comment
title:
fgColor: *foreground
bgColor: *current_line
highlightColor: *orange
counterColor: *blue
filterColor: *magenta
views:
charts:
bgColor: default
defaultDialColors:
- *blue
- *red
defaultChartColors:
- *blue
- *red
table:
fgColor: *foreground
bgColor: *background
cursorFgColor: *selection
cursorBgColor: *current_line
header:
fgColor: *foreground
bgColor: *background
sorterColor: *cyan
xray:
fgColor: *foreground
bgColor: *background
cursorColor: *current_line
graphicColor: *blue
showIcons: false
yaml:
keyColor: *magenta
colonColor: *blue
valueColor: *foreground
logs:
fgColor: *foreground
bgColor: *background
indicator:
fgColor: *foreground
bgColor: *selection
toggleOnColor: *magenta
toggleOffColor: *blue
================================================
FILE: internal/config/json/testdata/views/cool.yaml
================================================
views:
v1/nodes:
columns:
- NAME
- IP
v1/endpoints:
sortColumn: AGE:asc
columns:
- NAME
- NAMESPACE
- ENDPOINTS
- AGE
================================================
FILE: internal/config/json/testdata/views/toast.yaml
================================================
views:
v1/nodes:
v1/endpoints:
sortCol: AGE:asc
cols:
- NAME
- NAMESPACE
- ENDPOINTS
- AGE
================================================
FILE: internal/config/json/validator.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package json
import (
"cmp"
_ "embed"
"errors"
"fmt"
"log/slog"
"slices"
"github.com/derailed/k9s/internal/slogs"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"
)
const (
// PluginsSchema describes plugins schema.
PluginsSchema = "plugins.json"
// PluginSchema describes a plugin snippet schema.
PluginSchema = "plugin.json"
// PluginMultiSchema describes plugin snippets schema.
PluginMultiSchema = "plugin-multi.json"
// AliasesSchema describes aliases schema.
AliasesSchema = "aliases.json"
// ViewsSchema describes views schema.
ViewsSchema = "views.json"
// HotkeysSchema describes hotkeys schema.
HotkeysSchema = "hotkeys.json"
// K9sSchema describes k9s config schema.
K9sSchema = "k9s.json"
// ContextSchema describes context config schema.
ContextSchema = "context.json"
// SkinSchema describes skin config schema.
SkinSchema = "skin.json"
)
var (
//go:embed schemas/plugins.json
pluginsSchema string
//go:embed schemas/plugin.json
pluginSchema string
//go:embed schemas/plugin-multi.json
pluginMultiSchema string
//go:embed schemas/aliases.json
aliasSchema string
//go:embed schemas/views.json
viewsSchema string
//go:embed schemas/k9s.json
k9sSchema string
//go:embed schemas/context.json
contextSchema string
//go:embed schemas/hotkeys.json
hotkeysSchema string
//go:embed schemas/skin.json
skinSchema string
)
// Validator tracks schemas validation.
type Validator struct {
schemas map[string]gojsonschema.JSONLoader
loader *gojsonschema.SchemaLoader
}
// NewValidator returns a new instance.
func NewValidator() *Validator {
v := Validator{
schemas: map[string]gojsonschema.JSONLoader{
K9sSchema: gojsonschema.NewStringLoader(k9sSchema),
ContextSchema: gojsonschema.NewStringLoader(contextSchema),
AliasesSchema: gojsonschema.NewStringLoader(aliasSchema),
ViewsSchema: gojsonschema.NewStringLoader(viewsSchema),
PluginsSchema: gojsonschema.NewStringLoader(pluginsSchema),
PluginSchema: gojsonschema.NewStringLoader(pluginSchema),
PluginMultiSchema: gojsonschema.NewStringLoader(pluginMultiSchema),
HotkeysSchema: gojsonschema.NewStringLoader(hotkeysSchema),
SkinSchema: gojsonschema.NewStringLoader(skinSchema),
},
}
v.register()
return &v
}
// Init initializes the schemas.
func (v *Validator) register() {
v.loader = gojsonschema.NewSchemaLoader()
v.loader.Validate = true
clog := slog.With(slogs.Subsys, "schema")
for k, s := range v.schemas {
if err := v.loader.AddSchema(k, s); err != nil {
clog.Error("Schema initialization failed",
slogs.SchemaFile, k,
slogs.Error, err,
)
}
}
}
// ValidatePlugins validates plugins schema.
// Checks for full, snippet and multi snippets schemas.
func (v *Validator) ValidatePlugins(bb []byte) (string, error) {
var errs error
for _, k := range []string{PluginsSchema, PluginSchema, PluginMultiSchema} {
if err := v.Validate(k, bb); err != nil {
errs = errors.Join(errs, err)
continue
}
return k, nil
}
return "", errs
}
// Validate runs document thru given schema validation.
func (v *Validator) Validate(k string, bb []byte) error {
var m any
err := yaml.Unmarshal(bb, &m)
if err != nil {
return err
}
s, ok := v.schemas[k]
if !ok {
return fmt.Errorf("no schema found for: %q", k)
}
result, err := gojsonschema.Validate(s, gojsonschema.NewGoLoader(m))
if err != nil {
return err
}
if result.Valid() {
return nil
}
slices.SortFunc(result.Errors(), func(a, b gojsonschema.ResultError) int {
return cmp.Compare(a.Description(), b.Description())
})
var errs error
for _, re := range result.Errors() {
errs = errors.Join(errs, errors.New(re.Description()))
}
return errs
}
func (v *Validator) ValidateObj(k string, o any) error {
s, ok := v.schemas[k]
if !ok {
return fmt.Errorf("no schema found for: %q", k)
}
result, err := gojsonschema.Validate(s, gojsonschema.NewGoLoader(o))
if err != nil {
return err
}
if result.Valid() {
return nil
}
slices.SortFunc(result.Errors(), func(a, b gojsonschema.ResultError) int {
return cmp.Compare(a.Description(), b.Description())
})
var errs error
for _, re := range result.Errors() {
errs = errors.Join(errs, errors.New(re.Description()))
}
return errs
}
================================================
FILE: internal/config/json/validator_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package json_test
import (
"os"
"path/filepath"
"testing"
"github.com/derailed/k9s/internal/config/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidatePluginSnippet(t *testing.T) {
plugPath := "testdata/plugins/snippet.yaml"
bb, err := os.ReadFile(plugPath)
require.NoError(t, err)
p := json.NewValidator()
require.NoError(t, p.Validate(json.PluginSchema, bb), plugPath)
}
func TestValidatePlugins(t *testing.T) {
uu := map[string]struct {
path, schema string
err string
}{
"cool": {
path: "testdata/plugins/cool.yaml",
schema: json.PluginsSchema,
},
"toast": {
path: "testdata/plugins/toast.yaml",
schema: json.PluginsSchema,
err: "scopes is required\nshortCut is required",
},
"cool-snippet": {
path: "testdata/plugins/snippet.yaml",
schema: json.PluginSchema,
},
"cool-snippets": {
path: "testdata/plugins/snippets.yaml",
schema: json.PluginMultiSchema,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.path)
require.NoError(t, err)
v := json.NewValidator()
if err := v.Validate(u.schema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestValidatePluginDir(t *testing.T) {
plugDir := "../../../plugins"
ee, err := os.ReadDir(plugDir)
require.NoError(t, err)
for _, e := range ee {
if e.IsDir() {
continue
}
ext := filepath.Ext(e.Name())
if ext == ".md" {
continue
}
assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
bb, err := os.ReadFile(filepath.Join(plugDir, e.Name()))
require.NoError(t, err)
p := json.NewValidator()
require.NoError(t, p.Validate(json.PluginsSchema, bb), e.Name())
}
}
func TestValidateSkinDir(t *testing.T) {
skinDir := "../../../skins"
ee, err := os.ReadDir(skinDir)
require.NoError(t, err)
p := json.NewValidator()
for _, e := range ee {
if e.IsDir() {
continue
}
ext := filepath.Ext(e.Name())
assert.Equal(t, ".yaml", ext, "expected yaml file: %q", e.Name())
assert.NotContains(t, "_", e.Name(), "underscore in: %q", e.Name())
bb, err := os.ReadFile(filepath.Join(skinDir, e.Name()))
require.NoError(t, err)
require.NoError(t, p.Validate(json.SkinSchema, bb), e.Name())
}
}
func TestValidateSkin(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/skins/cool.yaml",
},
"toast": {
f: "testdata/skins/toast.yaml",
err: `Additional property bodys is not allowed`,
},
}
v := json.NewValidator()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
require.NoError(t, err)
if err := v.Validate(json.SkinSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestValidateK9s(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/k9s/cool.yaml",
},
"toast": {
f: "testdata/k9s/toast.yaml",
err: `Additional property shellPods is not allowed`,
},
}
v := json.NewValidator()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
require.NoError(t, err)
if err := v.Validate(json.K9sSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestValidateContext(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/context/cool.yaml",
},
"toast": {
f: "testdata/context/toast.yaml",
err: `Additional property fred is not allowed
Additional property namespaces is not allowed`,
},
}
v := json.NewValidator()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
require.NoError(t, err)
if err := v.Validate(json.ContextSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestValidateAliases(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/aliases/cool.yaml",
},
"toast": {
f: "testdata/aliases/toast.yaml",
err: `Additional property alias is not allowed
aliases is required`,
},
}
v := json.NewValidator()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
require.NoError(t, err)
if err := v.Validate(json.AliasesSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
func TestValidateViews(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"happy": {
f: "testdata/views/cool.yaml",
},
"toast": {
f: "testdata/views/toast.yaml",
err: `Additional property cols is not allowed
Additional property sortCol is not allowed
Invalid type. Expected: object, given: null
columns is required`,
},
}
v := json.NewValidator()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
bb, err := os.ReadFile(u.f)
require.NoError(t, err)
if err := v.Validate(json.ViewsSchema, bb); err != nil {
assert.Equal(t, u.err, err.Error())
}
})
}
}
================================================
FILE: internal/config/k9s.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"errors"
"fmt"
"io/fs"
"log/slog"
"net/http"
"net/url"
"os"
"path/filepath"
"sync"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs"
)
type gpuVendors map[string]string
// KnownGPUVendors tracks a set of known GPU vendors.
var KnownGPUVendors = defaultGPUVendors
var defaultGPUVendors = gpuVendors{
"nvidia": "nvidia.com/gpu",
"nvidia-shared": "nvidia.com/gpu.shared",
"amd": "amd.com/gpu",
"intel": "gpu.intel.com/i915",
}
// K9s tracks K9s configuration options.
type K9s struct {
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
GPUVendors gpuVendors `json:"gpuVendors" yaml:"gpuVendors"`
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
RefreshRate float32 `json:"refreshRate" yaml:"refreshRate"`
APIServerTimeout string `json:"apiServerTimeout" yaml:"apiServerTimeout"`
MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
PortForwardAddress string `yaml:"portForwardAddress"`
UI UI `json:"ui" yaml:"ui"`
SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"`
DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"`
ShellPod *ShellPod `json:"shellPod" yaml:"shellPod"`
ImageScans ImageScans `json:"imageScans" yaml:"imageScans"`
Logger Logger `json:"logger" yaml:"logger"`
Thresholds Threshold `json:"thresholds" yaml:"thresholds"`
DefaultView string `json:"defaultView" yaml:"defaultView"`
manualRefreshRate float32
manualReadOnly *bool
manualCommand *string
manualScreenDumpDir *string
refreshRateWarned bool
dir *data.Dir
activeContextName string
activeConfig *data.Config
conn client.Connection
ks data.KubeSettings
mx sync.RWMutex
contextSwitch bool
}
// NewK9s create a new K9s configuration.
func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
return &K9s{
RefreshRate: defaultRefreshRate,
GPUVendors: make(gpuVendors),
MaxConnRetry: defaultMaxConnRetry,
APIServerTimeout: client.DefaultCallTimeoutDuration.String(),
ScreenDumpDir: AppDumpsDir,
Logger: NewLogger(),
Thresholds: NewThreshold(),
PortForwardAddress: defaultPFAddress(),
ShellPod: NewShellPod(),
ImageScans: NewImageScans(),
dir: data.NewDir(AppContextsDir),
conn: conn,
ks: ks,
}
}
func (k *K9s) ToggleContextSwitch(b bool) {
k.mx.Lock()
defer k.mx.Unlock()
k.contextSwitch = b
}
func (k *K9s) getContextSwitch() bool {
k.mx.Lock()
defer k.mx.Unlock()
return k.contextSwitch
}
func (k *K9s) resetConnection(conn client.Connection) {
k.mx.Lock()
defer k.mx.Unlock()
k.conn = conn
}
// Save saves the k9s config to disk.
func (k *K9s) Save(contextName, clusterName string, force bool) error {
path := filepath.Join(
AppContextsDir,
data.SanitizeContextSubpath(clusterName, contextName),
data.MainConfigFile,
)
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) || force {
slog.Debug("[CONFIG] Saving context config to disk",
slogs.Path, path,
slogs.Cluster, k.getActiveConfig().Context.GetClusterName(),
slogs.Context, k.getActiveContextName(),
)
return k.dir.Save(path, k.getActiveConfig())
}
return nil
}
// Merge merges k9s configs.
func (k *K9s) Merge(k1 *K9s) {
if k1 == nil {
return
}
for k, v := range k1.GPUVendors {
KnownGPUVendors[k] = v
}
k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh
k.DefaultView = k1.DefaultView
k.ScreenDumpDir = k1.ScreenDumpDir
k.RefreshRate = k1.RefreshRate
k.APIServerTimeout = k1.APIServerTimeout
k.MaxConnRetry = k1.MaxConnRetry
k.ReadOnly = k1.ReadOnly
k.NoExitOnCtrlC = k1.NoExitOnCtrlC
k.PortForwardAddress = k1.PortForwardAddress
k.UI = k1.UI
k.SkipLatestRevCheck = k1.SkipLatestRevCheck
k.DisablePodCounting = k1.DisablePodCounting
k.ShellPod = k1.ShellPod
k.Logger = k1.Logger
k.ImageScans = k1.ImageScans
if k1.Thresholds != nil {
k.Thresholds = k1.Thresholds
}
}
// AppScreenDumpDir fetch screen dumps dir.
func (k *K9s) AppScreenDumpDir() string {
d := k.ScreenDumpDir
if isStringSet(k.manualScreenDumpDir) {
d = *k.manualScreenDumpDir
k.ScreenDumpDir = d
}
if d == "" {
d = AppDumpsDir
}
return d
}
// ContextScreenDumpDir fetch context specific screen dumps dir.
func (k *K9s) ContextScreenDumpDir() string {
return filepath.Join(k.AppScreenDumpDir(), k.contextPath())
}
func (k *K9s) contextPath() string {
if k.getActiveConfig() == nil {
return "na"
}
return data.SanitizeContextSubpath(
k.getActiveConfig().Context.GetClusterName(),
k.ActiveContextName(),
)
}
// Reset resets configuration and context.
func (k *K9s) Reset() {
k.setActiveConfig(nil)
k.setActiveContextName("")
}
// ActiveContextNamespace fetch the context active ns.
func (k *K9s) ActiveContextNamespace() (string, error) {
act, err := k.ActiveContext()
if err != nil {
return "", err
}
return act.Namespace.Active, nil
}
// ActiveContextName returns the active context name.
func (k *K9s) ActiveContextName() string {
return k.getActiveContextName()
}
// ActiveContext returns the currently active context.
func (k *K9s) ActiveContext() (*data.Context, error) {
if cfg := k.getActiveConfig(); cfg != nil && cfg.Context != nil {
return cfg.Context, nil
}
ct, err := k.ActivateContext(k.ActiveContextName())
return ct, err
}
func (k *K9s) setActiveConfig(c *data.Config) {
k.mx.Lock()
defer k.mx.Unlock()
k.activeConfig = c
}
func (k *K9s) getActiveConfig() *data.Config {
k.mx.RLock()
defer k.mx.RUnlock()
return k.activeConfig
}
func (k *K9s) setActiveContextName(n string) {
k.mx.Lock()
defer k.mx.Unlock()
k.activeContextName = n
}
func (k *K9s) getActiveContextName() string {
k.mx.RLock()
defer k.mx.RUnlock()
return k.activeContextName
}
// ActivateContext initializes the active context if not present.
func (k *K9s) ActivateContext(contextName string) (*data.Context, error) {
k.setActiveContextName(contextName)
ct, err := k.ks.GetContext(contextName)
if err != nil {
return nil, err
}
cfg, err := k.dir.Load(contextName, ct)
if err != nil {
return nil, err
}
k.setActiveConfig(cfg)
if cfg.Context.Proxy != nil {
k.ks.SetProxy(func(*http.Request) (*url.URL, error) {
slog.Debug("Using proxy address", slogs.Address, cfg.Context.Proxy.Address)
return url.Parse(cfg.Context.Proxy.Address)
})
if k.conn != nil && k.conn.Config() != nil {
// We get on this branch when the user switches the context and k9s
// already has an API connection object so we just set the proxy to
// avoid recreation using client.InitConnection
k.conn.Config().SetProxy(func(*http.Request) (*url.URL, error) {
slog.Debug("Setting proxy address", slogs.Address, cfg.Context.Proxy.Address)
return url.Parse(cfg.Context.Proxy.Address)
})
if !k.conn.CheckConnectivity() {
return nil, fmt.Errorf("unable to connect to context %q", contextName)
}
}
}
k.Validate(k.conn, contextName, ct.Cluster)
// If the context specifies a namespace, use it!
if ns := ct.Namespace; ns != client.BlankNamespace {
k.getActiveConfig().Context.Namespace.Active = ns
} else if k.getActiveConfig().Context.Namespace.Active == "" {
k.getActiveConfig().Context.Namespace.Active = client.DefaultNamespace
}
if k.getActiveConfig().Context == nil {
return nil, fmt.Errorf("context activation failed for: %s", contextName)
}
return k.getActiveConfig().Context, nil
}
// Reload reloads the context config from disk.
func (k *K9s) Reload() error {
// Switching context skipping reload...
if k.getContextSwitch() {
return nil
}
ct, err := k.ks.GetContext(k.getActiveContextName())
if err != nil {
return err
}
cfg, err := k.dir.Load(k.getActiveContextName(), ct)
if err != nil {
return err
}
k.setActiveConfig(cfg)
k.getActiveConfig().Validate(k.conn, k.getActiveContextName(), ct.Cluster)
return nil
}
// Override overrides k9s config from cli args.
func (k *K9s) Override(k9sFlags *Flags) {
if k9sFlags.RefreshRate != nil && *k9sFlags.RefreshRate != DefaultRefreshRate {
k.manualRefreshRate = float32(*k9sFlags.RefreshRate)
}
k.UI.manualHeadless = k9sFlags.Headless
k.UI.manualLogoless = k9sFlags.Logoless
k.UI.manualCrumbsless = k9sFlags.Crumbsless
k.UI.manualSplashless = k9sFlags.Splashless
k.UI.manualInvert = k9sFlags.Invert
if k9sFlags.ReadOnly != nil && *k9sFlags.ReadOnly {
k.manualReadOnly = k9sFlags.ReadOnly
}
if k9sFlags.Write != nil && *k9sFlags.Write {
var falseVal bool
k.manualReadOnly = &falseVal
}
k.manualCommand = k9sFlags.Command
k.manualScreenDumpDir = k9sFlags.ScreenDumpDir
}
// IsHeadless returns headless setting.
func (k *K9s) IsHeadless() bool {
if IsBoolSet(k.UI.manualHeadless) {
return true
}
return k.UI.Headless
}
// IsLogoless returns logoless setting.
func (k *K9s) IsLogoless() bool {
if IsBoolSet(k.UI.manualLogoless) {
return true
}
return k.UI.Logoless
}
// IsCrumbsless returns crumbsless setting.
func (k *K9s) IsCrumbsless() bool {
if IsBoolSet(k.UI.manualCrumbsless) {
return true
}
return k.UI.Crumbsless
}
// IsSplashless returns splashless setting.
func (k *K9s) IsSplashless() bool {
if IsBoolSet(k.UI.manualSplashless) {
return true
}
return k.UI.Splashless
}
// IsInvert returns invert setting.
func (k *K9s) IsInvert() bool {
if IsBoolSet(k.UI.manualInvert) {
return true
}
return k.UI.Invert
}
// GetRefreshRate returns the current refresh rate.
func (k *K9s) GetRefreshRate() float32 {
k.mx.Lock()
defer k.mx.Unlock()
rate := k.RefreshRate
if k.manualRefreshRate != 0 {
rate = k.manualRefreshRate
}
if rate < DefaultRefreshRate {
if !k.refreshRateWarned {
slog.Warn("Refresh rate is below minimum, capping to minimum value",
slogs.Requested, float64(rate),
slogs.Minimum, float64(DefaultRefreshRate))
k.refreshRateWarned = true
}
return DefaultRefreshRate
}
return rate
}
// RefreshDuration returns the refresh rate as a time.Duration.
func (k *K9s) RefreshDuration() time.Duration {
return time.Duration(k.GetRefreshRate() * float32(time.Second))
}
// IsReadOnly returns the readonly setting.
func (k *K9s) IsReadOnly() bool {
ro := k.ReadOnly
if cfg := k.getActiveConfig(); cfg != nil && cfg.Context.ReadOnly != nil {
ro = *cfg.Context.ReadOnly
}
if k.manualReadOnly != nil {
ro = *k.manualReadOnly
}
return ro
}
// Validate the current configuration.
func (k *K9s) Validate(c client.Connection, contextName, clusterName string) {
if k.RefreshRate <= 0 {
k.RefreshRate = defaultRefreshRate
}
if k.MaxConnRetry <= 0 {
k.MaxConnRetry = defaultMaxConnRetry
}
if a := os.Getenv(envPFAddress); a != "" {
k.PortForwardAddress = a
}
if k.PortForwardAddress == "" {
k.PortForwardAddress = defaultPFAddress()
}
if k.getActiveConfig() == nil {
_, _ = k.ActivateContext(contextName)
}
if k.ShellPod != nil {
k.ShellPod.Validate()
}
k.Logger = k.Logger.Validate()
k.Thresholds = k.Thresholds.Validate()
if cfg := k.getActiveConfig(); cfg != nil {
cfg.Validate(c, contextName, clusterName)
}
}
================================================
FILE: internal/config/k9s_int_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_k9sOverrides(t *testing.T) {
var (
trueVal = true
cmd = "po"
dir = "/tmp/blee"
)
uu := map[string]struct {
k *K9s
rate float32
ro, hl, cl, sl, ll bool
}{
"plain": {
k: &K9s{
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 10.0,
MaxConnRetry: 0,
ReadOnly: false,
NoExitOnCtrlC: false,
UI: UI{},
SkipLatestRevCheck: false,
DisablePodCounting: false,
},
rate: 10.0,
},
"sub-second": {
k: &K9s{
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 0.5,
MaxConnRetry: 0,
ReadOnly: false,
NoExitOnCtrlC: false,
UI: UI{},
SkipLatestRevCheck: false,
DisablePodCounting: false,
},
rate: 2.0, // minimum enforced
},
"set": {
k: &K9s{
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 10.0,
MaxConnRetry: 0,
ReadOnly: true,
NoExitOnCtrlC: false,
UI: UI{
Headless: true,
Logoless: true,
Crumbsless: true,
Splashless: true,
},
SkipLatestRevCheck: false,
DisablePodCounting: false,
},
rate: 10.0,
ro: true,
hl: true,
ll: true,
cl: true,
sl: true,
},
"overrides": {
k: &K9s{
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 10.0,
MaxConnRetry: 0,
ReadOnly: false,
NoExitOnCtrlC: false,
UI: UI{
Headless: false,
Logoless: false,
Crumbsless: false,
manualHeadless: &trueVal,
manualLogoless: &trueVal,
manualCrumbsless: &trueVal,
manualSplashless: &trueVal,
},
SkipLatestRevCheck: false,
DisablePodCounting: false,
manualRefreshRate: 100.0,
manualReadOnly: &trueVal,
manualCommand: &cmd,
manualScreenDumpDir: &dir,
},
rate: 100.0,
ro: true,
hl: true,
ll: true,
cl: true,
sl: true,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.InDelta(t, u.rate, u.k.GetRefreshRate(), 0.001)
assert.Equal(t, u.ro, u.k.IsReadOnly())
assert.Equal(t, u.cl, u.k.IsCrumbsless())
assert.Equal(t, u.sl, u.k.IsSplashless())
assert.Equal(t, u.hl, u.k.IsHeadless())
assert.Equal(t, u.ll, u.k.IsLogoless())
})
}
}
func Test_screenDumpDirOverride(t *testing.T) {
uu := map[string]struct {
dir string
e string
}{
"empty": {
e: "/tmp/k9s-test/screen-dumps",
},
"override": {
dir: "/tmp/k9s-test/sd",
e: "/tmp/k9s-test/sd",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cfg := NewConfig(nil)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.K9s.manualScreenDumpDir = &u.dir
assert.Equal(t, u.e, cfg.K9s.AppScreenDumpDir())
})
}
}
================================================
FILE: internal/config/k9s_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"errors"
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func TestK9sReload(t *testing.T) {
config.AppConfigDir = "/tmp/k9s-test"
cl, ct := "cl-1", "ct-1-1"
uu := map[string]struct {
k *config.K9s
cl, ct string
err error
}{
"no-context": {
k: config.NewK9s(
mock.NewMockConnection(),
mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{
ClusterName: &cl,
Context: &ct,
}),
),
err: errors.New(`no context found for: ""`),
},
"set-context": {
k: config.NewK9s(
mock.NewMockConnection(),
mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{
ClusterName: &cl,
Context: &ct,
}),
),
ct: "ct-1-1",
cl: "cl-1",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
_, _ = u.k.ActivateContext(u.ct)
assert.Equal(t, u.err, u.k.Reload())
ct, err := u.k.ActiveContext()
assert.Equal(t, u.err, err)
if err == nil {
assert.Equal(t, u.cl, ct.ClusterName)
}
})
}
}
func TestK9sMerge(t *testing.T) {
cl, ct := "cl-1", "ct-1-1"
uu := map[string]struct {
k1, k2 *config.K9s
ek *config.K9s
}{
"no-opt": {
k1: config.NewK9s(
mock.NewMockConnection(),
mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{
ClusterName: &cl,
Context: &ct,
}),
),
ek: config.NewK9s(
mock.NewMockConnection(),
mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{
ClusterName: &cl,
Context: &ct,
}),
),
},
"override": {
k1: &config.K9s{
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 0,
MaxConnRetry: 0,
ReadOnly: false,
NoExitOnCtrlC: false,
UI: config.UI{},
SkipLatestRevCheck: false,
DisablePodCounting: false,
ShellPod: new(config.ShellPod),
ImageScans: config.ImageScans{},
Logger: config.Logger{},
Thresholds: nil,
},
k2: &config.K9s{
LiveViewAutoRefresh: true,
MaxConnRetry: 100,
ShellPod: config.NewShellPod(),
},
ek: &config.K9s{
LiveViewAutoRefresh: true,
ScreenDumpDir: "",
RefreshRate: 0,
MaxConnRetry: 100,
ReadOnly: false,
NoExitOnCtrlC: false,
UI: config.UI{},
SkipLatestRevCheck: false,
DisablePodCounting: false,
ShellPod: config.NewShellPod(),
ImageScans: config.ImageScans{},
Logger: config.Logger{},
Thresholds: nil,
},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
u.k1.Merge(u.k2)
assert.Equal(t, u.ek, u.k1)
})
}
}
func TestContextScreenDumpDir(t *testing.T) {
cfg := mock.NewMockConfig(t)
_, err := cfg.K9s.ActivateContext("ct-1-1")
require.NoError(t, err)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, "/tmp/k9s-test/screen-dumps/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir())
}
func TestAppScreenDumpDir(t *testing.T) {
cfg := mock.NewMockConfig(t)
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, "/tmp/k9s-test/screen-dumps", cfg.K9s.AppScreenDumpDir())
}
================================================
FILE: internal/config/logger.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
const (
// DefaultLoggerTailCount tracks default log tail size.
DefaultLoggerTailCount = 100
// MaxLogThreshold sets the max value for log size.
MaxLogThreshold = 5_000
// DefaultSinceSeconds tracks default log age.
DefaultSinceSeconds = -1 // tail logs by default
)
// Logger tracks logger options.
type Logger struct {
TailCount int64 `json:"tail" yaml:"tail"`
BufferSize int `json:"buffer" yaml:"buffer"`
SinceSeconds int64 `json:"sinceSeconds" yaml:"sinceSeconds"`
TextWrap bool `json:"textWrap" yaml:"textWrap"`
DisableAutoscroll bool `json:"disableAutoscroll" yaml:"disableAutoscroll"`
ColumnLock bool `json:"columnLock" yaml:"columnLock"`
ShowTime bool `json:"showTime" yaml:"showTime"`
}
// NewLogger returns a new instance.
func NewLogger() Logger {
return Logger{
TailCount: DefaultLoggerTailCount,
BufferSize: MaxLogThreshold,
SinceSeconds: DefaultSinceSeconds,
}
}
// Validate checks thresholds and make sure we're cool. If not use defaults.
func (l Logger) Validate() Logger {
if l.TailCount <= 0 {
l.TailCount = DefaultLoggerTailCount
}
if l.TailCount > MaxLogThreshold {
l.TailCount = MaxLogThreshold
}
if l.BufferSize <= 0 || l.BufferSize > MaxLogThreshold {
l.BufferSize = MaxLogThreshold
}
if l.SinceSeconds == 0 {
l.SinceSeconds = DefaultSinceSeconds
}
return l
}
================================================
FILE: internal/config/logger_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestNewLogger(t *testing.T) {
l := config.NewLogger()
l = l.Validate()
assert.Equal(t, int64(100), l.TailCount)
assert.Equal(t, 5000, l.BufferSize)
}
func TestLoggerValidate(t *testing.T) {
var l config.Logger
l = l.Validate()
assert.Equal(t, int64(100), l.TailCount)
assert.Equal(t, 5000, l.BufferSize)
}
================================================
FILE: internal/config/mock/test_helpers.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package mock
import (
"errors"
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"strings"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/require"
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); errors.Is(err, fs.ErrNotExist) {
return os.MkdirAll(d, 0700)
}
if err := os.RemoveAll(d); err != nil {
return err
}
return os.MkdirAll(d, 0700)
}
func NewMockConfig(t testing.TB) *config.Config {
if _, err := os.Stat("/tmp/test"); err == nil {
if e := os.RemoveAll("/tmp/test"); e != nil {
require.NoError(t, e)
}
}
config.AppContextsDir = "/tmp/test"
cl, ct := "cl-1", "ct-1-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-2",
},
ctId + "-3": {
Cluster: *f.ClusterName,
Namespace: client.DefaultNamespace,
},
"fred-blee": {
Cluster: "arn:aws:eks:eu-central-1:xxx:cluster/fred-blee",
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 (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
}
func (mockKubeSettings) SetProxy(func(*http.Request) (*url.URL, error)) {}
type mockConnection struct {
ct string
}
func NewMockConnection() mockConnection {
return mockConnection{}
}
func NewMockConnectionWithContext(ct string) mockConnection {
return mockConnection{ct: ct}
}
func (mockConnection) CanI(string, *client.GVR, string, []string) (bool, error) {
return true, nil
}
func (mockConnection) Config() *client.Config {
return nil
}
func (mockConnection) ConnectionOK() bool {
return false
}
func (mockConnection) Dial() (kubernetes.Interface, error) {
return nil, nil
}
func (mockConnection) DialLogs() (kubernetes.Interface, error) {
return nil, nil
}
func (mockConnection) SwitchContext(string) error {
return nil
}
func (mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) {
return nil, nil
}
func (mockConnection) RestConfig() (*restclient.Config, error) {
return nil, nil
}
func (mockConnection) MXDial() (*versioned.Clientset, error) {
return nil, nil
}
func (mockConnection) DynDial() (dynamic.Interface, error) {
return nil, nil
}
func (mockConnection) HasMetrics() bool {
return false
}
func (mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) {
return nil, nil
}
func (mockConnection) IsValidNamespace(string) bool {
return true
}
func (mockConnection) ServerVersion() (*version.Info, error) {
return nil, nil
}
func (mockConnection) CheckConnectivity() bool {
return false
}
func (m mockConnection) ActiveContext() string {
return m.ct
}
func (mockConnection) ActiveNamespace() string {
return ""
}
func (mockConnection) IsActiveNamespace(string) bool {
return false
}
================================================
FILE: internal/config/plugin.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"bytes"
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"github.com/karrick/godirwalk"
"gopkg.in/yaml.v3"
)
type plugins map[string]Plugin
// Plugins represents a collection of plugins.
type Plugins struct {
Plugins plugins `yaml:"plugins"`
}
// PluginInputType represents the type of input field.
type PluginInputType string
const (
InputTypeString PluginInputType = "string"
InputTypeNumber PluginInputType = "number"
InputTypeBool PluginInputType = "bool"
InputTypeDropdown PluginInputType = "dropdown"
)
// PluginInput describes an input field for a plugin.
type PluginInput struct {
Name string `yaml:"name"`
Label string `yaml:"label"`
Type PluginInputType `yaml:"type"`
Required bool `yaml:"required"`
Default string `yaml:"default"`
Options []string `yaml:"options"`
}
// Plugin describes a K9s plugin.
type Plugin struct {
Scopes []string `yaml:"scopes"`
Args []string `yaml:"args"`
ShortCut string `yaml:"shortCut"`
Override bool `yaml:"override"`
Pipes []string `yaml:"pipes"`
Description string `yaml:"description"`
Command string `yaml:"command"`
Confirm *bool `yaml:"confirm"`
Background bool `yaml:"background"`
Dangerous bool `yaml:"dangerous"`
OverwriteOutput bool `yaml:"overwriteOutput"`
Inputs []PluginInput `yaml:"inputs"`
}
func (p Plugin) String() string {
return fmt.Sprintf("[%s] %s(%s)", p.ShortCut, p.Command, strings.Join(p.Args, " "))
}
// ShouldConfirm returns whether the plugin should show a confirmation dialog.
// Defaults to true when inputs are defined, false otherwise.
func (p *Plugin) ShouldConfirm() bool {
if p.Confirm != nil {
return *p.Confirm
}
return len(p.Inputs) > 0
}
// Validate checks the plugin configuration for errors.
func (p *Plugin) Validate() error {
seen := make(map[string]struct{}, len(p.Inputs))
for _, input := range p.Inputs {
if _, ok := seen[input.Name]; ok {
return fmt.Errorf("duplicate input name %q", input.Name)
}
seen[input.Name] = struct{}{}
if input.Default == "" {
continue
}
switch input.Type {
case InputTypeDropdown:
if !slices.Contains(input.Options, input.Default) {
return fmt.Errorf("default value %q for input %q is not a valid option", input.Default, input.Name)
}
case InputTypeBool:
if input.Default != "true" && input.Default != "false" {
return fmt.Errorf("default value %q for bool input %q must be \"true\" or \"false\"", input.Default, input.Name)
}
case InputTypeNumber:
if _, err := strconv.ParseFloat(input.Default, 64); err != nil {
return fmt.Errorf("default value %q for number input %q is not a valid number", input.Default, input.Name)
}
}
}
return nil
}
// NewPlugins returns a new plugin.
func NewPlugins() Plugins {
return Plugins{
Plugins: make(map[string]Plugin),
}
}
// Load K9s plugins.
func (p Plugins) Load(path string, loadExtra bool) error {
var errs error
// Load from global config file
if err := p.load(AppPluginsFile); err != nil {
errs = errors.Join(errs, err)
}
// Load from cluster/context config
if err := p.load(path); err != nil {
errs = errors.Join(errs, err)
}
if !loadExtra {
return errs
}
// Load from XDG dirs
const k9sPluginsDir = "k9s/plugins"
for _, dir := range append(xdg.DataDirs, xdg.DataHome, xdg.ConfigHome) {
path := filepath.Join(dir, k9sPluginsDir)
if err := p.loadDir(path); err != nil {
errs = errors.Join(errs, err)
}
}
return errs
}
func (p *Plugins) load(path string) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
bb, err := os.ReadFile(path)
if err != nil {
return err
}
scheme, err := data.JSONValidator.ValidatePlugins(bb)
if err != nil {
slog.Warn("Plugin schema validation failed",
slogs.Path, path,
slogs.Error, err,
)
return fmt.Errorf("plugin validation failed for %s: %w", path, err)
}
d := yaml.NewDecoder(bytes.NewReader(bb))
d.KnownFields(true)
switch scheme {
case json.PluginSchema:
var o Plugin
if err := yaml.Unmarshal(bb, &o); err != nil {
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
}
if err := o.Validate(); err != nil {
return fmt.Errorf("plugin validation failed for %s: %w", path, err)
}
p.Plugins[strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))] = o
case json.PluginsSchema:
var oo Plugins
if err := yaml.Unmarshal(bb, &oo); err != nil {
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
}
for k := range oo.Plugins {
plug := oo.Plugins[k]
if err := plug.Validate(); err != nil {
return fmt.Errorf("plugin %q validation failed for %s: %w", k, path, err)
}
p.Plugins[k] = plug
}
case json.PluginMultiSchema:
var oo plugins
if err := yaml.Unmarshal(bb, &oo); err != nil {
return fmt.Errorf("plugin unmarshal failed for %s: %w", path, err)
}
for k := range oo {
plug := oo[k]
if err := plug.Validate(); err != nil {
return fmt.Errorf("plugin %q validation failed for %s: %w", k, path, err)
}
p.Plugins[k] = plug
}
}
return nil
}
func (p Plugins) loadDir(dir string) error {
if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
return nil
}
var errs error
errs = errors.Join(errs, godirwalk.Walk(dir, &godirwalk.Options{
FollowSymbolicLinks: true,
Callback: func(path string, de *godirwalk.Dirent) error {
if de.IsDir() || !isYamlFile(de.Name()) {
return nil
}
errs = errors.Join(errs, p.load(path))
return nil
},
ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
slog.Warn("Error at %s: %v - skipping node", slogs.Path, osPathname, slogs.Error, err)
return godirwalk.SkipNode
},
}))
return errs
}
================================================
FILE: internal/config/plugin_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPluginLoad(t *testing.T) {
uu := map[string]struct {
path string
err string
ee Plugins
}{
"snippet": {
path: "testdata/plugins/dir/snippet.1.yaml",
ee: Plugins{
Plugins: plugins{
"snippet.1": Plugin{
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
ShortCut: "shift-s",
Description: "blee",
Command: "duh",
Confirm: boolPtr(true),
OverwriteOutput: true,
},
},
},
},
"multi-snippets": {
path: "testdata/plugins/dir/snippet.multi.yaml",
ee: Plugins{
Plugins: plugins{
"crapola": Plugin{
ShortCut: "Shift-1",
Command: "crapola",
Description: "crapola",
Scopes: []string{"pods"},
},
"bozo": Plugin{
ShortCut: "Shift-2",
Description: "bozo",
Command: "bozo",
Scopes: []string{"pods", "svc"},
},
},
},
},
"full": {
path: "testdata/plugins/plugins.yaml",
ee: Plugins{
Plugins: plugins{
"blah": Plugin{
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
ShortCut: "shift-s",
Description: "blee",
Command: "duh",
Confirm: boolPtr(true),
},
},
},
},
"toast-no-file": {
path: "testdata/plugins/plugins-bozo.yaml",
ee: NewPlugins(),
},
"toast-invalid": {
path: "testdata/plugins/plugins-toast.yaml",
ee: NewPlugins(),
err: "plugin validation failed for testdata/plugins/plugins-toast.yaml: scopes is required\nAdditional property plugins is not allowed\ncommand is required\ndescription is required\nscopes is required\nshortCut is required\ncommand is required\ndescription is required\nscopes is required\nshortCut is required",
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
p := NewPlugins()
err := p.Load(u.path, false)
if err != nil {
assert.Equal(t, u.err, err.Error())
}
assert.Equal(t, u.ee, p)
})
}
}
func TestSinglePluginFileLoad(t *testing.T) {
e := Plugin{
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
ShortCut: "shift-s",
Description: "blee",
Command: "duh",
Confirm: boolPtr(true),
}
p := NewPlugins()
require.NoError(t, p.load("testdata/plugins/plugins.yaml"))
require.NoError(t, p.loadDir("/random/dir/not/exist"))
assert.Len(t, p.Plugins, 1)
v, ok := p.Plugins["blah"]
assert.True(t, ok)
assert.ObjectsAreEqual(e, v)
}
func TestMultiplePluginFilesLoad(t *testing.T) {
uu := map[string]struct {
path string
dir string
ee Plugins
}{
"empty": {
path: "testdata/plugins/plugins.yaml",
dir: "testdata/plugins/dir",
ee: Plugins{
Plugins: plugins{
"blah": {
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
ShortCut: "shift-s",
Description: "blee",
Command: "duh",
Confirm: boolPtr(true),
},
"snippet.1": {
ShortCut: "shift-s",
Command: "duh",
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
Description: "blee",
Confirm: boolPtr(true),
OverwriteOutput: true,
},
"snippet.2": {
Scopes: []string{"svc", "ing"},
Args: []string{"-n", "$NAMESPACE", "-oyaml"},
ShortCut: "shift-r",
Description: "bla",
Command: "duha",
Confirm: boolPtr(false),
Background: true,
},
"crapola": {
Scopes: []string{"pods"},
Command: "crapola",
Description: "crapola",
ShortCut: "Shift-1",
},
"bozo": {
Scopes: []string{"pods", "svc"},
Command: "bozo",
Description: "bozo",
ShortCut: "Shift-2",
},
},
},
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
p := NewPlugins()
require.NoError(t, p.load(u.path))
require.NoError(t, p.loadDir(u.dir))
assert.Equal(t, u.ee, p)
})
}
}
func TestPluginLoadSymlink(t *testing.T) {
tmp := t.TempDir()
linkFile := filepath.Join(tmp, "plugins-symlink.yaml")
wd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Symlink(filepath.Join(wd, "testdata", "plugins", "plugins.yaml"), linkFile))
linkDir := filepath.Join(tmp, "plugins-dir-symlink")
require.NoError(t, os.Symlink(filepath.Join(wd, "testdata", "plugins", "dir"), linkDir))
// Add a symlink with an infinite loop
loopDir := filepath.Join(tmp, "loop")
require.NoError(t, os.Mkdir(loopDir, 0o755))
require.NoError(t, os.Symlink(loopDir, filepath.Join(loopDir, "self")))
p := NewPlugins()
require.NoError(t, p.loadDir(tmp))
ee := Plugins{
Plugins: plugins{
"blah": Plugin{
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
ShortCut: "shift-s",
Description: "blee",
Command: "duh",
Confirm: boolPtr(true),
},
"snippet.1": {
ShortCut: "shift-s",
Command: "duh",
Scopes: []string{"po", "dp"},
Args: []string{"-n", "$NAMESPACE", "-boolean"},
Description: "blee",
Confirm: boolPtr(true),
OverwriteOutput: true,
},
"snippet.2": {
Scopes: []string{"svc", "ing"},
Args: []string{"-n", "$NAMESPACE", "-oyaml"},
ShortCut: "shift-r",
Description: "bla",
Command: "duha",
Confirm: boolPtr(false),
Background: true,
},
"crapola": {
Scopes: []string{"pods"},
Command: "crapola",
Description: "crapola",
ShortCut: "Shift-1",
},
"bozo": {
Scopes: []string{"pods", "svc"},
Command: "bozo",
Description: "bozo",
ShortCut: "Shift-2",
},
},
}
assert.Equal(t, ee, p)
}
================================================
FILE: internal/config/refresh_rate_test.go
================================================
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestRefreshRateBackwardCompatibility(t *testing.T) {
tests := map[string]struct {
yamlContent string
expected float32
}{
"integer_value": {
yamlContent: `refreshRate: 2`,
expected: 2.0,
},
"float_value": {
yamlContent: `refreshRate: 2.5`,
expected: 2.5,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var k K9s
err := yaml.Unmarshal([]byte(test.yamlContent), &k)
require.NoError(t, err)
assert.InDelta(t, test.expected, k.RefreshRate, 0.001)
})
}
}
func TestGetRefreshRateMinimum(t *testing.T) {
tests := map[string]struct {
refreshRate float32
manualRefreshRate float32
expected float32
}{
"below_minimum": {
refreshRate: 0.5,
expected: 2.0,
},
"at_minimum": {
refreshRate: 2.0,
expected: 2.0,
},
"above_minimum": {
refreshRate: 3.5,
expected: 3.5,
},
"manual_below_minimum": {
refreshRate: 3.0,
manualRefreshRate: 0.5,
expected: 2.0,
},
"manual_above_minimum": {
refreshRate: 2.0,
manualRefreshRate: 4.0,
expected: 4.0,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
k := K9s{
RefreshRate: test.refreshRate,
manualRefreshRate: test.manualRefreshRate,
}
assert.InDelta(t, test.expected, k.GetRefreshRate(), 0.001)
})
}
}
================================================
FILE: internal/config/scans.go
================================================
// 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
}
// ScanExcludes tracks vul scan exclusions.
type ScanExcludes struct {
Namespaces []string `json:"namespaces" yaml:"namespaces"`
Labels Labels `json:"labels" yaml:"labels"`
}
func newScanExcludes() ScanExcludes {
return ScanExcludes{
Labels: make(Labels),
}
}
func (b ScanExcludes) 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 `json:"enable" yaml:"enable"`
Exclusions ScanExcludes `json:"exclusions" yaml:"exclusions"`
}
// NewImageScans returns a new instance.
func NewImageScans() ImageScans {
return ImageScans{
Exclusions: newScanExcludes(),
}
}
// ShouldExclude checks if scan should be excluded given ns/labels
func (i ImageScans) ShouldExclude(ns string, ll map[string]string) bool {
if !i.Enable {
return false
}
return i.Exclusions.exclude(ns, ll)
}
================================================
FILE: internal/config/scans_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestScansShouldExclude(t *testing.T) {
uu := map[string]struct {
sc config.ImageScans
ns string
ll map[string]string
e bool
}{
"empty": {
sc: config.NewImageScans(),
},
"exclude-ns": {
sc: config.ImageScans{
Enable: true,
Exclusions: config.ScanExcludes{
Namespaces: []string{"ns-1", "ns-2", "ns-3"},
Labels: config.Labels{
"app": []string{"fred", "blee"},
},
},
},
ns: "ns-1",
ll: map[string]string{
"app": "freddy",
},
e: true,
},
"include-ns": {
sc: config.ImageScans{
Enable: true,
Exclusions: config.ScanExcludes{
Namespaces: []string{"ns-1", "ns-2", "ns-3"},
Labels: config.Labels{
"app": []string{"fred", "blee"},
},
},
},
ns: "ns-4",
ll: map[string]string{
"app": "bozo",
},
},
"exclude-labels": {
sc: config.ImageScans{
Enable: true,
Exclusions: config.ScanExcludes{
Namespaces: []string{"ns-1", "ns-2", "ns-3"},
Labels: config.Labels{
"app": []string{"fred", "blee"},
},
},
},
ns: "ns-4",
ll: map[string]string{
"app": "fred",
},
e: true,
},
"include-labels": {
sc: config.ImageScans{
Enable: true,
Exclusions: config.ScanExcludes{
Namespaces: []string{"ns-1", "ns-2", "ns-3"},
Labels: config.Labels{
"app": []string{"fred", "blee"},
},
},
},
ns: "ns-4",
ll: map[string]string{
"app": "freddy",
},
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.sc.ShouldExclude(u.ns, u.ll))
})
}
}
================================================
FILE: internal/config/shell_pod.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
v1 "k8s.io/api/core/v1"
)
const defaultDockerShellImage = "busybox:1.37.0"
// Limits represents resource limits.
type Limits map[v1.ResourceName]string
// ShellPod represents k9s shell configuration.
type ShellPod struct {
Image string `json:"image" yaml:"image"`
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
Namespace string `json:"namespace" yaml:"namespace"`
Limits Limits `json:"limits,omitempty" yaml:"limits,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"`
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
TTY bool `json:"tty,omitempty" yaml:"tty,omitempty"`
HostPathVolume []hostPathVolume `json:"hostPathVolume,omitempty" yaml:"hostPathVolume,omitempty"`
}
type hostPathVolume struct {
Name string `json:"name" yaml:"name"`
MountPath string `json:"mountPath" yaml:"mountPath"`
HostPath string `json:"hostPath" yaml:"hostPath"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
}
// NewShellPod returns a new instance.
func NewShellPod() *ShellPod {
return &ShellPod{
Image: defaultDockerShellImage,
Namespace: "default",
Limits: defaultLimits(),
}
}
// Validate validates the configuration.
func (s *ShellPod) Validate() {
if s.Image == "" {
s.Image = defaultDockerShellImage
}
if len(s.Limits) == 0 {
s.Limits = defaultLimits()
}
}
func defaultLimits() Limits {
return Limits{
v1.ResourceCPU: "100m",
v1.ResourceMemory: "100Mi",
}
}
================================================
FILE: internal/config/styles.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"fmt"
"os"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/tcell/v2"
"github.com/derailed/tview"
"gopkg.in/yaml.v3"
)
// StyleListener represents a skin's listener.
type StyleListener interface {
// StylesChanged notifies listener the skin changed.
StylesChanged(*Styles)
}
// TextStyle tracks text styles.
type TextStyle string
const (
// TextStyleNormal is the default text style.
TextStyleNormal TextStyle = "normal"
// TextStyleBold is the bold text style.
TextStyleBold TextStyle = "bold"
// TextStyleDim is the dim text style.
TextStyleDim TextStyle = "dim"
)
// ToShortString returns a short string representation of the text style.
func (ts TextStyle) ToShortString() string {
switch ts {
case TextStyleNormal:
return "-"
case TextStyleBold:
return "b"
case TextStyleDim:
return "d"
default:
return "d"
}
}
type (
// Styles tracks K9s styling options.
Styles struct {
K9s Style `json:"k9s" yaml:"k9s"`
listeners []StyleListener
}
// Style tracks K9s styles.
Style struct {
Body Body `json:"body" yaml:"body"`
Prompt Prompt `json:"prompt" yaml:"prompt"`
Help Help `json:"help" yaml:"help"`
Frame Frame `json:"frame" yaml:"frame"`
Info Info `json:"info" yaml:"info"`
Views Views `json:"views" yaml:"views"`
Dialog Dialog `json:"dialog" yaml:"dialog"`
}
// Prompt tracks command styles
Prompt struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
SuggestColor Color `json:"" yaml:"suggestColor"`
Border PromptBorder `json:"" yaml:"border"`
}
// PromptBorder tracks the color of the prompt depending on its kind (e.g., command or filter)
PromptBorder struct {
CommandColor Color `json:"command" yaml:"command"`
DefaultColor Color `json:"default" yaml:"default"`
}
// Help tracks help styles.
Help struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
SectionColor Color `json:"sectionColor" yaml:"sectionColor"`
KeyColor Color `json:"keyColor" yaml:"keyColor"`
NumKeyColor Color `json:"numKeyColor" yaml:"numKeyColor"`
}
// Body tracks body styles.
Body struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
LogoColor Color `json:"logoColor" yaml:"logoColor"`
LogoColorMsg Color `json:"logoColorMsg" yaml:"logoColorMsg"`
LogoColorInfo Color `json:"logoColorInfo" yaml:"logoColorInfo"`
LogoColorWarn Color `json:"logoColorWarn" yaml:"logoColorWarn"`
LogoColorError Color `json:"logoColorError" yaml:"logoColorError"`
}
// Dialog tracks dialog styles.
Dialog struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
ButtonFgColor Color `json:"buttonFgColor" yaml:"buttonFgColor"`
ButtonBgColor Color `json:"buttonBgColor" yaml:"buttonBgColor"`
ButtonFocusFgColor Color `json:"buttonFocusFgColor" yaml:"buttonFocusFgColor"`
ButtonFocusBgColor Color `json:"buttonFocusBgColor" yaml:"buttonFocusBgColor"`
LabelFgColor Color `json:"labelFgColor" yaml:"labelFgColor"`
FieldFgColor Color `json:"fieldFgColor" yaml:"fieldFgColor"`
}
// Frame tracks frame styles.
Frame struct {
Title Title `json:"title" yaml:"title"`
Border Border `json:"border" yaml:"border"`
Menu Menu `json:"menu" yaml:"menu"`
Crumb Crumb `json:"crumbs" yaml:"crumbs"`
Status Status `json:"status" yaml:"status"`
}
// Views tracks individual view styles.
Views struct {
Table Table `json:"table" yaml:"table"`
Xray Xray `json:"xray" yaml:"xray"`
Charts Charts `json:"charts" yaml:"charts"`
Yaml Yaml `json:"yaml" yaml:"yaml"`
Picker Picker `json:"picker" yaml:"picker"`
Log Log `json:"logs" yaml:"logs"`
}
// Status tracks resource status styles.
Status struct {
NewColor Color `json:"newColor" yaml:"newColor"`
ModifyColor Color `json:"modifyColor" yaml:"modifyColor"`
AddColor Color `json:"addColor" yaml:"addColor"`
PendingColor Color `json:"pendingColor" yaml:"pendingColor"`
ErrorColor Color `json:"errorColor" yaml:"errorColor"`
HighlightColor Color `json:"highlightColor" yaml:"highlightColor"`
KillColor Color `json:"killColor" yaml:"killColor"`
CompletedColor Color `json:"completedColor" yaml:"completedColor"`
}
// Log tracks Log styles.
Log struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
Indicator LogIndicator `json:"indicator" yaml:"indicator"`
}
// Picker tracks color when selecting containers
Picker struct {
MainColor Color `json:"mainColor" yaml:"mainColor"`
FocusColor Color `json:"focusColor" yaml:"focusColor"`
ShortcutColor Color `json:"shortcutColor" yaml:"shortcutColor"`
}
// LogIndicator tracks log view indicator.
LogIndicator struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
ToggleOnColor Color `json:"toggleOnColor" yaml:"toggleOnColor"`
ToggleOffColor Color `json:"toggleOffColor" yaml:"toggleOffColor"`
}
// Yaml tracks yaml styles.
Yaml struct {
KeyColor Color `json:"keyColor" yaml:"keyColor"`
ValueColor Color `json:"valueColor" yaml:"valueColor"`
ColonColor Color `json:"colonColor" yaml:"colonColor"`
}
// Title tracks title styles.
Title struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
HighlightColor Color `json:"highlightColor" yaml:"highlightColor"`
CounterColor Color `json:"counterColor" yaml:"counterColor"`
FilterColor Color `json:"filterColor" yaml:"filterColor"`
}
// Info tracks info styles.
Info struct {
SectionColor Color `json:"sectionColor" yaml:"sectionColor"`
FgColor Color `json:"fgColor" yaml:"fgColor"`
CPUColor Color `json:"cpuColor" yaml:"cpuColor"`
MEMColor Color `json:"memColor" yaml:"memColor"`
K9sRevColor Color `json:"k9sRevColor" yaml:"k9sRevColor"`
}
// Border tracks border styles.
Border struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
FocusColor Color `json:"focusColor" yaml:"focusColor"`
}
// Crumb tracks crumbs styles.
Crumb struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
ActiveColor Color `json:"activeColor" yaml:"activeColor"`
}
// Table tracks table styles.
Table struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
CursorFgColor Color `json:"cursorFgColor" yaml:"cursorFgColor"`
CursorBgColor Color `json:"cursorBgColor" yaml:"cursorBgColor"`
MarkColor Color `json:"markColor" yaml:"markColor"`
Header TableHeader `json:"header" yaml:"header"`
}
// TableHeader tracks table header styles.
TableHeader struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
SorterColor Color `json:"sorterColor" yaml:"sorterColor"`
SelectedSortColumnColor Color `json:"selectedSortColumnColor" yaml:"selectedSortColumnColor"`
}
// Xray tracks xray styles.
Xray struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
BgColor Color `json:"bgColor" yaml:"bgColor"`
CursorColor Color `json:"cursorColor" yaml:"cursorColor"`
CursorTextColor Color `json:"cursorTextColor" yaml:"cursorTextColor"`
GraphicColor Color `json:"graphicColor" yaml:"graphicColor"`
}
// Menu tracks menu styles.
Menu struct {
FgColor Color `json:"fgColor" yaml:"fgColor"`
FgStyle TextStyle `json:"fgStyle" yaml:"fgStyle"`
KeyColor Color `json:"keyColor" yaml:"keyColor"`
NumKeyColor Color `json:"numKeyColor" yaml:"numKeyColor"`
}
// Charts tracks charts styles.
Charts struct {
BgColor Color `json:"bgColor" yaml:"bgColor"`
DialBgColor Color `json:"dialBgColor" yaml:"dialBgColor"`
ChartBgColor Color `json:"chartBgColor" yaml:"chartBgColor"`
DefaultDialColors Colors `json:"defaultDialColors" yaml:"defaultDialColors"`
DefaultChartColors Colors `json:"defaultChartColors" yaml:"defaultChartColors"`
ResourceColors map[string]Colors `json:"resourceColors" yaml:"resourceColors"`
FocusFgColor Color `yaml:"focusFgColor"`
FocusBgColor Color `yaml:"focusBgColor"`
}
)
func newStyle() Style {
return Style{
Body: newBody(),
Prompt: newPrompt(),
Help: newHelp(),
Frame: newFrame(),
Info: newInfo(),
Views: newViews(),
Dialog: newDialog(),
}
}
func newDialog() Dialog {
return Dialog{
FgColor: "cadetblue",
BgColor: "black",
ButtonBgColor: "darkslateblue",
ButtonFgColor: "black",
ButtonFocusBgColor: "dodgerblue",
ButtonFocusFgColor: "black",
LabelFgColor: "white",
FieldFgColor: "white",
}
}
func newPrompt() Prompt {
return Prompt{
FgColor: "cadetblue",
BgColor: "black",
SuggestColor: "dodgerblue",
Border: PromptBorder{
DefaultColor: "seagreen",
CommandColor: "aqua",
},
}
}
func newCharts() Charts {
return Charts{
BgColor: "black",
DialBgColor: "black",
ChartBgColor: "black",
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
ResourceColors: map[string]Colors{
CPU: {Color("dodgerblue"), Color("darkslateblue")},
MEM: {Color("yellow"), Color("goldenrod")},
},
FocusFgColor: "white",
FocusBgColor: "orange",
}
}
func newViews() Views {
return Views{
Table: newTable(),
Xray: newXray(),
Charts: newCharts(),
Yaml: newYaml(),
Picker: newPicker(),
Log: newLog(),
}
}
func newFrame() Frame {
return Frame{
Title: newTitle(),
Border: newBorder(),
Menu: newMenu(),
Crumb: newCrumb(),
Status: newStatus(),
}
}
func newHelp() Help {
return Help{
FgColor: "cadetblue",
BgColor: "black",
SectionColor: "green",
KeyColor: "dodgerblue",
NumKeyColor: "fuchsia",
}
}
func newBody() Body {
return Body{
FgColor: "cadetblue",
BgColor: "black",
LogoColor: "orange",
LogoColorMsg: "white",
LogoColorInfo: "green",
LogoColorWarn: "mediumvioletred",
LogoColorError: "red",
}
}
func newStatus() Status {
return Status{
NewColor: "lightskyblue",
ModifyColor: "greenyellow",
AddColor: "dodgerblue",
PendingColor: "darkorange",
ErrorColor: "orangered",
HighlightColor: "aqua",
KillColor: "mediumpurple",
CompletedColor: "lightslategray",
}
}
func newPicker() Picker {
return Picker{
MainColor: "white",
FocusColor: "aqua",
ShortcutColor: "aqua",
}
}
func newLog() Log {
return Log{
FgColor: "lightskyblue",
BgColor: "black",
Indicator: newLogIndicator(),
}
}
func newLogIndicator() LogIndicator {
return LogIndicator{
FgColor: "dodgerblue",
BgColor: "black",
ToggleOnColor: "limegreen",
ToggleOffColor: "gray",
}
}
func newYaml() Yaml {
return Yaml{
KeyColor: "steelblue",
ColonColor: "white",
ValueColor: "papayawhip",
}
}
func newTitle() Title {
return Title{
FgColor: "aqua",
BgColor: "black",
HighlightColor: "fuchsia",
CounterColor: "papayawhip",
FilterColor: "seagreen",
}
}
func newInfo() Info {
return Info{
SectionColor: "white",
FgColor: "orange",
CPUColor: "lawngreen",
MEMColor: "darkturquoise",
K9sRevColor: "aqua",
}
}
func newXray() Xray {
return Xray{
FgColor: "aqua",
BgColor: "black",
CursorColor: "dodgerblue",
CursorTextColor: "black",
GraphicColor: "cadetblue",
}
}
func newTable() Table {
return Table{
FgColor: "aqua",
BgColor: "black",
CursorFgColor: "black",
CursorBgColor: "aqua",
MarkColor: "palegreen",
Header: newTableHeader(),
}
}
func newTableHeader() TableHeader {
return TableHeader{
FgColor: "white",
BgColor: "black",
SorterColor: "aqua",
SelectedSortColumnColor: "lightskyblue",
}
}
func newCrumb() Crumb {
return Crumb{
FgColor: "black",
BgColor: "aqua",
ActiveColor: "orange",
}
}
func newBorder() Border {
return Border{
FgColor: "dodgerblue",
FocusColor: "lightskyblue",
}
}
func newMenu() Menu {
return Menu{
FgColor: "white",
KeyColor: "dodgerblue",
NumKeyColor: "fuchsia",
}
}
// 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(),
}
}
// Reset resets styles.
func (s *Styles) Reset(invert bool) {
if err := yaml.Unmarshal(stockSkinTpl, s); err != nil {
s.K9s = newStyle()
}
if invert {
s.K9s.Invert()
}
}
// FgColor returns the foreground color.
func (s *Styles) FgColor() tcell.Color {
return s.Body().FgColor.Color()
}
// BgColor returns the background color.
func (s *Styles) BgColor() tcell.Color {
return s.Body().BgColor.Color()
}
// AddListener registers a new listener.
func (s *Styles) AddListener(l StyleListener) {
s.listeners = append(s.listeners, l)
}
// RemoveListener removes a listener.
func (s *Styles) RemoveListener(l StyleListener) {
victim := -1
for i, lis := range s.listeners {
if lis == l {
victim = i
break
}
}
if victim == -1 {
return
}
s.listeners = append(s.listeners[:victim], s.listeners[victim+1:]...)
}
func (s *Styles) fireStylesChanged() {
for _, list := range s.listeners {
list.StylesChanged(s)
}
}
// Body returns body styles.
func (s *Styles) Body() Body {
return s.K9s.Body
}
// Prompt returns prompt styles.
func (s *Styles) Prompt() Prompt {
return s.K9s.Prompt
}
// Frame returns frame styles.
func (s *Styles) Frame() Frame {
return s.K9s.Frame
}
// Crumb returns crumb styles.
func (s *Styles) Crumb() Crumb {
return s.Frame().Crumb
}
// Title returns title styles.
func (s *Styles) Title() Title {
return s.Frame().Title
}
// Charts returns charts styles.
func (s *Styles) Charts() Charts {
return s.K9s.Views.Charts
}
// Dialog returns dialog styles.
func (s *Styles) Dialog() Dialog {
return s.K9s.Dialog
}
// Table returns table styles.
func (s *Styles) Table() Table {
return s.K9s.Views.Table
}
// Xray returns xray styles.
func (s *Styles) Xray() Xray {
return s.K9s.Views.Xray
}
// Views returns views styles.
func (s *Styles) Views() Views {
return s.K9s.Views
}
// Invert inverts all colors in the Style.
func (s *Style) Invert() {
s.Body.Invert()
s.Prompt.Invert()
s.Help.Invert()
s.Frame.Invert()
s.Info.Invert()
s.Views.Invert()
s.Dialog.Invert()
}
// Invert inverts all colors in Body.
func (b *Body) Invert() {
b.FgColor = b.FgColor.InvertColor()
b.BgColor = b.BgColor.InvertColor()
b.LogoColor = b.LogoColor.InvertColor()
b.LogoColorMsg = b.LogoColorMsg.InvertColor()
b.LogoColorInfo = b.LogoColorInfo.InvertColor()
b.LogoColorWarn = b.LogoColorWarn.InvertColor()
b.LogoColorError = b.LogoColorError.InvertColor()
}
// Invert inverts all colors in Prompt.
func (p *Prompt) Invert() {
p.FgColor = p.FgColor.InvertColor()
p.BgColor = p.BgColor.InvertColor()
p.SuggestColor = p.SuggestColor.InvertColor()
p.Border.Invert()
}
// Invert inverts all colors in PromptBorder.
func (p *PromptBorder) Invert() {
p.CommandColor = p.CommandColor.InvertColor()
p.DefaultColor = p.DefaultColor.InvertColor()
}
// Invert inverts all colors in Help.
func (h *Help) Invert() {
h.FgColor = h.FgColor.InvertColor()
h.BgColor = h.BgColor.InvertColor()
h.SectionColor = h.SectionColor.InvertColor()
h.KeyColor = h.KeyColor.InvertColor()
h.NumKeyColor = h.NumKeyColor.InvertColor()
}
// Invert inverts all colors in Dialog.
func (d *Dialog) Invert() {
d.FgColor = d.FgColor.InvertColor()
d.BgColor = d.BgColor.InvertColor()
d.ButtonFgColor = d.ButtonFgColor.InvertColor()
d.ButtonBgColor = d.ButtonBgColor.InvertColor()
d.ButtonFocusFgColor = d.ButtonFocusFgColor.InvertColor()
d.ButtonFocusBgColor = d.ButtonFocusBgColor.InvertColor()
d.LabelFgColor = d.LabelFgColor.InvertColor()
d.FieldFgColor = d.FieldFgColor.InvertColor()
}
// Invert inverts all colors in Frame.
func (f *Frame) Invert() {
f.Title.Invert()
f.Border.Invert()
f.Menu.Invert()
f.Crumb.Invert()
f.Status.Invert()
}
// Invert inverts all colors in Title.
func (t *Title) Invert() {
t.FgColor = t.FgColor.InvertColor()
t.BgColor = t.BgColor.InvertColor()
t.HighlightColor = t.HighlightColor.InvertColor()
t.CounterColor = t.CounterColor.InvertColor()
t.FilterColor = t.FilterColor.InvertColor()
}
// Invert inverts all colors in Border.
func (b *Border) Invert() {
b.FgColor = b.FgColor.InvertColor()
b.FocusColor = b.FocusColor.InvertColor()
}
// Invert inverts all colors in Menu.
func (m *Menu) Invert() {
m.FgColor = m.FgColor.InvertColor()
m.KeyColor = m.KeyColor.InvertColor()
m.NumKeyColor = m.NumKeyColor.InvertColor()
}
// Invert inverts all colors in Crumb.
func (c *Crumb) Invert() {
c.FgColor = c.FgColor.InvertColor()
c.BgColor = c.BgColor.InvertColor()
c.ActiveColor = c.ActiveColor.InvertColor()
}
// Invert inverts all colors in Status.
func (s *Status) Invert() {
s.NewColor = s.NewColor.InvertColor()
s.ModifyColor = s.ModifyColor.InvertColor()
s.AddColor = s.AddColor.InvertColor()
s.PendingColor = s.PendingColor.InvertColor()
s.ErrorColor = s.ErrorColor.InvertColor()
s.HighlightColor = s.HighlightColor.InvertColor()
s.KillColor = s.KillColor.InvertColor()
s.CompletedColor = s.CompletedColor.InvertColor()
}
// Invert inverts all colors in Info.
func (i *Info) Invert() {
i.SectionColor = i.SectionColor.InvertColor()
i.FgColor = i.FgColor.InvertColor()
i.CPUColor = i.CPUColor.InvertColor()
i.MEMColor = i.MEMColor.InvertColor()
i.K9sRevColor = i.K9sRevColor.InvertColor()
}
// Invert inverts all colors in Views.
func (v *Views) Invert() {
v.Table.Invert()
v.Xray.Invert()
v.Charts.Invert()
v.Yaml.Invert()
v.Picker.Invert()
v.Log.Invert()
}
// Invert inverts all colors in Table.
func (t *Table) Invert() {
t.FgColor = t.FgColor.InvertColor()
t.BgColor = t.BgColor.InvertColor()
t.CursorFgColor = t.CursorFgColor.InvertColor()
t.CursorBgColor = t.CursorBgColor.InvertColor()
t.MarkColor = t.MarkColor.InvertColor()
t.Header.Invert()
}
// Invert inverts all colors in TableHeader.
func (t *TableHeader) Invert() {
t.FgColor = t.FgColor.InvertColor()
t.BgColor = t.BgColor.InvertColor()
t.SorterColor = t.SorterColor.InvertColor()
t.SelectedSortColumnColor = t.SelectedSortColumnColor.InvertColor()
}
// Invert inverts all colors in Xray.
func (x *Xray) Invert() {
x.FgColor = x.FgColor.InvertColor()
x.BgColor = x.BgColor.InvertColor()
x.CursorColor = x.CursorColor.InvertColor()
x.CursorTextColor = x.CursorTextColor.InvertColor()
x.GraphicColor = x.GraphicColor.InvertColor()
}
// Invert inverts all colors in Charts.
func (c *Charts) Invert() {
c.BgColor = c.BgColor.InvertColor()
c.DialBgColor = c.DialBgColor.InvertColor()
c.ChartBgColor = c.ChartBgColor.InvertColor()
c.FocusFgColor = c.FocusFgColor.InvertColor()
c.FocusBgColor = c.FocusBgColor.InvertColor()
c.DefaultDialColors = c.DefaultDialColors.Invert()
c.DefaultChartColors = c.DefaultChartColors.Invert()
for k, v := range c.ResourceColors {
c.ResourceColors[k] = v.Invert()
}
}
// Invert inverts all colors in Yaml.
func (y *Yaml) Invert() {
y.KeyColor = y.KeyColor.InvertColor()
y.ValueColor = y.ValueColor.InvertColor()
y.ColonColor = y.ColonColor.InvertColor()
}
// Invert inverts all colors in Picker.
func (p *Picker) Invert() {
p.MainColor = p.MainColor.InvertColor()
p.FocusColor = p.FocusColor.InvertColor()
p.ShortcutColor = p.ShortcutColor.InvertColor()
}
// Invert inverts all colors in Log.
func (l *Log) Invert() {
l.FgColor = l.FgColor.InvertColor()
l.BgColor = l.BgColor.InvertColor()
l.Indicator.Invert()
}
// Invert inverts all colors in LogIndicator.
func (l *LogIndicator) Invert() {
l.FgColor = l.FgColor.InvertColor()
l.BgColor = l.BgColor.InvertColor()
l.ToggleOnColor = l.ToggleOnColor.InvertColor()
l.ToggleOffColor = l.ToggleOffColor.InvertColor()
}
// Load K9s configuration from file.
func (s *Styles) Load(path string, invert bool) error {
bb, err := os.ReadFile(path)
if err != nil {
return err
}
if err := data.JSONValidator.Validate(json.SkinSchema, bb); err != nil {
return err
}
if err := yaml.Unmarshal(bb, s); err != nil {
return err
}
if invert {
s.K9s.Invert()
}
return nil
}
// Update apply terminal colors based on styles.
func (s *Styles) Update() {
tview.Styles.PrimitiveBackgroundColor = s.BgColor()
tview.Styles.ContrastBackgroundColor = s.BgColor()
tview.Styles.MoreContrastBackgroundColor = s.BgColor()
tview.Styles.PrimaryTextColor = s.FgColor()
tview.Styles.BorderColor = s.K9s.Frame.Border.FgColor.Color()
tview.Styles.FocusColor = s.K9s.Frame.Border.FocusColor.Color()
tview.Styles.TitleColor = s.FgColor()
tview.Styles.GraphicsColor = s.FgColor()
tview.Styles.SecondaryTextColor = s.FgColor()
tview.Styles.TertiaryTextColor = s.FgColor()
tview.Styles.InverseTextColor = s.FgColor()
tview.Styles.ContrastSecondaryTextColor = s.FgColor()
s.fireStylesChanged()
}
// Dump for debug.
func (s *Styles) Dump() {
bb, _ := yaml.Marshal(s)
fmt.Println(string(bb))
}
================================================
FILE: internal/config/styles_int_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_newStyle(t *testing.T) {
s := newStyle()
assert.Equal(t, Color("black"), s.Body.BgColor)
assert.Equal(t, Color("cadetblue"), s.Body.FgColor)
assert.Equal(t, Color("lightskyblue"), s.Frame.Status.NewColor)
}
================================================
FILE: internal/config/styles_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/tcell/v2"
"github.com/derailed/tview"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewStyle(t *testing.T) {
s := config.NewStyles()
assert.Equal(t, config.Color("black"), s.K9s.Body.BgColor)
assert.Equal(t, config.Color("cadetblue"), s.K9s.Body.FgColor)
assert.Equal(t, config.Color("lightskyblue"), s.K9s.Frame.Status.NewColor)
}
func TestColor(t *testing.T) {
uu := map[string]tcell.Color{
"blah": tcell.ColorDefault,
"blue": tcell.ColorBlue.TrueColor(),
"#ffffff": tcell.NewHexColor(16777215),
"#ff0000": tcell.NewHexColor(16711680),
}
for k := range uu {
c, u := k, uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u, config.NewColor(c).Color())
})
}
}
func TestSkinHappy(t *testing.T) {
s := config.NewStyles()
require.NoError(t, s.Load("../../skins/black-and-wtf.yaml", false))
s.Update()
assert.Equal(t, "#ffffff", s.Body().FgColor.String())
assert.Equal(t, "#000000", s.Body().BgColor.String())
assert.Equal(t, "#000000", s.Table().BgColor.String())
assert.Equal(t, tcell.ColorWhite.TrueColor(), s.FgColor())
assert.Equal(t, tcell.ColorBlack.TrueColor(), s.BgColor())
assert.Equal(t, tcell.ColorBlack.TrueColor(), tview.Styles.PrimitiveBackgroundColor)
}
func TestSkinLoad(t *testing.T) {
uu := map[string]struct {
f string
err string
}{
"not-exist": {
f: "testdata/skins/blee.yaml",
err: "open testdata/skins/blee.yaml: no such file or directory",
},
"toast": {
f: "testdata/skins/boarked.yaml",
err: `Additional property bgColor is not allowed
Additional property fgColor is not allowed
Additional property logoColor is not allowed
Invalid type. Expected: object, given: array`,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
s := config.NewStyles()
err := s.Load(u.f, false)
if err != nil {
assert.Equal(t, u.err, err.Error())
}
assert.Equal(t, "#5f9ea0", s.Body().FgColor.String())
assert.Equal(t, "#000000", s.Body().BgColor.String())
assert.Equal(t, "#000000", s.Table().BgColor.String())
assert.Equal(t, tcell.ColorCadetBlue.TrueColor(), s.FgColor())
assert.Equal(t, tcell.ColorBlack.TrueColor(), s.BgColor())
assert.Equal(t, tcell.ColorBlack.TrueColor(), tview.Styles.PrimitiveBackgroundColor)
})
}
}
================================================
FILE: internal/config/templates/aliases.yaml
================================================
aliases:
dp: deployments
sec: v1/secrets
jo: jobs
cr: clusterroles
crb: clusterrolebindings
ro: roles
rb: rolebindings
np: networkpolicies
================================================
FILE: internal/config/templates/benchmarks.yaml
================================================
benchmarks:
defaults:
concurrency: 2
requests: 200
================================================
FILE: internal/config/templates/hotkeys.yaml
================================================
hotKeys:
# Examples...
# shift-0:
# shortCut: Shift-0
# description: View Workloads
# command: wk k8s-app=cilium
================================================
FILE: internal/config/templates/stock-skin.yaml
================================================
# -----------------------------------------------------------------------------
# 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:
command: aqua
default: seagreen
help:
fgColor: cadetblue
bgColor: black
sectionColor: green
keyColor: dodgerblue
numKeyColor: fuchsia
frame:
title:
fgColor: aqua
bgColor: black
highlightColor: fuchsia
counterColor: papayawhip
filterColor: seagreen
border:
fgColor: dodgerblue
focusColor: lightskyblue
menu:
fgColor: white
keyColor: dodgerblue
numKeyColor: fuchsia
crumbs:
fgColor: black
bgColor: aqua
activeColor: orange
status:
newColor: lightskyblue
modifyColor: greenyellow
addColor: dodgerblue
pendingColor: darkorange
errorColor: orangered
highlightColor: aqua
killColor: mediumpurple
completedColor: lightslategray
info:
sectionColor: white
fgColor: orange
views:
table:
fgColor: aqua
bgColor: black
cursorFgColor: black
cursorBgColor: aqua
markColor: palegreen
header:
fgColor: white
bgColor: black
sorterColor: aqua
selectedSortColumnColor: lightskyblue
xray:
fgColor: aqua
bgColor: black
cursorColor: dodgerblue
cursorTextColor: black
graphicColor: cadetblue
charts:
bgColor: black
dialBgColor: black
chartBgColor: black
focusFgColor: white
focusBgColor: orange
defaultDialColors:
- palegreen
- orangered
defaultChartColors:
- palegreen
- orangered
resourceColors:
cpu:
- dodgerblue
- darkslateblue
mem:
- yellow
- goldenrod
yaml:
keyColor: steelblue
valueColor: papayawhip
colonColor: white
picker:
mainColor: white
focusColor: aqua
shortcutColor: aqua
logs:
fgColor: lightskyblue
bgColor: black
indicator:
fgColor: dodgerblue
bgColor: black
toggleOnColor: limegreen
toggleOffColor: gray
dialog:
fgColor: cadetblue
bgColor: black
buttonFgColor: black
buttonBgColor: darkslateblue
buttonFocusFgColor: black
buttonFocusBgColor: dodgerblue
labelFgColor: white
fieldFgColor: white
================================================
FILE: internal/config/testdata/aliases/aliases.yaml
================================================
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
================================================
FILE: internal/config/testdata/aliases/plain.yaml
================================================
aliases:
dp: "apps/v1/deployments"
pe: "v1/pods"
================================================
FILE: internal/config/testdata/benchmarks/b_containers.yaml
================================================
benchmarks:
defaults:
concurrency: 2
requests: 1000
containers:
c1:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /duh
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
c2:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /fred
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
services:
default/nginx:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
blee/fred:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /blee
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
================================================
FILE: internal/config/testdata/benchmarks/b_containers_1.yaml
================================================
benchmarks:
defaults:
concurrency: 20
requests: 100
containers:
c1:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /duh
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
c2:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /fred
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
services:
default/nginx:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
blee/fred:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /blee
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
================================================
FILE: internal/config/testdata/benchmarks/b_good.yaml
================================================
benchmarks:
defaults:
concurrency: 2
requests: 1000
services:
default/nginx:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
blee/fred:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /zorg
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
================================================
FILE: internal/config/testdata/benchmarks/b_toast.yaml
================================================
benchmarks:
service:
- default/nginx:
concurrency: 1
http:
requests: 100
http2: true
method: GET
url: http://35.224.16.201/
body: |-
{"fred": "blee"}
headers:
- "Accept: text/html"
- "Content-Type: application/json"
auth:
user: "fred"
password: "blee"
================================================
FILE: internal/config/testdata/benchmarks/bench-fred.yaml
================================================
benchmarks:
defaults:
concurrency: 2
requests: 1000
services:
default/nginx:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
blee/fred:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /zorg
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
================================================
FILE: internal/config/testdata/configs/default.yaml
================================================
k9s:
liveViewAutoRefresh: false
gpuVendors: {}
screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 2
apiServerTimeout: 2m0s
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
portForwardAddress: localhost
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
reactive: false
noIcons: false
invert: false
defaultsToFullScreen: false
useFullGVRTitle: false
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 100
buffer: 5000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
defaultView: ""
================================================
FILE: internal/config/testdata/configs/expected.yaml
================================================
k9s:
liveViewAutoRefresh: true
gpuVendors:
bozo: bozo/gpu.com
screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 100
apiServerTimeout: 30s
maxConnRetry: 5
readOnly: true
noExitOnCtrlC: false
portForwardAddress: localhost
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
reactive: false
noIcons: false
invert: false
defaultsToFullScreen: false
useFullGVRTitle: true
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 500
buffer: 800
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
defaultView: ""
================================================
FILE: internal/config/testdata/configs/k9s.yaml
================================================
k9s:
liveViewAutoRefresh: true
gpuVendors: {}
screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 2
apiServerTimeout: 10s
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
portForwardAddress: localhost
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
reactive: false
noIcons: false
invert: false
defaultsToFullScreen: false
useFullGVRTitle: false
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 200
buffer: 2000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
defaultView: ""
================================================
FILE: internal/config/testdata/configs/k9s_toast.yaml
================================================
k9s:
liveViewAutoRefresh: true
screenDumpDir: /tmp/screen-dumps
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
splashless: false
noIcons: false
invert: false
skipLatestRevCheck: yes
disablePodCounts: false
shellPods:
image: busybox:1.37.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 200
buffer: 2000
sinceSeconds: -1
textWrap: false
disableAutoscroll: false
columnLock: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
defaultView: ""
================================================
FILE: internal/config/testdata/hotkeys/hotkeys.yaml
================================================
hotKeys:
pods:
shortCut: shift-0
description: Launch pod view
command: pods
keepHistory: true
================================================
FILE: internal/config/testdata/k8s.yaml
================================================
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:6443
name: docker-for-desktop-cluster
contexts:
- context:
cluster: docker-for-desktop-cluster
user: docker-for-desktop
name: docker-for-desktop
current-context: docker-for-desktop
users:
- name: docker-for-desktop
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJWFNHb3I3ZlJlOHN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ESXlNREkyTlRaYUZ3MHlNREF5TURreE5qQXhNVGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGxBTWZKVUUvWUIwb0UKWmN5TmE1S0dVMkZpRmNYLys2dFJQUlpETkhZSkE1ZGtaME40UC9kVVdZeWJGRlVYc0E3UU1Sbm1mS280Q25MTQptK28wS2NUd3NRMnY3UzlPejVJYlJCOVZGVnFqNDJmNW9mVFFDcnZNN20wWVovNlRzcjhtSDE0QVYzWkRZaWtsCkF1VjlqRUgvczF5WWppbG0rODVlbm02RUZYYkJMV2czcXZkQ3VxNmlMa2FjWFptWTJYVXVtTWVOTVZnQllrS1UKVi84czJ0VlhyTlhvaU9qZVFMZlIvYmpvYkFZbzlMM0JWZFczYUxjanBwcDYzWmE0YlZITHYyQ2ZXMDcwNjNvbQpYZ0syM1hHWjQwdFFGaElxbUlvZktYZ0lVSWk2YVV3UVI0WFVRd3RMeTk4aHRDazZ2ckl5UUpMWkdKV29WVlU4CitJclVtZFRyQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFRRzlVaFVjUDdJQzdyZmRyK1pxTTBKbGxITzY3MzZoTgpVRkpvNmZSRUdDbjlxclN6SW44K0RZV1N3RnF4ZVRhZlNFK3VJZHFGREQ1ais1bWhEQzBzZUV2WWlNQ09CZFJDCk4xT3RRK1lrQndndnNKU3RxZGdzNTRXdkJwLzFiS09leFNLS1laTzJPaUJLd3NRV1ZXeksrQ0VjOXhRSm1jN2MKZGhlK0tNZVNTeC9LSmR0bHc4VWVSUkVCOU00WjZjRHpLYzQ2cjhBd04zWkxibzdsYzVCNE0wb2lXMUVwR0wwQgpKUXYrT1FDblV4K0d1dVcvTGdNT1JQRVFXaXF1UjFvWXlJQjlRb0wxRXFCTDZHejhTWjVtbTE1ellWSVZXTHh6ClNQLzVyd2VjNDY0Z216RDR4MHJVUHBIaWlRSVJzUjk1WXBIMjNxWkl2QlVwd2dnTjJnd2hTUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBNVFESHlWQlAyQWRLQkdYTWpXdVNobE5oWWhYRi8vdXJVVDBXUXpSMkNRT1haR2RECmVELzNWRm1NbXhSVkY3QU8wREVaNW55cU9BcHl6SnZxTkNuRThMRU5yKzB2VHMrU0cwUWZWUlZhbytObithSDAKMEFxN3pPNXRHR2YrazdLL0poOWVBRmQyUTJJcEpRTGxmWXhCLzdOY21JNHBadnZPWHA1dWhCVjJ3UzFvTjZyMwpRcnF1b2k1R25GMlptTmwxTHBqSGpURllBV0pDbEZmL0xOclZWNnpWNklqbzNrQzMwZjI0Nkd3R0tQUzl3VlhWCnQyaTNJNmFhZXQyV3VHMVJ5NzlnbjF0TzlPdDZKbDRDdHQxeG1lTkxVQllTS3BpS0h5bDRDRkNJdW1sTUVFZUYKMUVNTFM4dmZJYlFwT3I2eU1rQ1MyUmlWcUZWVlBQaUsxSm5VNndJREFRQUJBb0lCQUZSY29EenlZQ2VXTDlkRQo1VUVuNHRlbk9kWFhiWlNxMHViZm1TYnkyWlRpaE5BUkZwTGpCYXRHUGYwWFZXMmZoeVY5SVN4K3VucGdwdi9uClpEVUpPaXJ0SHJ5enBOemtyTTlzbmhwSy9wUW5mek5BVFo2aWhhS3VKdlI1d3hnSUhsRGQ5MVFxNUQ5WWx3MnkKYm5aOHlBZDV2Ti9hWnpnd0JVdG9GQkNHazdQLzRxK0JlbHZoNWd1SzdzS0dvRi94dGMwWlp6RGtYMkw5VHJlZQowSE5nTmJlYm91SHhlVHBkcGNLQzZ2TENST2tqb0RTdDY1ZEo2ajBGTzhzTERVcWRrWkxNa0ducTdoZWhYV2JwClBtRVI5dWc3Qk1HVUFtcHhpbGdGVHM0MnRSNnoxZXdvSWs5bC92V3ZMTFpZbEE3OUo4YkU0UTNPZGpXc2Nza1YKV0ZpakZ0a0NnWUVBNXU2SnV3b3A0Z2QzTjNhUHVhakpHNEpjYTZNWFZQNEVVay9vY2pobloyRDF5cjd2ZXhZSApVaE1WN2p6dzJUQ1FJa2JtMWZlSTZpa1llUzNVNXprZDhKSm9VaXV4T3ZsS0VJSlFrTEROQyttMHFuN0xEamU1Cmx0SkE3Sm9IdDhSTzNMS1JBTFhvQTVsV0l0NWNQLzVuTW1IMTlDZGQ3L294MU0xRXFFYUxNMmNDZ1lFQS9keWsKMExyR0VtbTg0SlU0dDIvZDVzNm5ERnAxOWxhUXJlbFY1bWRsZEFKNGRPVHkxZXV4dHNFeS8xSFExWXBLa25aSgpTa2Q2RTJzYk1MUncxdzFGWTZpczI0ektmaWtLV0N5SXBPMTkvQWh5UkpweGxKTlN4a2hjK1FpVXVSd1lsVmtMCi9XQ3dFUFVVaDI1VHJRNE9LRTNKazh3VmVmdFlwdkZNRDhvaHc5MENnWUF5ZTkxQ05XT1lsUmM3MmNCcnp2ay8KK1V5by96dGZpalI1cGh4anMrN3ZDNlJRRVZPYkxlS2x6NlJRczZQWFp5VnJTT0szemVoeGdGQm9WVnVndkx6TgoxY1BXaXRTdzFzU1pQVlBOZmNrbG5JNnhZd3lTN0IyM1dmbDFmK3JHQXJWV3kvYWxHQjlEZ2lieGNuanFTSHhZCjZFOXpjNU8yblpSOU4rNlZkdTZCYXdLQmdDV3Vtc2hnOFFYS3JENnA1OEZTMloxcEQyTEdDcnlHSFBPenJ3eUUKVElycjB2V0hCb1M2ZDZhcEJ1amZQQ0IyWnB0Vzg0b1RFZ3ZQMmpsZ2oxOWNtUEF5R1haOWI1RktoajZRWGJnZAppSlhncXhXRDExZzJoaExvcXVSTVljY1laSTNHcWdEeVdUQXJNT0RwZjRJd2srbG5vb1JOeHVKVWJOUmEvTzliCkVhZ0JBb0dBUE9VZk15M3JPSWRBRTV4Q0VxOUlza1hXblFrcmRwYktKVDVzbVFyVUhuRFh4QWM5L0libm1jWmwKOWN4Y3czdktMbWVZU3loWFF5ZWF1VXo3amZhdjZ3TGhnVi9KK1NYdlBlUng2aDFmb0lsVUF4WDJMdDFSOEduZApNejhqdHJxN29ycE1EQU9xOHNyaGxzZkZDYnJtUFZKSnNTd1J4R3ZJN3ZLTm54VXRXVFE9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
================================================
FILE: internal/config/testdata/kubes/test.yaml
================================================
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /Users/test/ca.crt
server: https://1.2.3.4:8443
name: cl-1
- cluster:
certificate-authority: /Users/test/ca.crt
server: https://5.6.7.8:8443
name: cl-2
contexts:
- context:
cluster: cl-1
user: user1
namespace: ns-1
name: ct-1-1
- context:
cluster: cl-1
user: user2
namespace: ns-2
name: ct-1-2
- context:
cluster: cl-2
user: user2
namespace: ns-2
name: ct-2-1
current-context: ct-1-1
preferences: {}
users:
- name: user1
user:
client-certificate: /Users/test/client.crt
client-key: /Users/test/client.key
- name: user2
user:
client-certificate: /Users/test/client.crt
client-key: /Users/test/client.key
================================================
FILE: internal/config/testdata/plugins/dir/snippet.1.yaml
================================================
shortCut: shift-s
description: blee
scopes:
- po
- dp
command: duh
args:
- -n
- $NAMESPACE
- -boolean
background: false
confirm: true
overwriteOutput: true
================================================
FILE: internal/config/testdata/plugins/dir/snippet.2.yaml
================================================
shortCut: shift-r
confirm: false
description: bla
scopes:
- svc
- ing
command: duha
background: true
args:
- -n
- $NAMESPACE
- -oyaml
================================================
FILE: internal/config/testdata/plugins/dir/snippet.multi.yaml
================================================
crapola:
shortCut: Shift-1
description: crapola
scopes:
- pods
command: crapola
background: false
bozo:
shortCut: Shift-2
description: bozo
scopes:
- pods
- svc
command: bozo
================================================
FILE: internal/config/testdata/plugins/plugins-toast.yaml
================================================
plugins:
blah:
shortCut: shift-s
confirm: true
description: blee
scoped:
- po
- dp
command: duh
background: false
args:
- -n
- $NAMESPACE
- -boolean
================================================
FILE: internal/config/testdata/plugins/plugins.yaml
================================================
plugins:
blah:
shortCut: shift-s
confirm: true
description: blee
scopes:
- po
- dp
command: duh
background: false
args:
- -n
- $NAMESPACE
- -boolean
================================================
FILE: internal/config/testdata/skins/black-and-wtf.yaml
================================================
k9s:
body:
fgColor: white
bgColor: black
logoColor: white
info:
fgColor: navajowhite
sectionColor: white
frame:
border:
fgColor: white
focusColor: white
menu:
fgColor: white
keyColor: white
numKeyColor: navajowhite
crumb:
fgColor: black
bgColor: navajowhite
activeColor: whitesmoke
status:
newColor: ghostwhite
modifyColor: navajowhite
addColor: darkslategray
errorColor: whitesmoke
highlightcolor: dimgray
killColor: slategray
completedColor: gray
title:
fgColor: ghostwhite
highlightColor: navajowhite
counterColor: navajowhite
filterColor: slategray
views:
table:
fgColor: white
bgColor: black
cursorColor: white
header:
fgColor: darkgray
bgColor: black
sorterColor: white
================================================
FILE: internal/config/testdata/skins/boarked.yaml
================================================
k9s:
fgColor: blee
bgColor: black
logoColor: white
info:
- fgColor: fred
- sectionColor: white
================================================
FILE: internal/config/testdata/skins/empty.yaml
================================================
k9s:
body:
================================================
FILE: internal/config/testdata/views/views.yaml
================================================
views:
v1/pods:
columns:
- NAMESPACE
- NAME
- AGE
- IP
v1/pods@default:
columns:
- NAME
- IP
- AGE
v1/pods@ns*:
columns:
- AGE
- NAME
- IP
bozo:
columns:
- DUH
- BLAH
- BLEE
================================================
FILE: internal/config/threshold.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
const (
// SeverityLow tracks low severity.
SeverityLow SeverityLevel = iota
// SeverityMedium tracks medium severity level.
SeverityMedium
// SeverityHigh tracks high severity level.
SeverityHigh
)
// SeverityLevel tracks severity levels.
type SeverityLevel int
// Severity tracks a resource severity levels.
type Severity struct {
Critical int `yaml:"critical"`
Warn int `yaml:"warn"`
}
// NewSeverity returns a new instance.
func NewSeverity() *Severity {
return &Severity{
Critical: 90,
Warn: 70,
}
}
// Validate checks all thresholds and make sure we're cool. If not use defaults.
func (s *Severity) Validate() {
norm := NewSeverity()
if !validateRange(s.Warn) {
s.Warn = norm.Warn
}
if !validateRange(s.Critical) {
s.Critical = norm.Critical
}
}
func validateRange(v int) bool {
if v <= 0 || v > 100 {
return false
}
return true
}
// Threshold tracks threshold to alert user when exceeded.
type Threshold map[string]*Severity
// NewThreshold returns a new threshold.
func NewThreshold() Threshold {
return Threshold{
CPU: NewSeverity(),
MEM: NewSeverity(),
}
}
// Validate a namespace is setup correctly.
func (t Threshold) Validate() Threshold {
for _, k := range []string{CPU, MEM} {
v, ok := t[k]
if !ok {
t[k] = NewSeverity()
} else {
v.Validate()
}
}
return t
}
// LevelFor returns a defcon level for the current state.
func (t Threshold) LevelFor(k string, v int) SeverityLevel {
s, ok := t[k]
if !ok || v < 0 || v > 100 {
return SeverityLow
}
if v >= s.Critical {
return SeverityHigh
}
if v >= s.Warn {
return SeverityMedium
}
return SeverityLow
}
// SeverityColor returns a defcon level associated level.
func (t *Threshold) SeverityColor(k string, v int) string {
//nolint:exhaustive
switch t.LevelFor(k, v) {
case SeverityHigh:
return "red"
case SeverityMedium:
return "orangered"
default:
return "green"
}
}
================================================
FILE: internal/config/threshold_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestSeverityValidate(t *testing.T) {
uu := map[string]struct {
d, e *config.Severity
}{
"default": {
d: config.NewSeverity(),
e: config.NewSeverity(),
},
"toast": {
d: &config.Severity{Warn: 10},
e: &config.Severity{Warn: 10, Critical: 90},
},
"negative": {
d: &config.Severity{Warn: -1},
e: config.NewSeverity(),
},
"out-of-range": {
d: &config.Severity{Warn: 150},
e: config.NewSeverity(),
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
u.d.Validate()
assert.Equal(t, u.e, u.d)
})
}
}
func TestLevelFor(t *testing.T) {
uu := map[string]struct {
k string
v int
e config.SeverityLevel
}{
"normal": {
k: config.CPU,
v: 0,
e: config.SeverityLow,
},
"4": {
k: config.CPU,
v: 71,
e: config.SeverityMedium,
},
"3": {
k: config.CPU,
v: 75,
e: config.SeverityMedium,
},
"2": {
k: config.CPU,
v: 80,
e: config.SeverityMedium,
},
"1": {
k: config.CPU,
v: 100,
e: config.SeverityHigh,
},
"over": {
k: config.CPU,
v: 150,
e: config.SeverityLow,
},
"over-mem": {
k: config.MEM,
v: 150,
e: config.SeverityLow,
},
}
o := config.NewThreshold()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, o.LevelFor(u.k, u.v))
})
}
}
================================================
FILE: internal/config/types.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
const (
defaultRefreshRate = 2
defaultMaxConnRetry = 5
// CPU tracks cpu usage.
CPU = "cpu"
// MEM tracks memory usage.
MEM = "memory"
)
// UI tracks ui specific configs.
type UI struct {
// EnableMouse toggles mouse support.
EnableMouse bool `json:"enableMouse" yaml:"enableMouse"`
// Headless toggles top header display.
Headless bool `json:"headless" yaml:"headless"`
// LogoLess toggles k9s logo.
Logoless bool `json:"logoless" yaml:"logoless"`
// Crumbsless toggles nav crumb display.
Crumbsless bool `json:"crumbsless" yaml:"crumbsless"`
// Splashless disables the splash screen on startup.
Splashless bool `json:"splashless" yaml:"splashless"`
// Reactive toggles reactive ui changes.
Reactive bool `json:"reactive" yaml:"reactive"`
// NoIcons toggles icons display.
NoIcons bool `json:"noIcons" yaml:"noIcons"`
// Invert inverts all skin colors using Oklch lightness inversion.
Invert bool `json:"invert" yaml:"invert"`
// Skin reference the general k9s skin name.
// Can be overridden per context.
Skin string `json:"skin" yaml:"skin,omitempty"`
// DefaultsToFullScreen toggles fullscreen on views like logs, yaml, details.
DefaultsToFullScreen bool `json:"defaultsToFullScreen" yaml:"defaultsToFullScreen"`
// UseFullGVRTitle toggles the display of full GVR (group/version/resource) vs R in views title.
UseFullGVRTitle bool `json:"useFullGVRTitle" yaml:"useFullGVRTitle"`
manualHeadless *bool
manualLogoless *bool
manualCrumbsless *bool
manualSplashless *bool
manualInvert *bool
}
================================================
FILE: internal/config/views.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"cmp"
"errors"
"fmt"
"io/fs"
"log/slog"
"maps"
"os"
"regexp"
"slices"
"strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v3"
)
// ViewConfigListener represents a view config listener.
type ViewConfigListener interface {
// ViewSettingsChanged notifies listener the view configuration changed.
ViewSettingsChanged(*ViewSetting)
// GetNamespace return the view namespace
GetNamespace() string
}
// ViewSetting represents a view configuration.
type ViewSetting struct {
Columns []string `yaml:"columns"`
SortColumn string `yaml:"sortColumn"`
}
func (v *ViewSetting) HasCols() bool {
return len(v.Columns) > 0
}
func (v *ViewSetting) IsBlank() bool {
return v == nil || (len(v.Columns) == 0 && v.SortColumn == "")
}
func (v *ViewSetting) SortCol() (name string, asc bool, err error) {
if v == nil || v.SortColumn == "" {
return "", false, fmt.Errorf("no sort column specified")
}
tt := strings.Split(v.SortColumn, ":")
if len(tt) < 2 {
return "", false, fmt.Errorf("invalid sort column spec: %q. must be col-name:asc|desc", v.SortColumn)
}
return tt[0], tt[1] == "asc", nil
}
// Equals checks if two view settings are equal.
func (v *ViewSetting) Equals(vs *ViewSetting) bool {
if v == nil && vs == nil {
return true
}
if v == nil || vs == nil {
return false
}
if c := slices.Compare(v.Columns, vs.Columns); c != 0 {
return false
}
return cmp.Compare(v.SortColumn, vs.SortColumn) == 0
}
// CustomView represents a collection of view customization.
type CustomView struct {
Views map[string]ViewSetting `yaml:"views"`
listeners map[string]ViewConfigListener
}
// NewCustomView returns a views configuration.
func NewCustomView() *CustomView {
return &CustomView{
Views: make(map[string]ViewSetting),
listeners: make(map[string]ViewConfigListener),
}
}
// Reset clears out configurations.
func (v *CustomView) Reset() {
for k := range v.Views {
delete(v.Views, k)
}
}
// Load loads view configurations.
func (v *CustomView) Load(path string) error {
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return nil
}
bb, err := os.ReadFile(path)
if err != nil {
return err
}
if err := data.JSONValidator.Validate(json.ViewsSchema, bb); err != nil {
slog.Warn("Validation failed. Please update your config and restart!",
slogs.Path, path,
slogs.Error, err,
)
}
var in CustomView
if err := yaml.Unmarshal(bb, &in); err != nil {
return err
}
v.Views = in.Views
v.fireConfigChanged()
return nil
}
// AddListeners registers a new listener for various commands.
func (v *CustomView) AddListeners(l ViewConfigListener, cmds ...string) {
for _, cmd := range cmds {
if cmd != "" {
v.listeners[cmd] = l
}
}
v.fireConfigChanged()
}
// AddListener registers a new listener.
func (v *CustomView) AddListener(cmd string, l ViewConfigListener) {
v.listeners[cmd] = l
v.fireConfigChanged()
}
// RemoveListener unregister a listener.
func (v *CustomView) RemoveListener(l ViewConfigListener) {
for k, list := range v.listeners {
if list == l {
delete(v.listeners, k)
}
}
}
func (v *CustomView) fireConfigChanged() {
cmds := slices.Collect(maps.Keys(v.listeners))
slices.SortFunc(cmds, func(a, b string) int {
switch {
case strings.Contains(a, "/") && !strings.Contains(b, "/"):
return 1
case !strings.Contains(a, "/") && strings.Contains(b, "/"):
return -1
default:
return strings.Compare(a, b)
}
})
type tuple struct {
cmd string
vs *ViewSetting
}
var victim tuple
for _, cmd := range cmds {
if vs := v.getVS(cmd, v.listeners[cmd].GetNamespace()); vs != nil {
slog.Debug("Reloading custom view settings", slogs.Command, cmd)
victim = tuple{cmd, vs}
break
}
victim = tuple{cmd, nil}
}
if victim.cmd != "" {
v.listeners[victim.cmd].ViewSettingsChanged(victim.vs)
}
}
func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
if client.IsAllNamespaces(ns) {
ns = client.NamespaceAll
}
k := gvr
kk := slices.Collect(maps.Keys(v.Views))
slices.SortFunc(kk, strings.Compare)
slices.Reverse(kk)
for _, key := range kk {
if !strings.HasPrefix(key, gvr) && !strings.HasPrefix(gvr, key) {
continue
}
switch {
case strings.Contains(key, "@"):
tt := strings.Split(key, "@")
if len(tt) != 2 {
break
}
nsk := gvr
if ns != "" {
nsk += "@" + ns
}
if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(nsk) {
vs := v.Views[key]
return &vs
}
case strings.HasPrefix(k, key):
kk := strings.Fields(k)
if len(kk) == 2 {
if v, ok := v.Views[kk[0]+"@"+kk[1]]; ok {
return &v
}
if key == kk[0] {
vs := v.Views[key]
return &vs
}
}
fallthrough
case key == k:
vs := v.Views[key]
return &vs
}
}
return nil
}
================================================
FILE: internal/config/views_int_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config
import (
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCustomView_getVS(t *testing.T) {
uu := map[string]struct {
cv *CustomView
gvr, ns string
e *ViewSetting
}{
"empty": {},
"miss": {
gvr: "zorg",
},
"gvr": {
gvr: client.PodGVR.String(),
e: &ViewSetting{
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
},
"gvr+ns": {
gvr: client.PodGVR.String(),
ns: "default",
e: &ViewSetting{
Columns: []string{"NAME", "IP", "AGE"},
},
},
"rx": {
gvr: client.PodGVR.String(),
ns: "ns-fred",
e: &ViewSetting{
Columns: []string{"AGE", "NAME", "IP"},
},
},
"alias": {
gvr: "bozo",
e: &ViewSetting{
Columns: []string{"DUH", "BLAH", "BLEE"},
},
},
"toast-no-ns": {
gvr: client.PodGVR.String(),
ns: "zorg",
e: &ViewSetting{
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
},
"toast-no-res": {
gvr: client.SvcGVR.String(),
ns: "zorg",
},
}
v := NewCustomView()
require.NoError(t, v.Load("testdata/views/views.yaml"))
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, v.getVS(u.gvr, u.ns))
})
}
}
================================================
FILE: internal/config/views_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package config_test
import (
"log/slog"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
func TestCustomViewLoad(t *testing.T) {
uu := map[string]struct {
cv *config.CustomView
path string
key string
e []string
}{
"empty": {},
"gvr": {
path: "testdata/views/views.yaml",
key: client.PodGVR.String(),
e: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
"gvr+ns": {
path: "testdata/views/views.yaml",
key: "v1/pods@default",
e: []string{"NAME", "IP", "AGE"},
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
cfg := config.NewCustomView()
require.NoError(t, cfg.Load(u.path))
assert.Equal(t, u.e, cfg.Views[u.key].Columns)
})
}
}
func TestViewSettingEquals(t *testing.T) {
uu := map[string]struct {
v1, v2 *config.ViewSetting
e bool
}{
"v1-nil-v2-nil": {
e: true,
},
"v1-v2-empty": {
v1: new(config.ViewSetting),
v2: new(config.ViewSetting),
e: true,
},
"v1-nil": {
v1: new(config.ViewSetting),
},
"nil-v2": {
v2: new(config.ViewSetting),
},
"v1-v2-blank": {
v1: &config.ViewSetting{
Columns: []string{"A"},
},
v2: new(config.ViewSetting),
},
"v1-v2-nil": {
v1: &config.ViewSetting{
Columns: []string{"A"},
},
},
"same": {
v1: &config.ViewSetting{
Columns: []string{"A", "B", "C"},
},
v2: &config.ViewSetting{
Columns: []string{"A", "B", "C"},
},
e: true,
},
"order": {
v1: &config.ViewSetting{
Columns: []string{"C", "A", "B"},
},
v2: &config.ViewSetting{
Columns: []string{"A", "B", "C"},
},
},
"delta": {
v1: &config.ViewSetting{
Columns: []string{"A", "B", "C"},
},
v2: &config.ViewSetting{
Columns: []string{"B"},
},
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equalf(t, u.e, u.v1.Equals(u.v2), "%#v and %#v", u.v1, u.v2)
})
}
}
================================================
FILE: internal/dao/accessor.go
================================================
package dao
import (
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
)
var accessors = Accessors{
client.WkGVR: new(Workload),
client.CtGVR: new(Context),
client.CoGVR: new(Container),
client.ScnGVR: new(ImageScan),
client.SdGVR: new(ScreenDump),
client.BeGVR: new(Benchmark),
client.PfGVR: new(PortForward),
client.DirGVR: new(Dir),
client.SvcGVR: new(Service),
client.PodGVR: new(Pod),
client.NodeGVR: new(Node),
client.NsGVR: new(Namespace),
client.CmGVR: new(ConfigMap),
client.SecGVR: new(Secret),
client.DpGVR: new(Deployment),
client.DsGVR: new(DaemonSet),
client.StsGVR: new(StatefulSet),
client.RsGVR: new(ReplicaSet),
client.CjGVR: new(CronJob),
client.JobGVR: new(Job),
client.HmGVR: new(HelmChart),
client.HmhGVR: new(HelmHistory),
client.CrdGVR: new(CustomResourceDefinition),
}
// Accessors represents a collection of dao accessors.
type Accessors map[*client.GVR]Accessor
// AccessorFor returns a client accessor for a resource if registered.
// Otherwise it returns a generic accessor.
// Customize here for non resource types or types with metrics or logs.
func AccessorFor(f Factory, gvr *client.GVR) (Accessor, error) {
r, ok := accessors[gvr]
if !ok {
r = new(Scaler)
slog.Debug("No DAO registry entry. Using generics!", slogs.GVR, gvr)
}
r.Init(f, gvr)
return r, nil
}
================================================
FILE: internal/dao/alias.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
)
var _ Accessor = (*Alias)(nil)
// Alias tracks standard and custom command aliases.
type Alias struct {
NonResource
*config.Aliases
}
// NewAlias returns a new set of aliases.
func NewAlias(f Factory) *Alias {
a := Alias{
Aliases: config.NewAliases(),
}
a.Init(f, client.AliGVR)
return &a
}
// AliasesFor returns a set of aliases for a given gvr.
func (a *Alias) AliasesFor(gvr *client.GVR) sets.Set[string] {
return a.Aliases.AliasesFor(gvr)
}
// List returns a collection of aliases.
func (*Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
aa, ok := ctx.Value(internal.KeyAliases).(*Alias)
if !ok {
return nil, fmt.Errorf("expecting *Alias but got %T", ctx.Value(internal.KeyAliases))
}
m := aa.ShortNames()
oo := make([]runtime.Object, 0, len(m))
for gvr, aliases := range m {
sort.StringSlice(aliases).Sort()
oo = append(oo, render.AliasRes{
GVR: gvr,
Aliases: aliases,
})
}
return oo, nil
}
// Get fetch a resource.
func (*Alias) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
// Ensure makes sure alias are loaded.
func (a *Alias) Ensure(path string) (config.Alias, error) {
if err := MetaAccess.LoadResources(a.Factory); err != nil {
return config.Alias{}, err
}
return a.Alias, a.load(path)
}
func (a *Alias) load(path string) error {
if err := a.Load(path); err != nil {
return err
}
crdGVRS := make(client.GVRs, 0, 50)
for _, gvr := range MetaAccess.AllGVRs() {
meta, err := MetaAccess.MetaFor(gvr)
if err != nil {
return err
}
if IsK9sMeta(meta) {
continue
}
if IsCRD(meta) {
crdGVRS = append(crdGVRS, gvr)
continue
}
a.Define(gvr, gvr.AsResourceName())
// Allow single shot commands for k8s resources only expect for metrics resource which override pods and nodes ;(!
if isStandardGroup(gvr.GVSub()) && gvr.G() != "metrics.k8s.io" {
a.Define(gvr, meta.Name, meta.SingularName)
}
if len(meta.ShortNames) > 0 {
a.Define(gvr, meta.ShortNames...)
}
a.Define(gvr, gvr.String())
}
for _, gvr := range crdGVRS {
meta, err := MetaAccess.MetaFor(gvr)
if err != nil {
return err
}
a.Define(gvr, strings.ToLower(meta.Kind), meta.Name)
a.Define(gvr, meta.SingularName)
if len(meta.ShortNames) > 0 {
a.Define(gvr, meta.ShortNames...)
}
a.Define(gvr, gvr.String())
a.Define(gvr, meta.Name+"."+meta.Group)
}
return nil
}
================================================
FILE: internal/dao/alias_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAliasList(t *testing.T) {
a := dao.Alias{}
a.Init(makeFactory(), client.AliGVR)
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
oo, err := a.List(ctx, "-")
require.NoError(t, err)
assert.Len(t, oo, 2)
assert.Len(t, oo[0].(render.AliasRes).Aliases, 2)
}
// ----------------------------------------------------------------------------
// Helpers...
func makeAliases() *dao.Alias {
gvr1 := client.NewGVR("v1/fred")
gvr2 := client.NewGVR("v1/blee")
return &dao.Alias{
Aliases: &config.Aliases{
Alias: config.Alias{
"fred": gvr1,
"f": gvr1,
"blee": gvr2,
"b": gvr2,
},
},
}
}
================================================
FILE: internal/dao/benchmark.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Benchmark)(nil)
_ Nuker = (*Benchmark)(nil)
BenchRx = regexp.MustCompile(`[:|]+`)
)
// Benchmark represents a benchmark resource.
type Benchmark struct {
NonResource
}
// Delete nukes a resource.
func (*Benchmark) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
return os.Remove(path)
}
// Get returns a resource.
func (*Benchmark) Get(context.Context, string) (runtime.Object, error) {
panic("NYI")
}
// List returns a collection of resources.
func (*Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyDir).(string)
if !ok {
return nil, errors.New("no benchmark dir found in context")
}
path, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("no path specified in context")
}
pathMatch := BenchRx.ReplaceAllString(strings.Replace(path, "/", "_", 1), "_")
ff, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(ff))
for _, f := range ff {
if !strings.HasPrefix(f.Name(), pathMatch) {
continue
}
if fi, err := f.Info(); err == nil {
oo = append(oo, render.BenchInfo{File: fi, Path: filepath.Join(dir, f.Name())})
}
}
return oo, nil
}
================================================
FILE: internal/dao/benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBenchmarkList(t *testing.T) {
a := dao.Benchmark{}
a.Init(makeFactory(), client.BeGVR)
ctx := context.WithValue(context.Background(), internal.KeyDir, "testdata/bench")
ctx = context.WithValue(ctx, internal.KeyPath, "")
oo, err := a.List(ctx, "-")
require.NoError(t, err)
assert.Len(t, oo, 1)
assert.Equal(t, "testdata/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
}
================================================
FILE: internal/dao/cluster.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
)
// RefScanner represents a resource reference scanner.
type RefScanner interface {
// Init initializes the scanner
Init(Factory, *client.GVR)
// Scan scan the resource for references.
Scan(ctx context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error)
// ScanSA scan the resource for serviceaccount references.
ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error)
}
// Ref represents a resource reference.
type Ref struct {
GVR string
FQN string
}
// Refs represents a collection of resource references.
type Refs []Ref
var (
_ RefScanner = (*Deployment)(nil)
_ RefScanner = (*StatefulSet)(nil)
_ RefScanner = (*DaemonSet)(nil)
_ RefScanner = (*Job)(nil)
_ RefScanner = (*CronJob)(nil)
)
func scanners() map[*client.GVR]RefScanner {
return map[*client.GVR]RefScanner{
client.DpGVR: new(Deployment),
client.DsGVR: new(DaemonSet),
client.StsGVR: new(StatefulSet),
client.CjGVR: new(CronJob),
client.JobGVR: new(Job),
}
}
// ScanForRefs scans cluster resources for resource references.
func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
rgvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, errors.New("expecting context GVR")
}
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("expecting context Path")
}
wait, ok := ctx.Value(internal.KeyWait).(bool)
if !ok {
slog.Warn("Expecting context Wait key. Using default")
}
var wg sync.WaitGroup
out := make(chan Refs)
for gvr, scanner := range scanners() {
wg.Add(1)
go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
defer wg.Done()
s.Init(f, gvr)
refs, err := s.Scan(ctx, rgvr, fqn, wait)
if err != nil {
slog.Error("Reference scan failed for",
slogs.RefType, fmt.Sprintf("%T", s),
slogs.Error, err,
)
return
}
select {
case out <- refs:
case <-ctx.Done():
return
}
}(ctx, gvr, scanner, out, wait)
}
go func() {
wg.Wait()
close(out)
}()
res := make(Refs, 0, 10)
for refs := range out {
res = append(res, refs...)
}
return res, nil
}
// ScanForSARefs scans cluster resources for serviceaccount refs.
func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("expecting context Path")
}
wait, ok := ctx.Value(internal.KeyWait).(bool)
if !ok {
return nil, errors.New("expecting context Wait")
}
var wg sync.WaitGroup
out := make(chan Refs)
for gvr, scanner := range scanners() {
wg.Add(1)
go func(ctx context.Context, gvr *client.GVR, s RefScanner, out chan Refs, wait bool) {
defer wg.Done()
s.Init(f, gvr)
refs, err := s.ScanSA(ctx, fqn, wait)
if err != nil {
slog.Error("ServiceAccount scan failed",
slogs.RefType, fmt.Sprintf("%T", s),
slogs.Error, err,
)
return
}
select {
case out <- refs:
case <-ctx.Done():
return
}
}(ctx, gvr, scanner, out, wait)
}
go func() {
wg.Wait()
close(out)
}()
res := make(Refs, 0, 10)
for refs := range out {
res = append(res, refs...)
}
return res, nil
}
================================================
FILE: internal/dao/cm.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
var _ Accessor = (*ConfigMap)(nil)
// ConfigMap represents a configmap resource.
type ConfigMap struct {
Resource
}
================================================
FILE: internal/dao/container.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"strconv"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
var (
_ Accessor = (*Container)(nil)
_ Loggable = (*Container)(nil)
)
const (
initIDX = "I"
mainIDX = "M"
ephIDX = "E"
)
// Container represents a pod's container dao.
type Container struct {
NonResource
}
// List returns a collection of containers.
func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error) {
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, fmt.Errorf("no context path for %q", c.gvr)
}
var (
cmx client.ContainersMetrics
err error
)
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx {
cmx, _ = client.DialMetrics(c.Client()).FetchContainersMetrics(ctx, fqn)
}
po, err := c.fetchPod(fqn)
if err != nil {
return nil, err
}
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)+len(po.Spec.EphemeralContainers))
for i := range po.Spec.InitContainers {
res = append(res, makeContainerRes(
initIDX,
i,
&(po.Spec.InitContainers[i]),
po,
cmx[po.Spec.InitContainers[i].Name]),
)
}
for i := range po.Spec.Containers {
res = append(res, makeContainerRes(
mainIDX,
i,
&(po.Spec.Containers[i]),
po,
cmx[po.Spec.Containers[i].Name]),
)
}
for i := range po.Spec.EphemeralContainers {
co := v1.Container(po.Spec.EphemeralContainers[i].EphemeralContainerCommon)
res = append(res, makeContainerRes(
ephIDX,
i,
&co,
po,
cmx[co.Name]),
)
}
return res, nil
}
// TailLogs tails a given container logs.
func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
po := Pod{}
po.Init(c.Factory, client.PodGVR)
return po.TailLogs(ctx, opts)
}
// ----------------------------------------------------------------------------
// Helpers...
func makeContainerRes(kind string, idx int, co *v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics) render.ContainerRes {
return render.ContainerRes{
Idx: kind + strconv.Itoa(idx+1),
Container: co,
Status: getContainerStatus(kind, co.Name, &po.Status),
MX: cmx,
Age: po.GetCreationTimestamp(),
}
}
func getContainerStatus(kind, name string, status *v1.PodStatus) *v1.ContainerStatus {
switch kind {
case mainIDX:
for i := range status.ContainerStatuses {
if status.ContainerStatuses[i].Name == name {
return &status.ContainerStatuses[i]
}
}
case initIDX:
for i := range status.InitContainerStatuses {
if status.InitContainerStatuses[i].Name == name {
return &status.InitContainerStatuses[i]
}
}
case ephIDX:
for i := range status.EphemeralContainerStatuses {
if status.EphemeralContainerStatuses[i].Name == name {
return &status.EphemeralContainerStatuses[i]
}
}
}
return nil
}
func (c *Container) fetchPod(fqn string) (*v1.Pod, error) {
o, err := c.getFactory().Get(client.PodGVR, fqn, true, labels.Everything())
if err != nil {
return nil, fmt.Errorf("failed to locate pod %q: %w", fqn, err)
}
var po v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po)
return &po, err
}
================================================
FILE: internal/dao/container_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/watch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
"sigs.k8s.io/yaml"
)
func TestContainerList(t *testing.T) {
c := dao.Container{}
c.Init(makePodFactory(), client.CoGVR)
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
oo, err := c.List(ctx, "")
require.NoError(t, err)
assert.Len(t, oo, 1)
}
// ----------------------------------------------------------------------------
// Helpers...
type conn struct{}
func makeConn() *conn {
return &conn{}
}
func (*conn) Config() *client.Config { return nil }
func (*conn) Dial() (kubernetes.Interface, error) { return nil, nil }
func (*conn) DialLogs() (kubernetes.Interface, error) { return nil, nil }
func (*conn) ConnectionOK() bool { return true }
func (*conn) SwitchContext(string) error { return nil }
func (*conn) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, nil }
func (*conn) RestConfig() (*restclient.Config, error) { return nil, nil }
func (*conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
func (*conn) DynDial() (dynamic.Interface, error) { return nil, nil }
func (*conn) HasMetrics() bool { return false }
func (*conn) CheckConnectivity() bool { return false }
func (*conn) IsNamespaced(string) bool { return false }
func (*conn) SupportsResource(string) bool { return false }
func (*conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }
func (*conn) SupportsRes(string, []string) (a string, b bool, e error) { return "", false, nil }
func (*conn) ServerVersion() (*version.Info, error) { return nil, nil }
func (*conn) CurrentNamespaceName() (string, error) { return "", nil }
func (*conn) CanI(string, *client.GVR, string, []string) (bool, error) { return true, nil }
func (*conn) ActiveContext() string { return "" }
func (*conn) ActiveNamespace() string { return "" }
func (*conn) IsValidNamespace(string) bool { return true }
func (*conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil }
func (*conn) IsActiveNamespace(string) bool { return false }
type podFactory struct{}
var _ dao.Factory = &testFactory{}
func (podFactory) Client() client.Connection {
return makeConn()
}
func (podFactory) Get(*client.GVR, string, bool, labels.Selector) (runtime.Object, error) {
var m map[string]any
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: m}, nil
}
func (podFactory) List(*client.GVR, string, bool, labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
func (podFactory) ForResource(string, *client.GVR) (informers.GenericInformer, error) {
return nil, nil
}
func (podFactory) CanForResource(string, *client.GVR, []string) (informers.GenericInformer, error) {
return nil, nil
}
func (podFactory) WaitForCacheSync() {}
func (podFactory) Forwarders() watch.Forwarders { return nil }
func (podFactory) DeleteForwarder(string) {}
func makePodFactory() dao.Factory {
return podFactory{}
}
func poYaml() string {
return `apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2018-12-14T17:36:43Z"
labels:
blee: duh
name: fred
namespace: blee
spec:
containers:
- env:
- name: fred
value: "1"
valueFrom:
configMapKeyRef:
key: blee
image: blee
name: fred
resources: {}
priority: 1
priorityClassName: bozo
volumes:
- hostPath:
path: /blee
type: Directory
name: fred
status:
containerStatuses:
- image: ""
imageID: ""
lastState: {}
name: fred
ready: false
restartCount: 0
state:
running:
startedAt: null
phase: Running
`
}
================================================
FILE: internal/dao/context.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Context)(nil)
_ Switchable = (*Context)(nil)
)
// Context represents a kubernetes context.
type Context struct {
NonResource
}
func (c *Context) config() *client.Config {
return c.getFactory().Client().Config()
}
// Get a Context.
func (c *Context) Get(_ context.Context, path string) (runtime.Object, error) {
co, err := c.config().GetContext(path)
if err != nil {
return nil, err
}
return &render.NamedContext{Name: path, Context: co}, nil
}
// List all Contexts on the current cluster.
func (c *Context) List(context.Context, string) ([]runtime.Object, error) {
ctxs, err := c.config().Contexts()
if err != nil {
return nil, err
}
cc := make([]runtime.Object, 0, len(ctxs))
for k, v := range ctxs {
cc = append(cc, render.NewNamedContext(c.config(), k, v))
}
return cc, nil
}
// MustCurrentContextName return the active context name.
func (c *Context) MustCurrentContextName() string {
cl, err := c.config().CurrentContextName()
if err != nil {
slog.Error("Fetching current context", slogs.Error, err)
}
return cl
}
// Switch to another context.
func (c *Context) Switch(ctx string) error {
return c.getFactory().Client().SwitchContext(ctx)
}
================================================
FILE: internal/dao/crd.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
var (
_ Accessor = (*CustomResourceDefinition)(nil)
_ Nuker = (*CustomResourceDefinition)(nil)
)
// CustomResourceDefinition represents a CRD resource model.
type CustomResourceDefinition struct {
Resource
}
================================================
FILE: internal/dao/cronjob.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/rand"
)
const maxJobNameSize = 42
var (
_ Accessor = (*CronJob)(nil)
_ Runnable = (*CronJob)(nil)
_ ImageLister = (*CronJob)(nil)
)
// CronJob represents a cronjob K8s resource.
type CronJob struct {
Generic
}
// ListImages lists container images.
func (c *CronJob) ListImages(_ context.Context, fqn string) ([]string, error) {
cj, err := c.GetInstance(fqn)
if err != nil {
return nil, err
}
return render.ExtractImages(&cj.Spec.JobTemplate.Spec.Template.Spec), nil
}
// Run a CronJob.
func (c *CronJob) Run(path string) error {
ns, n := client.Namespaced(path)
auth, err := c.Client().CanI(ns, client.JobGVR, n, []string{client.GetVerb, client.CreateVerb})
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to run jobs")
}
o, err := c.getFactory().Get(c.gvr, path, true, labels.Everything())
if err != nil {
return err
}
var cj batchv1.CronJob
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
if err != nil {
return errors.New("expecting CronJob resource")
}
jobName := cj.Name
if len(cj.Name) >= maxJobNameSize {
jobName = cj.Name[0:maxJobNameSize]
}
trueVal := true
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName + "-manual-" + rand.String(3),
Namespace: ns,
Labels: cj.Spec.JobTemplate.Labels,
Annotations: cj.Spec.JobTemplate.Annotations,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: c.gvr.GV().String(),
Kind: "CronJob",
BlockOwnerDeletion: &trueVal,
Controller: &trueVal,
Name: cj.Name,
UID: cj.UID,
},
},
},
Spec: cj.Spec.JobTemplate.Spec,
}
dial, err := c.Client().Dial()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), c.Client().Config().CallTimeout())
defer cancel()
_, err = dial.BatchV1().Jobs(ns).Create(ctx, job, metav1.CreateOptions{})
return err
}
// ScanSA scans for serviceaccount refs.
func (c *CronJob) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := c.getFactory().List(c.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var cj batchv1.CronJob
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
if err != nil {
return nil, errors.New("expecting CronJob resource")
}
if serviceAccountMatches(cj.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName, n) {
refs = append(refs, Ref{
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
}
}
return refs, nil
}
// GetInstance fetch a matching cronjob.
func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
o, err := c.getFactory().Get(c.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var cj batchv1.CronJob
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
if err != nil {
return nil, errors.New("expecting cronjob resource")
}
return &cj, nil
}
// ToggleSuspend toggles suspend/resume on a CronJob.
func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
ns, n := client.Namespaced(path)
auth, err := c.Client().CanI(ns, c.gvr, n, []string{client.GetVerb, client.UpdateVerb})
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to (un)suspend cronjobs")
}
dial, err := c.Client().Dial()
if err != nil {
return err
}
cj, err := dial.BatchV1().CronJobs(ns).Get(ctx, n, metav1.GetOptions{})
if err != nil {
return err
}
if cj.Spec.Suspend != nil {
current := !*cj.Spec.Suspend
cj.Spec.Suspend = ¤t
} else {
trueVal := true
cj.Spec.Suspend = &trueVal
}
_, err = dial.BatchV1().CronJobs(ns).Update(ctx, cj, metav1.UpdateOptions{})
return err
}
// Scan scans for cluster resource refs.
func (c *CronJob) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := c.getFactory().List(c.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var cj batchv1.CronJob
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
if err != nil {
return nil, errors.New("expecting CronJob resource")
}
switch gvr {
case client.CmGVR:
if !hasConfigMap(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
case client.SecGVR:
found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait)
if err != nil {
slog.Warn("Failed to locate secret",
slogs.FQN, fqn,
slogs.Error, err,
)
continue
}
if !found {
continue
}
refs = append(refs, Ref{
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
case client.PcGVR:
if !hasPC(&cj.Spec.JobTemplate.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: c.GVR(),
FQN: client.FQN(cj.Namespace, cj.Name),
})
}
}
return refs, nil
}
================================================
FILE: internal/dao/cruiser.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
func mustMap(o runtime.Object, field string) map[string]any {
u, ok := o.(*unstructured.Unstructured)
if !ok {
panic("no unstructured")
}
m, ok := u.Object[field].(map[string]any)
if !ok {
panic(fmt.Sprintf("map extract failed for %q", field))
}
return m
}
func mustSlice(o runtime.Object, field string) []any {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil
}
s, ok := u.Object[field].([]any)
if !ok {
return nil
}
return s
}
func mustField(o map[string]any, field string) any {
f, ok := o[field]
if !ok {
panic(fmt.Sprintf("no field for %q", field))
}
return f
}
================================================
FILE: internal/dao/cruiser_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestCruiserMeta(t *testing.T) {
o := loadJSON(t, "crb")
m := mustMap(o, "metadata")
assert.Equal(t, "blee", mustField(m, "name"))
}
func TestCruiserSlice(t *testing.T) {
o := loadJSON(t, "crb")
s := mustSlice(o, "subjects")
assert.Len(t, s, 1)
assert.Equal(t, "fernand", mustField(s[0].(map[string]any), "name"))
assert.Equal(t, "User", mustField(s[0].(map[string]any), "kind"))
}
// Helpers...
func loadJSON(t require.TestingT, n string) *unstructured.Unstructured {
raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n))
require.NoError(t, err)
var o unstructured.Unstructured
err = json.Unmarshal(raw, &o)
require.NoError(t, err)
return &o
}
================================================
FILE: internal/dao/describe.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
"k8s.io/kubectl/pkg/describe"
)
// Describe describes a resource.
func Describe(c client.Connection, gvr *client.GVR, path string) (string, error) {
mapper := RestMapper{Connection: c}
m, err := mapper.ToRESTMapper()
if err != nil {
slog.Error("No REST mapper for resource",
slogs.GVR, gvr,
slogs.Error, err,
)
return "", err
}
gvk, err := m.KindFor(gvr.GVR())
if err != nil {
slog.Error("No GVK for resource %s",
slogs.GVR, gvr,
slogs.Error, err,
)
return "", err
}
ns, n := client.Namespaced(path)
if client.IsClusterScoped(ns) {
ns = client.BlankNamespace
}
mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind)
if err != nil {
slog.Error("Unable to find mapper",
slogs.GVR, gvr,
slogs.ResName, n,
slogs.Error, err,
)
return "", err
}
d, err := describe.Describer(c.Config().Flags(), mapping)
if err != nil {
slog.Error("Unable to find describer",
slogs.GVR, gvr.AsResourceName(),
slogs.Error, err,
)
return "", err
}
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
}
================================================
FILE: internal/dao/dir.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
)
var _ Accessor = (*Dir)(nil)
// Dir tracks standard and custom command aliases.
type Dir struct {
NonResource
}
// NewDir returns a new set of aliases.
func NewDir(f Factory) *Dir {
var a Dir
a.Init(f, client.DirGVR)
return &a
}
var yamlRX = regexp.MustCompile(`.*\.(yml|yaml|json)`)
// List returns a collection of aliases.
func (*Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("no dir in context")
}
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(files))
for _, f := range files {
if strings.HasPrefix(f.Name(), ".") || !f.IsDir() && !yamlRX.MatchString(f.Name()) {
continue
}
oo = append(oo, render.DirRes{
Path: filepath.Join(dir, f.Name()),
Entry: f,
})
}
return oo, err
}
// Get fetch a resource.
func (*Dir) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
================================================
FILE: internal/dao/dir_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDir(t *testing.T) {
d := dao.NewDir(nil)
ctx := context.WithValue(context.Background(), internal.KeyPath, "testdata/dir")
oo, err := d.List(ctx, "")
require.NoError(t, err)
assert.Len(t, oo, 2)
}
================================================
FILE: internal/dao/dp.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
)
var (
_ Accessor = (*Deployment)(nil)
_ Nuker = (*Deployment)(nil)
_ Loggable = (*Deployment)(nil)
_ Restartable = (*Deployment)(nil)
_ Scalable = (*Deployment)(nil)
_ Controller = (*Deployment)(nil)
_ ContainsPodSpec = (*Deployment)(nil)
_ ImageLister = (*Deployment)(nil)
)
// Deployment represents a deployment K8s resource.
type Deployment struct {
Resource
}
// ListImages lists container images.
func (d *Deployment) ListImages(_ context.Context, fqn string) ([]string, error) {
dp, err := d.GetInstance(fqn)
if err != nil {
return nil, err
}
return render.ExtractImages(&dp.Spec.Template.Spec), nil
}
// Scale a Deployment.
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
return scaleRes(ctx, d.getFactory(), client.DpGVR, path, replicas)
}
// Restart a Deployment rollout.
func (d *Deployment) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path, opts)
}
// TailLogs tail logs for all pods represented by this Deployment.
func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
dp, err := d.GetInstance(opts.Path)
if err != nil {
return nil, err
}
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
return nil, fmt.Errorf("no valid selector found on deployment: %s", opts.Path)
}
return podLogs(ctx, dp.Spec.Selector.MatchLabels, opts)
}
// Pod returns a pod victim by name.
func (d *Deployment) Pod(fqn string) (string, error) {
dp, err := d.GetInstance(fqn)
if err != nil {
return "", err
}
return podFromSelector(d.Factory, dp.Namespace, dp.Spec.Selector.MatchLabels)
}
// GetInstance fetch a matching deployment.
func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
o, err := d.Factory.Get(d.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var dp appsv1.Deployment
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
if err != nil {
return nil, errors.New("expecting Deployment resource")
}
return &dp, nil
}
// ScanSA scans for serviceaccount refs.
func (d *Deployment) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var dp appsv1.Deployment
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
if err != nil {
return nil, errors.New("expecting Deployment resource")
}
if serviceAccountMatches(dp.Spec.Template.Spec.ServiceAccountName, n) {
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
}
}
return refs, nil
}
// Scan scans for resource references.
func (d *Deployment) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var dp appsv1.Deployment
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
if err != nil {
return nil, errors.New("expecting Deployment resource")
}
switch gvr {
case client.CmGVR:
if !hasConfigMap(&dp.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
case client.SecGVR:
found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait)
if err != nil {
slog.Warn("Fail to locate secret",
slogs.FQN, fqn,
slogs.Error, err,
)
continue
}
if !found {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
case client.PvcGVR:
if !hasPVC(&dp.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
case client.PcGVR:
if !hasPC(&dp.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(dp.Namespace, dp.Name),
})
}
}
return refs, nil
}
// GetPodSpec returns a pod spec given a resource.
func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) {
dp, err := d.GetInstance(path)
if err != nil {
return nil, err
}
podSpec := dp.Spec.Template.Spec
return &podSpec, nil
}
// SetImages sets container images.
func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
auth, err := d.Client().CanI(ns, d.gvr, n, client.PatchAccess)
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to patch a deployment")
}
jsonPatch, err := GetTemplateJsonPatch(imageSpecs)
if err != nil {
return err
}
dial, err := d.Client().Dial()
if err != nil {
return err
}
_, err = dial.AppsV1().Deployments(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
jsonPatch,
metav1.PatchOptions{},
)
return err
}
// Helpers...
func hasPVC(spec *v1.PodSpec, name string) bool {
for i := range spec.Volumes {
if spec.Volumes[i].PersistentVolumeClaim != nil && spec.Volumes[i].PersistentVolumeClaim.ClaimName == name {
return true
}
}
return false
}
func hasPC(spec *v1.PodSpec, name string) bool {
return spec.PriorityClassName == name
}
func hasConfigMap(spec *v1.PodSpec, name string) bool {
for i := range spec.InitContainers {
if containerHasConfigMap(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
return true
}
}
for i := range spec.Containers {
if containerHasConfigMap(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
return true
}
}
for i := range spec.EphemeralContainers {
if containerHasConfigMap(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
return true
}
}
for i := range spec.Volumes {
if cm := spec.Volumes[i].ConfigMap; cm != nil {
if cm.Name == name {
return true
}
}
}
return false
}
func hasSecret(f Factory, spec *v1.PodSpec, ns, name string, wait bool) (bool, error) {
for i := range spec.InitContainers {
if containerHasSecret(spec.InitContainers[i].EnvFrom, spec.InitContainers[i].Env, name) {
return true, nil
}
}
for i := range spec.Containers {
if containerHasSecret(spec.Containers[i].EnvFrom, spec.Containers[i].Env, name) {
return true, nil
}
}
for i := range spec.EphemeralContainers {
if containerHasSecret(spec.EphemeralContainers[i].EnvFrom, spec.EphemeralContainers[i].Env, name) {
return true, nil
}
}
for _, s := range spec.ImagePullSecrets {
if s.Name == name {
return true, nil
}
}
if saName := spec.ServiceAccountName; saName != "" {
o, err := f.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())
if err != nil {
return false, err
}
var sa v1.ServiceAccount
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sa)
if err != nil {
return false, errors.New("expecting ServiceAccount resource")
}
for _, ref := range sa.Secrets {
if ref.Namespace == ns && ref.Name == name {
return true, nil
}
}
}
for i := range spec.Volumes {
if sec := spec.Volumes[i].Secret; sec != nil {
if sec.SecretName == name {
return true, nil
}
}
}
return false, nil
}
func containerHasSecret(envFrom []v1.EnvFromSource, env []v1.EnvVar, name string) bool {
for _, e := range envFrom {
if e.SecretRef != nil && e.SecretRef.Name == name {
return true
}
}
for _, e := range env {
if e.ValueFrom == nil || e.ValueFrom.SecretKeyRef == nil {
continue
}
if e.ValueFrom.SecretKeyRef.Name == name {
return true
}
}
return false
}
func containerHasConfigMap(envFrom []v1.EnvFromSource, env []v1.EnvVar, name string) bool {
for _, e := range envFrom {
if e.ConfigMapRef != nil && e.ConfigMapRef.Name == name {
return true
}
}
for _, e := range env {
if e.ValueFrom == nil || e.ValueFrom.ConfigMapKeyRef == nil {
continue
}
if e.ValueFrom.ConfigMapKeyRef.Name == name {
return true
}
}
return false
}
func scaleRes(ctx context.Context, f Factory, gvr *client.GVR, path string, replicas int32) error {
ns, n := client.Namespaced(path)
auth, err := f.Client().CanI(ns, client.NewGVR(gvr.String()+":scale"), n, []string{client.GetVerb, client.UpdateVerb})
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to scale: %s", gvr)
}
dial, err := f.Client().Dial()
if err != nil {
return err
}
switch gvr {
case client.DpGVR:
scale, e := dial.AppsV1().Deployments(ns).GetScale(ctx, n, metav1.GetOptions{})
if e != nil {
return e
}
scale.Spec.Replicas = replicas
_, e = dial.AppsV1().Deployments(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
return e
case client.StsGVR:
scale, e := dial.AppsV1().StatefulSets(ns).GetScale(ctx, n, metav1.GetOptions{})
if e != nil {
return e
}
scale.Spec.Replicas = replicas
_, e = dial.AppsV1().StatefulSets(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
return e
default:
return fmt.Errorf("unsupported resource for scaling: %s", gvr)
}
}
func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GVR, path string, opts *metav1.PatchOptions) error {
o, err := f.Get(gvr, path, true, labels.Everything())
if err != nil {
return err
}
var r = new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, r)
if err != nil {
return err
}
ns, n := client.Namespaced(path)
auth, err := f.Client().CanI(ns, gvr, n, client.PatchAccess)
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to restart %q", gvr)
}
dial, err := f.Client().Dial()
if err != nil {
return err
}
before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), *r)
if err != nil {
return err
}
after, err := polymorphichelpers.ObjectRestarterFn(*r)
if err != nil {
return err
}
diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, *r)
if err != nil {
return err
}
switch gvr {
case client.DpGVR:
_, err = dial.AppsV1().Deployments(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
diff,
*opts,
)
case client.DsGVR:
_, err = dial.AppsV1().DaemonSets(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
diff,
*opts,
)
case client.StsGVR:
_, err = dial.AppsV1().StatefulSets(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
diff,
*opts,
)
}
return err
}
================================================
FILE: internal/dao/ds.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/watch"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
var (
_ Accessor = (*DaemonSet)(nil)
_ Nuker = (*DaemonSet)(nil)
_ Loggable = (*DaemonSet)(nil)
_ Restartable = (*DaemonSet)(nil)
_ Controller = (*DaemonSet)(nil)
_ ContainsPodSpec = (*DaemonSet)(nil)
_ ImageLister = (*DaemonSet)(nil)
)
// DaemonSet represents a K8s daemonset.
type DaemonSet struct {
Resource
}
// ListImages lists container images.
func (d *DaemonSet) ListImages(_ context.Context, fqn string) ([]string, error) {
ds, err := d.GetInstance(fqn)
if err != nil {
return nil, err
}
return render.ExtractImages(&ds.Spec.Template.Spec), nil
}
// Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path, opts)
}
// TailLogs tail logs for all pods represented by this DaemonSet.
func (d *DaemonSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
ds, err := d.GetInstance(opts.Path)
if err != nil {
return nil, err
}
if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 {
return nil, fmt.Errorf("no valid selector found on daemonset %q", opts.Path)
}
return podLogs(ctx, ds.Spec.Selector.MatchLabels, opts)
}
func podLogs(ctx context.Context, sel map[string]string, opts *LogOptions) ([]LogChan, error) {
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok {
return nil, errors.New("expecting a context factory")
}
ls, err := metav1.ParseToLabelSelector(toSelector(sel))
if err != nil {
return nil, err
}
lsel, err := metav1.LabelSelectorAsSelector(ls)
if err != nil {
return nil, err
}
ns, _ := client.Namespaced(opts.Path)
oo, err := f.List(client.PodGVR, ns, true, lsel)
if err != nil {
return nil, err
}
opts.MultiPods = true
var po Pod
po.Init(f, client.PodGVR)
outs := make([]LogChan, 0, len(oo))
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expected unstructured got %t", o)
}
opts = opts.Clone()
opts.Path = client.FQN(u.GetNamespace(), u.GetName())
cc, err := po.TailLogs(ctx, opts)
if err != nil {
return nil, err
}
outs = append(outs, cc...)
}
return outs, nil
}
// Pod returns a pod victim by name.
func (d *DaemonSet) Pod(fqn string) (string, error) {
ds, err := d.GetInstance(fqn)
if err != nil {
return "", err
}
return podFromSelector(d.Factory, ds.Namespace, ds.Spec.Selector.MatchLabels)
}
// GetInstance returns a daemonset instance.
func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
o, err := d.getFactory().Get(d.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return nil, errors.New("expecting DaemonSet resource")
}
return &ds, nil
}
// ScanSA scans for serviceaccount refs.
func (d *DaemonSet) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return nil, errors.New("expecting DaemonSet resource")
}
if serviceAccountMatches(ds.Spec.Template.Spec.ServiceAccountName, n) {
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
}
}
return refs, nil
}
// Scan scans for cluster refs.
func (d *DaemonSet) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := d.getFactory().List(d.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return nil, errors.New("expecting StatefulSet resource")
}
switch gvr {
case client.CmGVR:
if !hasConfigMap(&ds.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
case client.SecGVR:
found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait)
if err != nil {
slog.Warn("Unable to locate secret",
slogs.FQN, fqn,
slogs.Error, err,
)
continue
}
if !found {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
case client.PvcGVR:
if !hasPVC(&ds.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
case client.PcGVR:
if !hasPC(&ds.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: d.GVR(),
FQN: client.FQN(ds.Namespace, ds.Name),
})
}
}
return refs, nil
}
// GetPodSpec returns a pod spec given a resource.
func (d *DaemonSet) GetPodSpec(path string) (*v1.PodSpec, error) {
ds, err := d.GetInstance(path)
if err != nil {
return nil, err
}
podSpec := ds.Spec.Template.Spec
return &podSpec, nil
}
// SetImages sets container images.
func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
auth, err := d.Client().CanI(ns, d.gvr, n, client.PatchAccess)
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to patch a daemonset")
}
jsonPatch, err := GetTemplateJsonPatch(imageSpecs)
if err != nil {
return err
}
dial, err := d.Client().Dial()
if err != nil {
return err
}
_, err = dial.AppsV1().DaemonSets(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
jsonPatch,
metav1.PatchOptions{},
)
return err
}
// ----------------------------------------------------------------------------
// Helpers...
func toSelector(m map[string]string) string {
s := make([]string, 0, len(m))
for k, v := range m {
s = append(s, k+"="+v)
}
return strings.Join(s, ",")
}
================================================
FILE: internal/dao/dynamic.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
type Dynamic struct {
Generic
}
// Get returns a given resource as a table object.
func (d *Dynamic) Get(ctx context.Context, path string) (runtime.Object, error) {
oo, err := d.toTable(ctx, path)
if err != nil || len(oo) == 0 {
return nil, err
}
return oo[0], nil
}
// List returns a collection of resources as one or more table objects.
func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
return d.toTable(ctx, ns+"/")
}
func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) {
sel := labels.Everything()
if s, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
sel = s
}
opts := []string{d.gvr.AsResourceName()}
ns, n := client.Namespaced(fqn)
if n != "" {
opts = append(opts, n)
}
allNS := client.IsAllNamespaces(ns)
flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags())
f := cmdutil.NewFactory(flags)
b := f.NewBuilder().
Unstructured().
NamespaceParam(ns).DefaultNamespace().AllNamespaces(allNS).
LabelSelectorParam(sel.String()).
FieldSelectorParam("").
RequestChunksOf(0).
ResourceTypeOrNameArgs(true, opts...).
ContinueOnError().
Latest().
Flatten().
TransformRequests(d.transformRequests).
Do()
if err := b.Err(); err != nil {
return nil, err
}
infos, err := b.Infos()
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(infos))
for _, info := range infos {
o, err := decodeIntoTable(info.Object, allNS)
if err != nil {
return nil, err
}
oo = append(oo, o.(*metav1.Table))
}
return oo, nil
}
var recognizedTableVersions = map[schema.GroupVersionKind]bool{
metav1beta1.SchemeGroupVersion.WithKind("Table"): true,
metav1.SchemeGroupVersion.WithKind("Table"): true,
}
func decodeIntoTable(obj runtime.Object, allNs bool) (runtime.Object, error) {
event, isEvent := obj.(*metav1.WatchEvent)
if isEvent {
obj = event.Object.Object
}
if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] {
return nil, fmt.Errorf("attempt to decode non-Table object: %v", obj.GetObjectKind().GroupVersionKind())
}
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("attempt to decode non-Unstructured object")
}
var table metav1.Table
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &table); err != nil {
return nil, err
}
if allNs {
defs := make([]metav1.TableColumnDefinition, 0, len(table.ColumnDefinitions)+1)
defs = append(defs, metav1.TableColumnDefinition{Name: "Namespace", Type: "string"})
defs = append(defs, table.ColumnDefinitions...)
table.ColumnDefinitions = defs
}
for i := range table.Rows {
row := &table.Rows[i]
if row.Object.Raw == nil || row.Object.Object != nil {
continue
}
converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw)
if err != nil {
return nil, err
}
row.Object.Object = converted
var m metav1.Object
if obj := row.Object.Object; obj != nil {
m, _ = meta.Accessor(obj)
}
var ns string
if m != nil {
ns = m.GetNamespace()
}
if allNs {
cells := make([]any, 0, len(row.Cells)+1)
cells = append(cells, ns)
cells = append(cells, row.Cells...)
row.Cells = cells
}
}
if isEvent {
event.Object.Object = &table
return event, nil
}
return &table, nil
}
func (d *Dynamic) transformRequests(req *rest.Request) {
req.SetHeader("Accept", strings.Join([]string{
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
"application/json",
}, ","))
if d.includeObj {
req.Param("includeObject", "Object")
}
}
================================================
FILE: internal/dao/generic.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
)
type Grace int64
const (
// DefaultGrace uses delete default termination policy.
DefaultGrace Grace = -1
// ForceGrace sets delete grace-period to 0.
ForceGrace Grace = 0
// NowGrace set delete grace-period to 1,
NowGrace Grace = 1
)
var _ Describer = (*Generic)(nil)
// Generic represents a generic resource.
type Generic struct {
NonResource
}
// List returns a collection of resources.
// BOZO!! no auth check??
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, ok := ctx.Value(internal.KeyLabels).(labels.Selector)
if !ok {
labelSel = labels.Everything()
}
if client.IsAllNamespace(ns) {
ns = client.BlankNamespace
}
dial, err := g.dynClient()
if err != nil {
return nil, err
}
opts := metav1.ListOptions{LabelSelector: labelSel.String()}
var ll *unstructured.UnstructuredList
if client.IsClusterScoped(ns) {
ll, err = dial.List(ctx, opts)
} else {
ll, err = dial.Namespace(ns).List(ctx, opts)
}
if err != nil {
return nil, err
}
oo := make([]runtime.Object, len(ll.Items))
for i := range ll.Items {
oo[i] = &ll.Items[i]
}
return oo, nil
}
// Get returns a given resource.
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
ns, n := client.Namespaced(path)
dial, err := g.dynClient()
if err != nil {
return nil, err
}
var opts metav1.GetOptions
if client.IsClusterScoped(ns) {
return dial.Get(ctx, n, opts)
}
return dial.Namespace(ns).Get(ctx, n, opts)
}
// Describe describes a resource.
func (g *Generic) Describe(path string) (string, error) {
return Describe(g.Client(), g.gvr, path)
}
// ToYAML returns a resource yaml.
func (g *Generic) ToYAML(path string, showManaged bool) (string, error) {
o, err := g.Get(context.Background(), path)
if err != nil {
return "", err
}
raw, err := ToYAML(o, showManaged)
if err != nil {
return "", fmt.Errorf("unable to marshal resource %w", err)
}
return raw, nil
}
// Delete deletes a resource.
func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
ns, n := client.Namespaced(path)
auth, err := g.Client().CanI(ns, g.gvr, n, []string{client.DeleteVerb})
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to delete %s", path)
}
var gracePeriod *int64
if grace != DefaultGrace {
gracePeriod = (*int64)(&grace)
}
opts := metav1.DeleteOptions{
PropagationPolicy: propagation,
GracePeriodSeconds: gracePeriod,
}
dial, err := g.dynClient()
if err != nil {
return err
}
if client.IsClusterScoped(ns) {
return dial.Delete(ctx, n, opts)
}
ctx, cancel := context.WithTimeout(ctx, g.Client().Config().CallTimeout())
defer cancel()
return dial.Namespace(ns).Delete(ctx, n, opts)
}
func (g *Generic) dynClient() (dynamic.NamespaceableResourceInterface, error) {
dial, err := g.Client().DynDial()
if err != nil {
return nil, err
}
return dial.Resource(g.gvr.GVR()), nil
}
================================================
FILE: internal/dao/helm_chart.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"log/slog"
"os"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/render/helm"
"github.com/derailed/k9s/internal/slogs"
"helm.sh/helm/v3/pkg/action"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var (
_ Accessor = (*HelmChart)(nil)
_ Nuker = (*HelmChart)(nil)
_ Describer = (*HelmChart)(nil)
_ Valuer = (*HelmChart)(nil)
)
// HelmChart represents a helm chart.
type HelmChart struct {
NonResource
}
// List returns a collection of resources.
func (h *HelmChart) List(_ context.Context, ns string) ([]runtime.Object, error) {
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
}
list := action.NewList(cfg)
list.All = true
list.SetStateMask()
rr, err := list.Run()
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(rr))
for _, r := range rr {
oo = append(oo, helm.ReleaseRes{Release: r})
}
return oo, nil
}
// Get returns a resource.
func (h *HelmChart) Get(_ context.Context, path string) (runtime.Object, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return nil, err
}
return helm.ReleaseRes{Release: resp}, nil
}
// GetValues returns values for a release
func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
}
vals := action.NewGetValues(cfg)
vals.AllValues = allValues
resp, err := vals.Run(n)
if err != nil {
return nil, err
}
return data.WriteYAML(resp)
}
// Describe returns the chart notes.
func (h *HelmChart) Describe(path string) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return "", err
}
return resp.Info.Notes, nil
}
// ToYAML returns the chart manifest.
func (h *HelmChart) ToYAML(path string, _ bool) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return "", err
}
return resp.Manifest, nil
}
// Delete uninstall a HelmChart.
func (h *HelmChart) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
return h.Uninstall(path, false)
}
// Uninstall uninstalls a HelmChart.
func (h *HelmChart) Uninstall(path string, keepHist bool) error {
ns, n := client.Namespaced(path)
flags := h.Client().Config().Flags()
cfg, err := ensureHelmConfig(flags, ns)
if err != nil {
return err
}
u := action.NewUninstall(cfg)
u.KeepHistory = keepHist
res, err := u.Run(n)
if err != nil {
return err
}
if res != nil && res.Info != "" {
return fmt.Errorf("%s", res.Info)
}
return nil
}
// ensureHelmConfig return a new configuration.
func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action.Configuration, error) {
settings := &genericclioptions.ConfigFlags{
Namespace: &ns,
Context: flags.Context,
BearerToken: flags.BearerToken,
APIServer: flags.APIServer,
CAFile: flags.CAFile,
KubeConfig: flags.KubeConfig,
Impersonate: flags.Impersonate,
Insecure: flags.Insecure,
TLSServerName: flags.TLSServerName,
ImpersonateGroup: flags.ImpersonateGroup,
WrapConfigFn: flags.WrapConfigFn,
}
cfg := new(action.Configuration)
err := cfg.Init(settings, ns, os.Getenv("HELM_DRIVER"), helmLogger)
return cfg, err
}
func helmLogger(fmat string, args ...any) {
slog.Debug("Log",
slogs.Log, fmt.Sprintf(fmat, args...),
slogs.Subsys, "helm",
)
}
================================================
FILE: internal/dao/helm_history.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/render/helm"
"helm.sh/helm/v3/pkg/action"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*HelmHistory)(nil)
_ Nuker = (*HelmHistory)(nil)
_ Describer = (*HelmHistory)(nil)
_ Valuer = (*HelmHistory)(nil)
)
// HelmHistory represents a helm chart.
type HelmHistory struct {
NonResource
}
// List returns a collection of resources.
func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, error) {
path, ok := ctx.Value(internal.KeyFQN).(string)
if !ok {
return nil, fmt.Errorf("expecting FQN in context")
}
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
}
hh, err := action.NewHistory(cfg).Run(n)
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(hh))
for _, r := range hh {
oo = append(oo, helm.ReleaseRes{Release: r})
}
return oo, nil
}
// Get returns a resource.
func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) {
fqn, rev, found := strings.Cut(path, ":")
if !found || rev == "" {
return nil, fmt.Errorf("invalid path %q", path)
}
ns, n := client.Namespaced(fqn)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return nil, err
}
getter := action.NewGet(cfg)
getter.Version, err = strconv.Atoi(rev)
if err != nil {
return nil, err
}
resp, err := getter.Run(n)
if err != nil {
return nil, err
}
return helm.ReleaseRes{Release: resp}, nil
}
// Describe returns the chart notes.
func (h *HelmHistory) Describe(path string) (string, error) {
rel, err := h.Get(context.Background(), path)
if err != nil {
return "", err
}
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
return resp.Release.Info.Notes, nil
}
// ToYAML returns the chart manifest.
func (h *HelmHistory) ToYAML(path string, _ bool) (string, error) {
rel, err := h.Get(context.Background(), path)
if err != nil {
return "", err
}
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
return resp.Release.Manifest, nil
}
// GetValues return the config for this chart.
func (h *HelmHistory) GetValues(path string, allValues bool) ([]byte, error) {
rel, err := h.Get(context.Background(), path)
if err != nil {
return nil, err
}
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return nil, fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
var content any
if allValues {
content = resp.Release.Chart.Values
} else {
content = resp.Release.Config
}
return data.WriteYAML(content)
}
func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return err
}
ver, err := strconv.Atoi(rev)
if err != nil {
return fmt.Errorf("could not convert revision to a number: %w", err)
}
clt := action.NewRollback(cfg)
clt.Version = ver
return clt.Run(n)
}
// Delete uninstall a Helm.
func (h *HelmHistory) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns)
if err != nil {
return err
}
res, err := action.NewUninstall(cfg).Run(n)
if err != nil {
return err
}
if res != nil && res.Info != "" {
return fmt.Errorf("%s", res.Info)
}
return nil
}
================================================
FILE: internal/dao/helpers.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"bytes"
"errors"
"fmt"
"log/slog"
"math"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
)
const (
defaultServiceAccount = "default"
// DefaultContainerAnnotation represents the annotation key for the default container.
DefaultContainerAnnotation = "kubectl.kubernetes.io/default-container"
)
// GetDefaultContainer returns a container name if specified in an annotation.
func GetDefaultContainer(m *metav1.ObjectMeta, spec *v1.PodSpec) (string, bool) {
defaultContainer, ok := m.Annotations[DefaultContainerAnnotation]
if !ok {
return "", false
}
for i := range spec.Containers {
if spec.Containers[i].Name == defaultContainer {
return defaultContainer, true
}
}
slog.Warn("Container not found. Annotation ignored",
slogs.Container, defaultContainer,
slogs.Annotation, DefaultContainerAnnotation,
)
return "", false
}
func extractFQN(o runtime.Object) string {
u, ok := o.(*unstructured.Unstructured)
if !ok {
slog.Error("Expecting unstructured", slogs.ResType, fmt.Sprintf("%T", o))
return client.NA
}
return FQN(u.GetNamespace(), u.GetName())
}
// FQN returns a fully qualified resource name.
func FQN(ns, n string) string {
if ns == "" {
return n
}
return ns + "/" + n
}
func inList(ll []string, s string) bool {
for _, l := range ll {
if l == s {
return true
}
}
return false
}
func toPerc(v, dv float64) float64 {
if dv == 0 {
return 0
}
return math.Round((v / dv) * 100)
}
// ToYAML converts a resource to its YAML representation.
func ToYAML(o runtime.Object, showManaged bool) (string, error) {
if o == nil {
return "", errors.New("no object to yamlize")
}
var p printers.ResourcePrinter = &printers.YAMLPrinter{}
if !showManaged {
o = o.DeepCopyObject()
p = &printers.OmitManagedFieldsPrinter{Delegate: p}
}
var buff bytes.Buffer
if err := p.PrintObj(o, &buff); err != nil {
slog.Error("PrintObj failed", slogs.Error, err)
return "", err
}
return buff.String(), nil
}
// serviceAccountMatches validates that the ServiceAccount referenced in the PodSpec matches the incoming
// ServiceAccount. If the PodSpec ServiceAccount is blank kubernetes will use the "default" ServiceAccount
// when deploying the pod, so if the incoming SA is "default" and podSA is an empty string that is also a match.
func serviceAccountMatches(podSA, saName string) bool {
if podSA == "" {
podSA = defaultServiceAccount
}
return podSA == saName
}
// ContinuousRanges takes a sorted slice of integers and returns a slice of
// sub-slices representing continuous ranges of integers.
func ContinuousRanges(indexes []int) [][]int {
var ranges [][]int
for i, p := 1, 0; i <= len(indexes); i++ {
if i == len(indexes) || indexes[i]-indexes[p] != i-p {
ranges = append(ranges, []int{indexes[p], indexes[i-1] + 1})
p = i
}
}
return ranges
}
================================================
FILE: internal/dao/helpers_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
)
func TestToPerc(t *testing.T) {
uu := []struct {
v1, v2, e float64
}{
{0, 0, 0},
{100, 200, 50},
{200, 100, 200},
}
for _, u := range uu {
//nolint:testifylint
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
}
}
func TestServiceAccountMatches(t *testing.T) {
uu := []struct {
podTemplate *v1.PodSpec
saName string
expect bool
}{
{podTemplate: &v1.PodSpec{
ServiceAccountName: "",
},
saName: defaultServiceAccount,
expect: true,
},
{podTemplate: &v1.PodSpec{
ServiceAccountName: "",
},
saName: "foo",
expect: false,
},
{podTemplate: &v1.PodSpec{
ServiceAccountName: "foo",
},
saName: "foo",
expect: true,
},
{podTemplate: &v1.PodSpec{
ServiceAccountName: "foo",
},
saName: "bar",
expect: false,
},
}
for _, u := range uu {
assert.Equal(t, u.expect, serviceAccountMatches(u.podTemplate.ServiceAccountName, u.saName))
}
}
func TestContinuousRanges(t *testing.T) {
tests := []struct {
Indexes []int
Ranges [][]int
}{
{
Indexes: []int{0},
Ranges: [][]int{{0, 1}},
},
{
Indexes: []int{1},
Ranges: [][]int{{1, 2}},
},
{
Indexes: []int{0, 1, 2},
Ranges: [][]int{{0, 3}},
},
{
Indexes: []int{4, 5, 6},
Ranges: [][]int{{4, 7}},
},
{
Indexes: []int{0, 2, 4, 5, 6},
Ranges: [][]int{{0, 1}, {2, 3}, {4, 7}},
},
}
for _, tt := range tests {
assert.Equal(t, tt.Ranges, ContinuousRanges(tt.Indexes))
}
}
================================================
FILE: internal/dao/img_scan.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/vul"
"k8s.io/apimachinery/pkg/runtime"
)
var _ Accessor = (*ImageScan)(nil)
// ImageScan represents vulnerability scans.
type ImageScan struct {
NonResource
}
func (is *ImageScan) listImages(ctx context.Context, gvr *client.GVR, path string) ([]string, error) {
res, err := AccessorFor(is.Factory, gvr)
if err != nil {
return nil, err
}
s, ok := res.(ImageLister)
if !ok {
return nil, fmt.Errorf("resource %s is not image lister: %T", gvr, res)
}
return s.ListImages(ctx, path)
}
// List returns a collection of scans.
func (is *ImageScan) List(ctx context.Context, _ string) ([]runtime.Object, error) {
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, fmt.Errorf("no context path for %q", is.gvr)
}
gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, fmt.Errorf("no context gvr for %q", is.gvr)
}
ii, err := is.listImages(ctx, gvr, fqn)
if err != nil {
return nil, err
}
res := make([]runtime.Object, 0, len(ii))
for _, img := range ii {
s, ok := vul.ImgScanner.GetScan(img)
if !ok {
continue
}
for _, r := range s.Table.Rows {
res = append(res, render.ImageScanRes{Image: img, Row: r})
}
}
return res, nil
}
================================================
FILE: internal/dao/job.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Job)(nil)
_ Nuker = (*Job)(nil)
_ Loggable = (*Job)(nil)
_ ImageLister = (*Deployment)(nil)
)
// Job represents a K8s job resource.
type Job struct {
Resource
}
// ListImages lists container images.
func (j *Job) ListImages(_ context.Context, fqn string) ([]string, error) {
job, err := j.GetInstance(fqn)
if err != nil {
return nil, err
}
return render.ExtractImages(&job.Spec.Template.Spec), nil
}
// List returns a collection of resources.
func (j *Job) List(ctx context.Context, ns string) ([]runtime.Object, error) {
oo, err := j.Resource.List(ctx, ns)
if err != nil {
return nil, err
}
ctrl, _ := ctx.Value(internal.KeyPath).(string)
_, n := client.Namespaced(ctrl)
ll := make([]runtime.Object, 0, 10)
for _, o := range oo {
var j batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &j)
if err != nil {
return nil, errors.New("expecting Job resource")
}
if n == "" {
ll = append(ll, o)
continue
}
for _, r := range j.OwnerReferences {
if r.Name == n {
ll = append(ll, o)
}
}
}
return ll, nil
}
// TailLogs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
o, err := j.getFactory().Get(j.gvr, opts.Path, true, labels.Everything())
if err != nil {
return nil, err
}
var job batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
if err != nil {
return nil, errors.New("expecting a job resource")
}
if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 {
return nil, fmt.Errorf("no valid selector found for job: %s", opts.Path)
}
return podLogs(ctx, job.Spec.Selector.MatchLabels, opts)
}
func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
o, err := j.getFactory().Get(j.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var job batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
if err != nil {
return nil, errors.New("expecting a job resource")
}
return &job, nil
}
// ScanSA scans for serviceaccount refs.
func (j *Job) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := j.getFactory().List(j.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var job batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
if err != nil {
return nil, errors.New("expecting Job resource")
}
if serviceAccountMatches(job.Spec.Template.Spec.ServiceAccountName, n) {
refs = append(refs, Ref{
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
}
}
return refs, nil
}
// Scan scans for resource references.
func (j *Job) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := j.getFactory().List(j.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var job batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
if err != nil {
return nil, errors.New("expecting Job resource")
}
switch gvr {
case client.CmGVR:
if !hasConfigMap(&job.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
case client.SecGVR:
found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait)
if err != nil {
slog.Warn("Locate secret failed",
slogs.FQN, fqn,
slogs.Error, err,
)
continue
}
if !found {
continue
}
refs = append(refs, Ref{
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
case client.PcGVR:
if !hasPC(&job.Spec.Template.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: j.GVR(),
FQN: client.FQN(job.Namespace, job.Name),
})
}
}
return refs, nil
}
================================================
FILE: internal/dao/log_item.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"bytes"
)
// LogChan represents a channel for logs.
type LogChan chan *LogItem
var ItemEOF = new(LogItem)
// LogItem represents a container log line.
type LogItem struct {
Pod, Container string
SingleContainer bool
Bytes []byte
IsError bool
}
// NewLogItem returns a new item.
func NewLogItem(bb []byte) *LogItem {
return &LogItem{
Bytes: bb,
}
}
// NewLogItemFromString returns a new item.
func NewLogItemFromString(s string) *LogItem {
return &LogItem{
Bytes: []byte(s),
}
}
// ID returns pod and or container based id.
func (l *LogItem) ID() string {
if l.Pod != "" {
return l.Pod
}
return l.Container
}
// GetTimestamp fetch log lime timestamp
func (l *LogItem) GetTimestamp() string {
index := bytes.Index(l.Bytes, []byte{' '})
if index < 0 {
return ""
}
return string(l.Bytes[:index])
}
// Info returns pod and container information.
func (l *LogItem) Info() string {
return l.Pod + "::" + l.Container
}
// IsEmpty checks if the entry is empty.
func (l *LogItem) IsEmpty() bool {
return len(l.Bytes) == 0
}
// Size returns the size of the item.
func (l *LogItem) Size() int {
return 100 + len(l.Bytes) + len(l.Pod) + len(l.Container)
}
// Render returns a log line as string.
func (l *LogItem) Render(paint string, showTime bool, bb *bytes.Buffer) {
index := bytes.Index(l.Bytes, []byte{' '})
if showTime && index > 0 {
bb.WriteString("[gray::b]")
bb.Write(l.Bytes[:index])
bb.WriteString(" ")
if l := 30 - len(l.Bytes[:index]); l > 0 {
bb.Write(bytes.Repeat([]byte{' '}, l))
}
bb.WriteString("[-::-]")
}
if l.Pod != "" {
bb.WriteString("[" + paint + "::]" + l.Pod)
}
if !l.SingleContainer && l.Container != "" {
if l.Pod != "" {
bb.WriteString(" ")
}
bb.WriteString("[" + paint + "::b]" + l.Container + "[-::-] ")
} else if l.Pod != "" {
bb.WriteString("[-::] ")
}
if index > 0 {
bb.Write(l.Bytes[index+1:])
} else {
bb.Write(l.Bytes)
}
}
================================================
FILE: internal/dao/log_item_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"bytes"
"fmt"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/tview"
"github.com/stretchr/testify/assert"
)
func TestLogItemEmpty(t *testing.T) {
uu := map[string]struct {
s string
e bool
}{
"empty": {s: "", e: true},
"full": {s: "Testing 1,2,3..."},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
i := dao.NewLogItemFromString(u.s)
assert.Equal(t, u.e, i.IsEmpty())
})
}
}
func TestLogItemRender(t *testing.T) {
uu := map[string]struct {
opts dao.LogOptions
log string
e string
}{
"empty": {
opts: dao.LogOptions{},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."),
e: "Testing 1,2,3...\n",
},
"container": {
opts: dao.LogOptions{
Container: "fred",
},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."),
e: "[yellow::b]fred[-::-] Testing 1,2,3...\n",
},
"pod": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "blee",
SingleContainer: true,
},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."),
e: "[yellow::]fred [yellow::b]blee[-::-] Testing 1,2,3...\n",
},
"full": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "blee",
SingleContainer: true,
ShowTimestamp: true,
},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."),
e: "[gray::b]2018-12-14T10:36:43.326972-07:00 [-::-][yellow::]fred [yellow::b]blee[-::-] Testing 1,2,3...\n",
},
"log-level": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "",
SingleContainer: false,
ShowTimestamp: false,
},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "2021-10-28T13:06:37Z [INFO] [blah-blah] Testing 1,2,3..."),
e: "[yellow::]fred[-::] 2021-10-28T13:06:37Z [INFO[] [blah-blah[] Testing 1,2,3...\n",
},
"escape": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "",
SingleContainer: false,
ShowTimestamp: false,
},
log: fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", `{"foo":["bar"]} Server listening on: [::]:5000`),
e: `[yellow::]fred[-::] {"foo":["bar"[]} Server listening on: [::[]:5000` + "\n",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
i := dao.NewLogItem([]byte(tview.Escape(u.log)))
_, n := client.Namespaced(u.opts.Path)
i.Pod, i.Container = n, u.opts.Container
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
i.Render("yellow", u.opts.ShowTimestamp, bb)
assert.Equal(t, u.e, bb.String())
})
}
}
func BenchmarkLogItemRenderTS(b *testing.B) {
s := []byte(fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."))
i := dao.NewLogItem(s)
i.Pod, i.Container = "fred", "blee"
b.ResetTimer()
b.ReportAllocs()
for range b.N {
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
i.Render("yellow", true, bb)
}
}
func BenchmarkLogItemRenderNoTS(b *testing.B) {
s := []byte(fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."))
i := dao.NewLogItem(s)
i.Pod, i.Container = "fred", "blee"
b.ResetTimer()
b.ReportAllocs()
for range b.N {
bb := bytes.NewBuffer(make([]byte, 0, i.Size()))
i.Render("yellow", false, bb)
}
}
================================================
FILE: internal/dao/log_items.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"bytes"
"fmt"
"regexp"
"strings"
"sync"
"github.com/derailed/k9s/internal"
"github.com/sahilm/fuzzy"
)
type podColors map[string]string
var podPalette = []string{
"teal",
"green",
"purple",
"lime",
"blue",
"yellow",
"fushia",
"aqua",
}
// LogItems represents a collection of log items.
type LogItems struct {
items []*LogItem
podColors podColors
mx sync.RWMutex
}
// NewLogItems returns a new instance.
func NewLogItems() *LogItems {
return &LogItems{
podColors: make(map[string]string),
}
}
// Items returns the log items.
func (l *LogItems) Items() []*LogItem {
l.mx.RLock()
defer l.mx.RUnlock()
return l.items
}
// Len returns the items length.
func (l *LogItems) Len() int {
l.mx.RLock()
defer l.mx.RUnlock()
return len(l.items)
}
// Clear removes all items.
func (l *LogItems) Clear() {
l.mx.Lock()
defer l.mx.Unlock()
l.items = l.items[:0]
for k := range l.podColors {
delete(l.podColors, k)
}
}
// Shift scrolls the lines by one.
func (l *LogItems) Shift(i *LogItem) {
l.mx.Lock()
defer l.mx.Unlock()
l.items = append(l.items[1:], i)
}
// Subset return a subset of logitems.
func (l *LogItems) Subset(index int) *LogItems {
l.mx.RLock()
defer l.mx.RUnlock()
return &LogItems{
items: l.items[index:],
podColors: l.podColors,
}
}
// Merge merges two logitems list.
func (l *LogItems) Merge(n *LogItems) {
l.mx.Lock()
defer l.mx.Unlock()
l.items = append(l.items, n.items...)
for k, v := range n.podColors {
l.podColors[k] = v
}
}
// Add augments the items.
func (l *LogItems) Add(ii ...*LogItem) {
l.mx.Lock()
defer l.mx.Unlock()
l.items = append(l.items, ii...)
}
func (l *LogItems) podColorFor(id string) string {
color, ok := l.podColors[id]
if ok {
return color
}
var idx int
for i, r := range id {
idx += i * int(r)
}
l.podColors[id] = podPalette[idx%len(podPalette)]
return l.podColors[id]
}
// Lines returns a collection of log lines.
func (l *LogItems) Lines(index int, showTime bool, ll [][]byte) {
l.mx.Lock()
defer l.mx.Unlock()
for i, item := range l.items[index:] {
bb := bytes.NewBuffer(make([]byte, 0, item.Size()))
item.Render(l.podColorFor(item.ID()), showTime, bb)
ll[i] = bb.Bytes()
}
}
// StrLines returns a collection of log lines.
func (l *LogItems) StrLines(index int, showTime bool) []string {
l.mx.Lock()
defer l.mx.Unlock()
ll := make([]string, len(l.items[index:]))
for i, item := range l.items[index:] {
bb := bytes.NewBuffer(make([]byte, 0, item.Size()))
item.Render(l.podColorFor(item.ID()), showTime, bb)
ll[i] = bb.String()
}
return ll
}
// Render returns logs as a collection of strings.
func (l *LogItems) Render(index int, showTime bool, ll [][]byte) {
for i, item := range l.items[index:] {
bb := bytes.NewBuffer(make([]byte, 0, item.Size()))
item.Render(l.podColorFor(item.ID()), showTime, bb)
ll[i] = bb.Bytes()
}
}
// DumpDebug for debugging.
func (l *LogItems) DumpDebug(m string) {
fmt.Println(m + strings.Repeat("-", 50))
for i, line := range l.items {
fmt.Println(i, string(line.Bytes))
}
}
// Filter filters out log items based on given filter.
func (l *LogItems) Filter(index int, q string, showTime bool) (matches []int, indices [][]int, err error) {
if q == "" {
return
}
if f, ok := internal.IsFuzzySelector(q); ok {
matches, indices = l.fuzzyFilter(index, f, showTime)
return
}
matches, indices, err = l.filterLogs(index, q, showTime)
if err != nil {
return
}
return matches, indices, nil
}
func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) (matches []int, indices [][]int) {
q = strings.TrimSpace(q)
matches, indices = make([]int, 0, len(l.items)), make([][]int, 0, len(l.items))
mm := fuzzy.Find(q, l.StrLines(index, showTime))
for _, m := range mm {
matches = append(matches, m.Index)
indices = append(indices, m.MatchedIndexes)
}
return matches, indices
}
func (l *LogItems) filterLogs(index int, q string, showTime bool) (matches []int, indices [][]int, err error) {
var invert bool
if internal.IsInverseSelector(q) {
invert = true
q = q[1:]
}
rx, err := regexp.Compile(`(?i)` + q)
if err != nil {
return nil, nil, err
}
matches, indices = make([]int, 0, len(l.items)), make([][]int, 0, len(l.items))
ll := make([][]byte, len(l.items[index:]))
l.Lines(index, showTime, ll)
for i, line := range ll {
locs := rx.FindAllIndex(line, -1)
if locs != nil && invert {
continue
}
if locs == nil && !invert {
continue
}
matches = append(matches, i)
ii := make([]int, 0, 10)
for _, loc := range locs {
for j := loc[0]; j < loc[1]; j++ {
ii = append(ii, j)
}
}
indices = append(indices, ii)
}
return matches, indices, nil
}
================================================
FILE: internal/dao/log_items_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"fmt"
"log/slog"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
)
func init() {
slog.SetDefault(slog.New(slog.DiscardHandler))
}
func TestLogItemsFilter(t *testing.T) {
uu := map[string]struct {
q string
opts dao.LogOptions
e []int
indices [][]int
err error
}{
"empty": {
opts: dao.LogOptions{},
},
"pod-name": {
q: "blee",
opts: dao.LogOptions{
Path: "fred/blee",
Container: "c1",
},
e: []int{0, 1, 2},
},
"container-name": {
q: "c1",
opts: dao.LogOptions{
Path: "fred/blee",
Container: "c1",
},
e: []int{0, 1, 2},
indices: [][]int{{26, 27}, {26, 27}, {26, 27}}, // matches container name "c1" at positions 26-27 in rendered format each line
},
"message": {
q: "zorg",
opts: dao.LogOptions{
Path: "fred/blee",
Container: "c1",
},
e: []int{2},
},
"fuzzy": {
q: "-f zorg",
opts: dao.LogOptions{
Path: "fred/blee",
Container: "c1",
},
e: []int{2},
},
"multi-origin-text-match": {
q: "will",
opts: dao.LogOptions{
Path: "fred/blee",
Container: "c1",
},
e: []int{1, 2},
indices: [][]int{{45, 46, 47, 48, 59, 60, 61, 62}, {64, 65, 66, 67, 70, 71, 72, 73, 76, 77, 78, 79}},
},
}
for k := range uu {
u := uu[k]
ii := dao.NewLogItems()
ii.Add(
dao.NewLogItem([]byte(fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."))),
dao.NewLogItemFromString("Bumble bee tuna. will be back. will win."),
dao.NewLogItemFromString("Jean Batiste Emmanuel Zorg. wili, will. will, will"),
)
t.Run(k, func(t *testing.T) {
_, n := client.Namespaced(u.opts.Path)
for _, i := range ii.Items() {
i.Pod, i.Container = n, u.opts.Container
}
res, indices, err := ii.Filter(0, u.q, false)
assert.Equal(t, u.err, err)
if err == nil {
assert.Equal(t, u.e, res)
if u.indices != nil {
assert.Equal(t, u.indices, indices)
}
}
})
}
}
func TestLogItemsRender(t *testing.T) {
uu := map[string]struct {
opts dao.LogOptions
e string
}{
"empty": {
opts: dao.LogOptions{},
e: "Testing 1,2,3...\n",
},
"container": {
opts: dao.LogOptions{
Container: "fred",
},
e: "[teal::b]fred[-::-] Testing 1,2,3...\n",
},
"pod-container": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "blee",
},
e: "[teal::]fred [teal::b]blee[-::-] Testing 1,2,3...\n",
},
"full": {
opts: dao.LogOptions{
Path: "blee/fred",
Container: "blee",
ShowTimestamp: true,
},
e: "[gray::b]2018-12-14T10:36:43.326972-07:00 [-::-][teal::]fred [teal::b]blee[-::-] Testing 1,2,3...\n",
},
}
s := []byte(fmt.Sprintf("%s %s\n", "2018-12-14T10:36:43.326972-07:00", "Testing 1,2,3..."))
for k := range uu {
ii := dao.NewLogItems()
ii.Add(dao.NewLogItem(s))
u := uu[k]
_, n := client.Namespaced(u.opts.Path)
ii.Items()[0].Pod, ii.Items()[0].Container = n, u.opts.Container
t.Run(k, func(t *testing.T) {
res := make([][]byte, 1)
ii.Render(0, u.opts.ShowTimestamp, res)
assert.Equal(t, u.e, string(res[0]))
})
}
}
================================================
FILE: internal/dao/log_options.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"fmt"
"time"
"github.com/derailed/k9s/internal/client"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// LogOptions represents logger options.
type LogOptions struct {
CreateDuration time.Duration
Path string
Container string
DefaultContainer string
SinceTime string
Lines int64
SinceSeconds int64
Head bool
Previous bool
SingleContainer bool
MultiPods bool
ShowTimestamp bool
AllContainers bool
}
// Info returns the option pod and container info.
func (o *LogOptions) Info() string {
if o.Container != "" {
return fmt.Sprintf("%s (%s)", o.Path, o.Container)
}
return o.Path
}
// Clone clones options.
func (o *LogOptions) Clone() *LogOptions {
return &LogOptions{
Path: o.Path,
Container: o.Container,
DefaultContainer: o.DefaultContainer,
Lines: o.Lines,
Previous: o.Previous,
Head: o.Head,
SingleContainer: o.SingleContainer,
MultiPods: o.MultiPods,
ShowTimestamp: o.ShowTimestamp,
SinceTime: o.SinceTime,
SinceSeconds: o.SinceSeconds,
AllContainers: o.AllContainers,
}
}
// HasContainer checks if a container is present.
func (o *LogOptions) HasContainer() bool {
return o.Container != ""
}
// ToggleAllContainers toggles single or all-containers if possible.
func (o *LogOptions) ToggleAllContainers() {
if o.SingleContainer {
return
}
o.AllContainers = !o.AllContainers
if o.AllContainers {
o.DefaultContainer, o.Container = o.Container, ""
return
}
if o.DefaultContainer != "" {
o.Container = o.DefaultContainer
}
}
// ToPodLogOptions returns pod log options.
func (o *LogOptions) ToPodLogOptions() *v1.PodLogOptions {
opts := v1.PodLogOptions{
Follow: true,
Timestamps: true,
Container: o.Container,
Previous: o.Previous,
TailLines: &o.Lines,
}
if o.Head {
var maxBytes int64 = 5000
opts.Follow = false
opts.TailLines, opts.SinceSeconds, opts.SinceTime = nil, nil, nil
opts.LimitBytes = &maxBytes
return &opts
}
if o.SinceSeconds < 0 {
return &opts
}
if o.SinceSeconds != 0 {
opts.SinceSeconds, opts.SinceTime = &o.SinceSeconds, nil
return &opts
}
if o.SinceTime == "" {
return &opts
}
if t, err := time.Parse(time.RFC3339, o.SinceTime); err == nil {
opts.SinceTime = &metav1.Time{Time: t.Add(time.Second)}
}
return &opts
}
// ToLogItem add a log header to display po/co information along with the log message.
func (o *LogOptions) ToLogItem(bytes []byte) *LogItem {
item := NewLogItem(bytes)
if len(bytes) == 0 {
return item
}
item.SingleContainer = o.SingleContainer
if item.SingleContainer {
item.Container = o.Container
}
if o.MultiPods {
_, pod := client.Namespaced(o.Path)
item.Pod, item.Container = pod, o.Container
} else {
item.Container = o.Container
}
return item
}
func (*LogOptions) ToErrLogItem(err error) *LogItem {
t := time.Now().UTC().Format(time.RFC3339Nano)
item := NewLogItem([]byte(fmt.Sprintf("%s [orange::b]%s[::-]\n", t, err)))
item.IsError = true
return item
}
================================================
FILE: internal/dao/log_options_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"testing"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
)
func TestLogOptionsToggleAllContainers(t *testing.T) {
uu := map[string]struct {
opts dao.LogOptions
co string
want bool
}{
"empty": {
opts: dao.LogOptions{},
want: true,
},
"container": {
opts: dao.LogOptions{Container: "blee"},
want: true,
},
"default-container": {
opts: dao.LogOptions{AllContainers: true},
co: "blee",
},
"single-container": {
opts: dao.LogOptions{Container: "blee", SingleContainer: true},
co: "blee",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
u.opts.DefaultContainer = "blee"
u.opts.ToggleAllContainers()
assert.Equal(t, u.want, u.opts.AllContainers)
assert.Equal(t, u.co, u.opts.Container)
})
}
}
================================================
FILE: internal/dao/node.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/kubectl/pkg/drain"
"k8s.io/kubectl/pkg/scheme"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
var (
_ Accessor = (*Node)(nil)
_ NodeMaintainer = (*Node)(nil)
)
// NodeMetricsFunc retrieves node metrics.
type NodeMetricsFunc func() (*mv1beta1.NodeMetricsList, error)
// Node represents a node model.
type Node struct {
Resource
}
// ToggleCordon toggles cordon/uncordon a node.
func (n *Node) ToggleCordon(fqn string, cordon bool) error {
slog.Debug("Toggle cordon on node",
slogs.GVR, n.GVR(),
slogs.FQN, fqn,
slogs.Bool, cordon,
)
o, err := FetchNode(context.Background(), n.Factory, fqn)
if err != nil {
return err
}
h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK())
if err != nil {
slog.Debug("Fail to toggle cordon on node",
slogs.FQN, fqn,
slogs.Error, err,
)
return err
}
if !h.UpdateIfRequired(cordon) {
if cordon {
return fmt.Errorf("node is already cordoned")
}
return fmt.Errorf("node is already uncordoned")
}
dial, err := n.getFactory().Client().Dial()
if err != nil {
return err
}
err, patchErr := h.PatchOrReplace(dial, false)
if patchErr != nil {
return patchErr
}
if err != nil {
return err
}
return nil
}
func (o DrainOptions) toDrainHelper(k kubernetes.Interface, w io.Writer) drain.Helper {
return drain.Helper{
Client: k,
GracePeriodSeconds: o.GracePeriodSeconds,
Timeout: o.Timeout,
DeleteEmptyDirData: o.DeleteEmptyDirData,
IgnoreAllDaemonSets: o.IgnoreAllDaemonSets,
DisableEviction: o.DisableEviction,
Out: w,
ErrOut: w,
Force: o.Force,
}
}
// Drain drains a node.
func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
cordoned, err := n.ensureCordoned(path)
if err != nil {
return err
}
if !cordoned {
if e := n.ToggleCordon(path, true); e != nil {
return e
}
}
dial, err := n.getFactory().Client().Dial()
if err != nil {
return err
}
h := opts.toDrainHelper(dial, w)
dd, errs := h.GetPodsForDeletion(path)
if len(errs) != 0 {
for _, e := range errs {
if _, err := fmt.Fprintf(h.ErrOut, "[%s] %s\n", path, e.Error()); err != nil {
return err
}
}
return errors.Join(errs...)
}
if err := h.DeleteOrEvictPods(dd.Pods()); err != nil {
return err
}
_, _ = fmt.Fprintf(h.Out, "Node %s drained!", path)
return nil
}
// Get returns a node resource.
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
oo, err := n.Resource.List(ctx, "")
if err != nil {
return nil, err
}
var raw *unstructured.Unstructured
for _, o := range oo {
if u, ok := o.(*unstructured.Unstructured); ok && u.GetName() == path {
raw = u
}
}
if raw == nil {
return nil, fmt.Errorf("unable to locate node %s", path)
}
var nmx *mv1beta1.NodeMetrics
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx {
nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path)
}
return &render.NodeWithMetrics{Raw: raw, MX: nmx}, nil
}
// List returns a collection of node resources.
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
oo, err := n.Resource.List(ctx, ns)
if err != nil {
return oo, err
}
var nmx client.NodesMetricsMap
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetricsMap(ctx)
}
shouldCountPods, _ := ctx.Value(internal.KeyPodCounting).(bool)
var pods []runtime.Object
if shouldCountPods {
pods, err = n.getFactory().List(client.PodGVR, client.BlankNamespace, false, labels.Everything())
if err != nil {
slog.Error("Unable to list pods", slogs.Error, err)
}
}
res := make([]runtime.Object, 0, len(oo))
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
}
fqn := extractFQN(o)
_, name := client.Namespaced(fqn)
podCount := -1
if shouldCountPods {
podCount, err = n.CountPods(pods, name)
if err != nil {
slog.Error("Unable to get pods count",
slogs.ResName, name,
slogs.Error, err,
)
}
}
res = append(res, &render.NodeWithMetrics{
Raw: u,
MX: nmx[name],
PodCount: podCount,
})
}
return res, nil
}
// CountPods counts the pods scheduled on a given node.
func (*Node) CountPods(oo []runtime.Object, nodeName string) (int, error) {
var count int
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return count, fmt.Errorf("expecting *Unstructured but got `%T", o)
}
spec, ok := u.Object["spec"].(map[string]any)
if !ok {
return count, fmt.Errorf("expecting spec interface map but got `%T", o)
}
if node, ok := spec["nodeName"]; ok && node == nodeName {
count++
}
}
return count, nil
}
// GetPods returns all pods running on given node.
func (n *Node) GetPods(nodeName string) ([]*v1.Pod, error) {
oo, err := n.getFactory().List(client.PodGVR, client.BlankNamespace, false, labels.Everything())
if err != nil {
return nil, err
}
pp := make([]*v1.Pod, 0, len(oo))
for _, o := range oo {
po := new(v1.Pod)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, po); err != nil {
return nil, err
}
if po.Spec.NodeName == nodeName {
pp = append(pp, po)
}
}
return pp, nil
}
// ensureCordoned returns whether the given node has been cordoned
func (n *Node) ensureCordoned(path string) (bool, error) {
o, err := FetchNode(context.Background(), n.Factory, path)
if err != nil {
return false, err
}
return o.Spec.Unschedulable, nil
}
// ----------------------------------------------------------------------------
// Helpers...
// FetchNode retrieves a node.
func FetchNode(_ context.Context, f Factory, path string) (*v1.Node, error) {
_, n := client.Namespaced(path)
auth, err := f.Client().CanI(client.ClusterScope, client.NodeGVR, n, client.GetAccess)
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to list nodes")
}
o, err := f.Get(client.NodeGVR, client.FQN(client.ClusterScope, path), true, labels.Everything())
if err != nil {
return nil, err
}
var node v1.Node
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &node)
if err != nil {
return nil, err
}
return &node, nil
}
// FetchNodes retrieves all nodes.
func FetchNodes(_ context.Context, f Factory, _ string) (*v1.NodeList, error) {
auth, err := f.Client().CanI(client.ClusterScope, client.NodeGVR, "", client.ListAccess)
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to list nodes")
}
oo, err := f.List(client.NodeGVR, "", false, labels.Everything())
if err != nil {
return nil, err
}
nn := make([]v1.Node, 0, len(oo))
for _, o := range oo {
var node v1.Node
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &node)
if err != nil {
return nil, err
}
nn = append(nn, node)
}
return &v1.NodeList{Items: nn}, nil
}
================================================
FILE: internal/dao/non_resource.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"sync"
"github.com/derailed/k9s/internal/client"
"k8s.io/apimachinery/pkg/runtime"
)
// NonResource represents a non k8s resource.
type NonResource struct {
Factory
gvr *client.GVR
mx sync.RWMutex
includeObj bool
}
// Init initializes the resource.
func (n *NonResource) Init(f Factory, gvr *client.GVR) {
n.mx.Lock()
n.Factory, n.gvr = f, gvr
n.mx.Unlock()
}
// SetIncludeObject sets if resource object should be included in the api server response.
func (n *NonResource) SetIncludeObject(f bool) {
n.includeObj = f
}
func (n *NonResource) gvrStr() string {
n.mx.RLock()
defer n.mx.RUnlock()
return n.gvr.String()
}
func (n *NonResource) getFactory() Factory {
n.mx.RLock()
defer n.mx.RUnlock()
return n.Factory
}
// GVR returns a gvr.
func (n *NonResource) GVR() string {
n.mx.RLock()
defer n.mx.RUnlock()
return n.gvrStr()
}
// Get returns the given resource.
func (*NonResource) Get(context.Context, string) (runtime.Object, error) {
return nil, fmt.Errorf("nyi")
}
================================================
FILE: internal/dao/ns.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
var _ Accessor = (*Namespace)(nil)
// Namespace represents a namespace resource.
type Namespace struct {
Resource
}
================================================
FILE: internal/dao/patch.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"encoding/json"
)
// ImageSpec represents a container image.
type ImageSpec struct {
Index int
Name, DockerImage string
Init bool
}
// ImageSpecs represents a collection of container images.
type ImageSpecs []ImageSpec
// JsonPatch track pod spec updates.
type JsonPatch struct {
Spec Spec `json:"spec"`
}
// Spec represents a pod template.
type Spec struct {
Template PodSpec `json:"template"`
}
// PodSpec represents a collection of container images.
type PodSpec struct {
Spec ImagesSpec `json:"spec"`
}
// ImagesSpec tracks container image updates.
type ImagesSpec struct {
SetElementOrderContainers []Element `json:"$setElementOrder/containers,omitempty"`
SetElementOrderInitContainers []Element `json:"$setElementOrder/initContainers,omitempty"`
Containers []Element `json:"containers,omitempty"`
InitContainers []Element `json:"initContainers,omitempty"`
}
// Element tracks a given container image.
type Element struct {
Image string `json:"image,omitempty"`
Name string `json:"name"`
}
// GetTemplateJsonPatch builds a json patch string to update PodSpec images.
func GetTemplateJsonPatch(imageSpecs ImageSpecs) ([]byte, error) {
jsonPatch := JsonPatch{
Spec: Spec{
Template: getPatchPodSpec(imageSpecs),
},
}
return json.Marshal(jsonPatch)
}
// GetJsonPatch returns container image patch.
func GetJsonPatch(imageSpecs ImageSpecs) ([]byte, error) {
podSpec := getPatchPodSpec(imageSpecs)
return json.Marshal(podSpec)
}
func getPatchPodSpec(imageSpecs ImageSpecs) PodSpec {
initElementsOrders, initElements, elementsOrders, elements := extractElements(imageSpecs)
podSpec := PodSpec{
Spec: ImagesSpec{
SetElementOrderInitContainers: initElementsOrders,
InitContainers: initElements,
SetElementOrderContainers: elementsOrders,
Containers: elements,
},
}
return podSpec
}
func extractElements(imageSpecs ImageSpecs) (initElementsOrders, initElements, elementsOrders, elements []Element) {
for _, spec := range imageSpecs {
if spec.Init {
initElementsOrders = append(initElementsOrders, Element{Name: spec.Name})
initElements = append(initElements, Element{Name: spec.Name, Image: spec.DockerImage})
} else {
elementsOrders = append(elementsOrders, Element{Name: spec.Name})
elements = append(elements, Element{Name: spec.Name, Image: spec.DockerImage})
}
}
return initElementsOrders, initElements, elementsOrders, elements
}
================================================
FILE: internal/dao/patch_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetTemplateJsonPatch(t *testing.T) {
type args struct {
imageSpecs ImageSpecs
}
uu := map[string]struct {
args args
want string
wantErr bool
}{
"simple": {
args: args{
imageSpecs: ImageSpecs{
ImageSpec{
Index: 0,
Name: "init",
DockerImage: "busybox:latest",
Init: true,
},
ImageSpec{
Index: 0,
Name: "nginx",
DockerImage: "nginx:latest",
Init: false,
},
},
},
want: `{"spec":{"template":{"spec":{"$setElementOrder/initContainers":[{"name":"init"}],"$setElementOrder/containers":[{"name":"nginx"}],"initContainers":[{"image":"busybox:latest","name":"init"}],"containers":[{"image":"nginx:latest","name":"nginx"}]}}}}`,
wantErr: false,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
got, err := GetTemplateJsonPatch(u.args.imageSpecs)
if (err != nil) != u.wantErr {
t.Errorf("GetTemplateJsonPatch() error = %v, wantErr %v", err, u.wantErr)
return
}
require.JSONEq(t, u.want, string(got), "Json strings should be equal")
})
}
}
func TestGetJsonPatch(t *testing.T) {
type args struct {
imageSpecs ImageSpecs
}
uu := map[string]struct {
args args
want string
wantErr bool
}{
"simple": {
args: args{
imageSpecs: ImageSpecs{
ImageSpec{
Index: 0,
Name: "init",
DockerImage: "busybox:latest",
Init: true,
},
ImageSpec{
Index: 0,
Name: "nginx",
DockerImage: "nginx:latest",
Init: false,
},
},
},
want: `{"spec":{"$setElementOrder/initContainers":[{"name":"init"}],"initContainers":[{"image":"busybox:latest","name":"init"}],"$setElementOrder/containers":[{"name":"nginx"}],"containers":[{"image":"nginx:latest","name":"nginx"}]}}`,
wantErr: false,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
got, err := GetJsonPatch(u.args.imageSpecs)
if (err != nil) != u.wantErr {
t.Errorf("GetTemplateJsonPatch() error = %v, wantErr %v", err, u.wantErr)
return
}
require.JSONEq(t, u.want, string(got), "Json strings should be equal")
})
}
}
================================================
FILE: internal/dao/pod.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log/slog"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/watch"
"github.com/derailed/tview"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
restclient "k8s.io/client-go/rest"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
var (
_ Accessor = (*Pod)(nil)
_ Nuker = (*Pod)(nil)
_ Loggable = (*Pod)(nil)
_ Controller = (*Pod)(nil)
_ ContainsPodSpec = (*Pod)(nil)
_ ImageLister = (*Pod)(nil)
)
type streamResult int
const (
logRetryCount = 20
logBackoffInitial = 500 * time.Millisecond
logBackoffMax = 30 * time.Second
logChannelBuffer = 50 // Buffer size for log channel to reduce drops
streamEOF streamResult = iota // legit container log close (no retry)
streamError // retryable error (network, auth, etc.)
streamCanceled // context canceled
)
// Pod represents a pod resource.
type Pod struct {
Resource
}
// shouldStopRetrying checks if we should stop retrying log streaming based on pod status.
func (p *Pod) shouldStopRetrying(path string) bool {
pod, err := p.GetInstance(path)
if err != nil {
return true
}
if pod.DeletionTimestamp != nil {
return true
}
switch pod.Status.Phase {
case v1.PodSucceeded, v1.PodFailed:
return true
default:
return false
}
}
// Get returns a resource instance if found, else an error.
func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
o, err := p.Resource.Get(ctx, path)
if err != nil {
return o, err
}
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
}
var pmx *mv1beta1.PodMetrics
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx {
pmx, _ = client.DialMetrics(p.Client()).FetchPodMetrics(ctx, path)
}
return &render.PodWithMetrics{Raw: u, MX: pmx}, nil
}
// ListImages lists container images.
func (p *Pod) ListImages(_ context.Context, path string) ([]string, error) {
pod, err := p.GetInstance(path)
if err != nil {
return nil, err
}
return render.ExtractImages(&pod.Spec), nil
}
// List returns a collection of nodes.
func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
oo, err := p.Resource.List(ctx, ns)
if err != nil {
return oo, err
}
var pmx client.PodsMetricsMap
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx {
pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetricsMap(ctx, ns)
}
sel, _ := ctx.Value(internal.KeyFields).(string)
fsel, err := labels.ConvertSelectorToLabelsMap(sel)
if err != nil {
return nil, err
}
nodeName := fsel["spec.nodeName"]
res := make([]runtime.Object, 0, len(oo))
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
}
fqn := extractFQN(o)
if nodeName == "" {
res = append(res, &render.PodWithMetrics{Raw: u, MX: pmx[fqn]})
continue
}
spec, ok := u.Object["spec"].(map[string]any)
if !ok {
return res, fmt.Errorf("expecting interface map but got `%T", o)
}
if spec["nodeName"] == nodeName {
res = append(res, &render.PodWithMetrics{Raw: u, MX: pmx[fqn]})
}
}
return res, nil
}
// Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
ns, n := client.Namespaced(path)
auth, err := p.Client().CanI(ns, client.NewGVR(client.PodGVR.String()+":log"), n, client.GetAccess)
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to view pod logs")
}
dial, err := p.Client().DialLogs()
if err != nil {
return nil, err
}
return dial.CoreV1().Pods(ns).GetLogs(n, opts), nil
}
// Containers returns all container names on pod.
func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
pod, err := p.GetInstance(path)
if err != nil {
return nil, err
}
cc := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
for i := range pod.Spec.Containers {
cc = append(cc, pod.Spec.Containers[i].Name)
}
if includeInit {
for i := range pod.Spec.InitContainers {
cc = append(cc, pod.Spec.InitContainers[i].Name)
}
}
return cc, nil
}
// Pod returns a pod victim by name.
func (*Pod) Pod(fqn string) (string, error) {
return fqn, nil
}
// GetInstance returns a pod instance.
func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) {
o, err := p.getFactory().Get(p.gvr, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
if err != nil {
return nil, err
}
return &pod, nil
}
// TailLogs tails a given container logs.
func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok {
return nil, errors.New("no factory in context")
}
o, err := fac.Get(p.gvr, opts.Path, true, labels.Everything())
if err != nil {
return nil, err
}
var po v1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
return nil, err
}
coCounts := len(po.Spec.InitContainers) + len(po.Spec.Containers) + len(po.Spec.EphemeralContainers)
if coCounts == 1 {
opts.SingleContainer = true
}
outs := make([]LogChan, 0, coCounts)
if co, ok := GetDefaultContainer(&po.ObjectMeta, &po.Spec); ok && !opts.AllContainers {
opts.DefaultContainer = co
return append(outs, tailLogs(ctx, p, opts)), nil
}
if opts.HasContainer() && !opts.AllContainers {
return append(outs, tailLogs(ctx, p, opts)), nil
}
for i := range po.Spec.InitContainers {
cfg := opts.Clone()
cfg.Container = po.Spec.InitContainers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
for i := range po.Spec.Containers {
cfg := opts.Clone()
cfg.Container = po.Spec.Containers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
for i := range po.Spec.EphemeralContainers {
cfg := opts.Clone()
cfg.Container = po.Spec.EphemeralContainers[i].Name
outs = append(outs, tailLogs(ctx, p, cfg))
}
return outs, nil
}
// ScanSA scans for ServiceAccount refs.
func (p *Pod) ScanSA(_ context.Context, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := p.getFactory().List(p.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
if err != nil {
return nil, errors.New("expecting Deployment resource")
}
// Just pick controller less pods...
if len(pod.OwnerReferences) > 0 {
continue
}
if serviceAccountMatches(pod.Spec.ServiceAccountName, n) {
refs = append(refs, Ref{
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
}
}
return refs, nil
}
// Scan scans for cluster resource refs.
func (p *Pod) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
ns, n := client.Namespaced(fqn)
oo, err := p.getFactory().List(p.gvr, ns, wait, labels.Everything())
if err != nil {
return nil, err
}
refs := make(Refs, 0, len(oo))
for _, o := range oo {
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
if err != nil {
return nil, errors.New("expecting Pod resource")
}
// Just pick controller less pods...
if len(pod.OwnerReferences) > 0 {
continue
}
switch gvr {
case client.CmGVR:
if !hasConfigMap(&pod.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
case client.SecGVR:
found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
if err != nil {
slog.Warn("Locate secret failed",
slogs.FQN, fqn,
slogs.Error, err,
)
continue
}
if !found {
continue
}
refs = append(refs, Ref{
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
case client.PvcGVR:
if !hasPVC(&pod.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
case client.PcGVR:
if !hasPC(&pod.Spec, n) {
continue
}
refs = append(refs, Ref{
GVR: p.GVR(),
FQN: client.FQN(pod.Namespace, pod.Name),
})
}
}
return refs, nil
}
// ----------------------------------------------------------------------------
// Helpers...
func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan {
out := make(LogChan, logChannelBuffer)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
podOpts := opts.ToPodLogOptions()
// Setup exponential backoff following project pattern
bf := backoff.NewExponentialBackOff()
bf.InitialInterval = logBackoffInitial
bf.MaxElapsedTime = 0
bf.MaxInterval = logBackoffMax / 2
backoffCtx := backoff.WithContext(bf, ctx)
delay := logBackoffInitial
for range logRetryCount {
req, err := logger.Logs(opts.Path, podOpts)
if err != nil {
slog.Error("Log request failed",
slogs.Container, opts.Info(),
slogs.Error, err,
)
// Check if we should stop retrying based on pod status
if pod, ok := logger.(*Pod); ok && pod.shouldStopRetrying(opts.Path) {
slog.Debug("Stopping log retry - pod is terminating or deleted",
slogs.Container, opts.Info(),
)
return
}
select {
case <-ctx.Done():
return
case <-time.After(delay):
if delay = backoffCtx.NextBackOff(); delay == backoff.Stop {
return
}
}
continue
}
stream, e := req.Stream(ctx)
if e != nil {
slog.Error("Stream logs failed",
slogs.Error, e,
slogs.Container, opts.Info(),
)
// Check if we should stop retrying based on pod status
if pod, ok := logger.(*Pod); ok && pod.shouldStopRetrying(opts.Path) {
slog.Debug("Stopping log retry - pod is terminating or deleted",
slogs.Container, opts.Info(),
)
return
}
select {
case <-ctx.Done():
return
case <-time.After(delay):
if delay = backoffCtx.NextBackOff(); delay == backoff.Stop {
return
}
}
continue
}
// Process logs until completion
result := readLogs(ctx, stream, out, opts)
switch result {
case streamEOF:
slog.Debug("Log stream ended cleanly",
slogs.Container, opts.Info(),
)
return
case streamError:
// Check if we should stop retrying based on pod status
if pod, ok := logger.(*Pod); ok && pod.shouldStopRetrying(opts.Path) {
slog.Debug("Stopping log retry after stream error - pod is terminating or deleted",
slogs.Container, opts.Info(),
)
return
}
slog.Debug("Log stream error, retrying",
slogs.Container, opts.Info(),
)
select {
case <-ctx.Done():
return
case <-time.After(delay):
if delay = backoffCtx.NextBackOff(); delay == backoff.Stop {
return
}
}
continue
case streamCanceled:
return
}
// Reset backoff and delay on successful connection
bf.Reset()
delay = logBackoffInitial
}
// Out of retries
out <- opts.ToErrLogItem(fmt.Errorf("failed to maintain log stream after %d retries", logRetryCount))
}()
go func() {
wg.Wait()
close(out)
}()
return out
}
func readLogs(ctx context.Context, stream io.ReadCloser, out chan<- *LogItem, opts *LogOptions) streamResult {
defer func() {
if err := stream.Close(); err != nil && !errors.Is(err, io.ErrClosedPipe) {
slog.Error("Failed to close stream",
slogs.Container, opts.Info(),
slogs.Error, err,
)
}
}()
r := bufio.NewReader(stream)
for {
bytes, err := r.ReadBytes('\n')
if err == nil {
item := opts.ToLogItem(tview.EscapeBytes(bytes))
select {
case <-ctx.Done():
return streamCanceled
case out <- item:
default:
// Avoid deadlock if consumer is too slow
slog.Warn("Dropping log line due to slow consumer",
slogs.Container, opts.Info(),
)
}
continue
}
if errors.Is(err, io.EOF) {
if len(bytes) > 0 {
// Emit trailing partial line before EOF
out <- opts.ToLogItem(tview.EscapeBytes(bytes))
}
slog.Debug("Log reader reached EOF", slogs.Container, opts.Info())
out <- opts.ToErrLogItem(fmt.Errorf("stream closed: %w for %s", err, opts.Info()))
return streamEOF
}
// Non-EOF error
slog.Debug("Log stream error, will retry connection",
slogs.Container, opts.Info(),
slogs.Error, fmt.Errorf("stream error: %w for %s", err, opts.Info()),
)
// Don't send stream errors to user - they will be retried
// Only final retry exhaustion message is shown
return streamError
}
}
// MetaFQN returns a fully qualified resource name.
func MetaFQN(m *metav1.ObjectMeta) string {
if m.Namespace == "" {
return m.Name
}
return FQN(m.Namespace, m.Name)
}
// GetPodSpec returns a pod spec given a resource.
func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) {
pod, err := p.GetInstance(path)
if err != nil {
return nil, err
}
podSpec := pod.Spec
return &podSpec, nil
}
// SetImages sets container images.
func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
ns, n := client.Namespaced(path)
auth, err := p.Client().CanI(ns, p.gvr, n, client.PatchAccess)
if err != nil {
return err
}
if !auth {
return fmt.Errorf("user is not authorized to patch a deployment")
}
manager, isManaged, err := p.isControlled(path)
if err != nil {
return err
}
if isManaged {
return fmt.Errorf("unable to set image. This pod is managed by %s. Please set the image on the controller", manager)
}
jsonPatch, err := GetJsonPatch(imageSpecs)
if err != nil {
return err
}
dial, err := p.Client().Dial()
if err != nil {
return err
}
_, err = dial.CoreV1().Pods(ns).Patch(
ctx,
n,
types.StrategicMergePatchType,
jsonPatch,
metav1.PatchOptions{},
)
return err
}
func (p *Pod) isControlled(path string) (fqn string, ok bool, err error) {
pod, err := p.GetInstance(path)
if err != nil {
return "", false, err
}
references := pod.GetObjectMeta().GetOwnerReferences()
if len(references) > 0 {
return fmt.Sprintf("%s/%s", references[0].Kind, references[0].Name), true, nil
}
return "", false, nil
}
var toastPhases = sets.New(
render.PhaseCompleted,
render.PhasePending,
render.PhaseCrashLoop,
render.PhaseError,
render.PhaseImagePullBackOff,
render.PhaseContainerStatusUnknown,
render.PhaseEvicted,
render.PhaseOOMKilled,
)
func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) {
oo, err := p.Resource.List(ctx, ns)
if err != nil {
return 0, err
}
var count int
for _, o := range oo {
u, ok := o.(*unstructured.Unstructured)
if !ok {
continue
}
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &pod)
if err != nil {
continue
}
if toastPhases.Has(render.PodStatus(&pod)) {
// !!BOZO!! Might need to bump timeout otherwise rev limit if too many??
fqn := client.FQN(pod.Namespace, pod.Name)
slog.Debug("Sanitizing resource", slogs.FQN, fqn)
if err := p.Delete(ctx, fqn, nil, 0); err != nil {
slog.Debug("Aborted! Sanitizer delete failed",
slogs.FQN, fqn,
slogs.Count, count,
slogs.Error, err,
)
return count, err
}
count++
}
}
slog.Debug("Sanitizer deleted pods", slogs.Count, count)
return count, nil
}
================================================
FILE: internal/dao/pod_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGetDefaultContainer(t *testing.T) {
uu := map[string]struct {
po *v1.Pod
wantContainer string
wantOk bool
}{
"no_annotation": {
po: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "container1"}},
},
},
wantContainer: "",
wantOk: false,
},
"container_not_present": {
po: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{DefaultContainerAnnotation: "container1"},
},
},
wantContainer: "",
wantOk: false,
},
"container_found": {
po: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{DefaultContainerAnnotation: "container1"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "container1"}},
},
},
wantContainer: "container1",
wantOk: true,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
container, ok := GetDefaultContainer(&u.po.ObjectMeta, &u.po.Spec)
assert.Equal(t, u.wantContainer, container)
assert.Equal(t, u.wantOk, ok)
})
}
}
================================================
FILE: internal/dao/port_forward.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"log/slog"
"regexp"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*PortForward)(nil)
_ Nuker = (*PortForward)(nil)
)
// PortForward represents a port forward dao.
type PortForward struct {
NonResource
}
// Delete deletes a portforward.
func (p *PortForward) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
p.getFactory().DeleteForwarder(path)
return nil
}
// List returns a collection of port forwards.
func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, error) {
benchFile, ok := ctx.Value(internal.KeyBenchCfg).(string)
if !ok || benchFile == "" {
return nil, fmt.Errorf("no benchmark config file found in context")
}
path, _ := ctx.Value(internal.KeyPath).(string)
bcfg, err := config.NewBench(benchFile)
if err != nil {
slog.Debug("No custom benchmark config file found", slogs.FileName, benchFile)
}
ff, cc := p.getFactory().Forwarders(), bcfg.Benchmarks.Containers
oo := make([]runtime.Object, 0, len(ff))
for k, f := range ff {
if !strings.HasPrefix(k, path) {
continue
}
cfg := render.BenchCfg{
C: bcfg.Benchmarks.Defaults.C,
N: bcfg.Benchmarks.Defaults.N,
}
if cust, ok := cc[PodToKey(k)]; ok {
cfg.C, cfg.N = cust.C, cust.N
cfg.Host, cfg.Path = cust.HTTP.Host, cust.HTTP.Path
}
oo = append(oo, render.ForwardRes{
Forwarder: f,
Config: cfg,
})
}
return oo, nil
}
// ----------------------------------------------------------------------------
// Helpers...
var podNameRX = regexp.MustCompile(`\A(.+)\-(\w{10})\-(\w{5})\z`)
// PodToKey converts a pod path to a generic bench config key.
func PodToKey(path string) string {
tokens := strings.Split(path, "|")
ns, po := client.Namespaced(tokens[0])
sections := podNameRX.FindStringSubmatch(po)
if len(sections) >= 1 {
po = sections[1]
}
return client.FQN(ns, po) + ":" + tokens[1]
}
// BenchConfigFor returns a custom bench spec if defined otherwise returns the default one.
func BenchConfigFor(benchFile, path string) config.BenchConfig {
def := config.DefaultBenchSpec()
cust, err := config.NewBench(benchFile)
if err != nil {
slog.Debug("No custom benchmark config file found. Using default",
slogs.FileName, benchFile,
slogs.Error, err,
)
return def
}
if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok {
return b
}
def.C, def.N = cust.Benchmarks.Defaults.C, cust.Benchmarks.Defaults.N
return def
}
================================================
FILE: internal/dao/port_forward_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
)
func TestBenchForConfig(t *testing.T) {
uu := map[string]struct {
file, key string
spec config.BenchConfig
}{
"no_file": {file: "", key: "", spec: config.DefaultBenchSpec()},
"spec": {file: "testdata/benchspec.yaml", key: "default/nginx-123-456|nginx", spec: config.BenchConfig{
C: 2,
N: 3000,
HTTP: config.HTTP{
Method: "GET",
Path: "/",
},
}},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.NotNil(t, u.spec, dao.BenchConfigFor(u.file, u.key))
})
}
}
================================================
FILE: internal/dao/port_forwarder.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/port"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
const defaultTimeout = 30 * time.Second
// PortForwarder tracks a port forward stream.
type PortForwarder struct {
Factory
genericclioptions.IOStreams
stopChan, readyChan chan struct{}
active bool
path string
tunnel port.PortTunnel
age time.Time
}
// NewPortForwarder returns a new port forward streamer.
func NewPortForwarder(f Factory) *PortForwarder {
return &PortForwarder{
Factory: f,
stopChan: make(chan struct{}),
readyChan: make(chan struct{}),
}
}
// String dumps as string.
func (p *PortForwarder) String() string {
return fmt.Sprintf("%s|%s", p.path, p.tunnel)
}
// Age returns the port forward age.
func (p *PortForwarder) Age() time.Time {
return p.age
}
// Active returns the forward status.
func (p *PortForwarder) Active() bool {
return p.active
}
// SetActive mark a portforward as active.
func (p *PortForwarder) SetActive(b bool) {
p.active = b
}
// Port returns the port mapping.
func (p *PortForwarder) Port() string {
return p.tunnel.PortMap()
}
// Address returns the port Address.
func (p *PortForwarder) Address() string {
return p.tunnel.Address
}
// ContainerPort returns the container port.
func (p *PortForwarder) ContainerPort() string {
return p.tunnel.ContainerPort
}
// LocalPort returns the local port.
func (p *PortForwarder) LocalPort() string {
return p.tunnel.LocalPort
}
// ID returns a pf id.
func (p *PortForwarder) ID() string {
return PortForwardID(p.path, p.tunnel.Container, p.tunnel.PortMap())
}
// Container returns the target's container.
func (p *PortForwarder) Container() string {
return p.tunnel.Container
}
// Stop terminates a port forward.
func (p *PortForwarder) Stop() {
p.active = false
if p.stopChan != nil {
close(p.stopChan)
p.stopChan = nil
}
}
// FQN returns the portforward unique id.
func (p *PortForwarder) FQN() string {
return p.path + ":" + p.tunnel.Container
}
// HasPortMapping checks if port mapping is defined for this fwd.
func (p *PortForwarder) HasPortMapping(portMap string) bool {
return p.tunnel.PortMap() == portMap
}
// Start initiates a port forward session for a given pod and ports.
func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.PortForwarder, error) {
p.path, p.tunnel, p.age = path, tt, time.Now()
ns, n := client.Namespaced(path)
auth, err := p.Client().CanI(ns, client.PodGVR, n, client.GetAccess)
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to get pods")
}
podName := strings.Split(n, "|")[0]
var res Pod
res.Init(p, client.PodGVR)
pod, err := res.GetInstance(client.FQN(ns, podName))
if err != nil {
return nil, err
}
if pod.Status.Phase != v1.PodRunning {
return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
}
auth, err = p.Client().CanI(ns, client.PodGVR.WithSubResource("portforward"), "", []string{client.CreateVerb})
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to update portforward")
}
cfg, err := p.Client().RestConfig()
if err != nil {
return nil, err
}
cfg.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"}
cfg.APIPath = "/api"
codec, _ := codec()
cfg.NegotiatedSerializer = codec.WithoutConversion()
clt, err := rest.RESTClientFor(cfg)
if err != nil {
return nil, err
}
req := clt.Post().
Resource("pods").
Namespace(ns).
Name(podName).
SubResource("portforward")
return p.forwardPorts("POST", req.URL(), tt.Address, tt.PortMap())
}
func (p *PortForwarder) forwardPorts(method string, u *url.URL, addr, portMap string) (*portforward.PortForwarder, error) {
cfg, err := p.Client().Config().RESTConfig()
if err != nil {
return nil, err
}
transport, upgrader, err := spdy.RoundTripperFor(cfg)
if err != nil {
return nil, err
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport, Timeout: defaultTimeout}, method, u)
if !cmdutil.PortForwardWebsockets.IsDisabled() {
tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(u, cfg)
if err != nil {
return nil, err
}
// First attempt tunneling (websocket) dialer, then fallback to spdy dialer.
dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool {
return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
})
}
return portforward.NewOnAddresses(dialer, []string{addr}, []string{portMap}, p.stopChan, p.readyChan, p.Out, p.ErrOut)
}
// ----------------------------------------------------------------------------
// Helpers...
// PortForwardID computes port-forward identifier.
func PortForwardID(path, co, portMap string) string {
if strings.Contains(path, "|") {
return path + "|" + portMap
}
return path + "|" + co + "|" + portMap
}
func codec() (serializer.CodecFactory, runtime.ParameterCodec) {
scheme := runtime.NewScheme()
gv := schema.GroupVersion{Group: "", Version: "v1"}
metav1.AddToGroupVersion(scheme, gv)
scheme.AddKnownTypes(gv, &metav1.Table{}, &metav1.TableOptions{})
scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{})
return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme)
}
================================================
FILE: internal/dao/pulse.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
)
// Pulse tracks pulses.
type Pulse struct {
NonResource
}
// List lists out pulses.
func (*Pulse) List(context.Context, string) ([]runtime.Object, error) {
return nil, fmt.Errorf("NYI")
}
================================================
FILE: internal/dao/rbac.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Rbac)(nil)
_ Nuker = (*Rbac)(nil)
)
// Rbac represents a model for listing rbac resources.
type Rbac struct {
Resource
}
// List lists out rbac resources.
func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) {
gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, fmt.Errorf("expecting a context gvr")
}
path, ok := ctx.Value(internal.KeyPath).(string)
if !ok || path == "" {
return r.Resource.List(ctx, ns)
}
switch gvr.R() {
case "clusterrolebindings":
return r.loadClusterRoleBinding(path)
case "rolebindings":
return r.loadRoleBinding(path)
case "clusterroles":
return r.loadClusterRole(path)
case "roles":
return r.loadRole(path)
default:
return nil, fmt.Errorf("expecting clusterrole/role but found %s", gvr.R())
}
}
func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
crbo, err := r.getFactory().Get(client.CrbGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
var crb rbacv1.ClusterRoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(crbo.(*unstructured.Unstructured).Object, &crb)
if err != nil {
return nil, err
}
cro, err := r.getFactory().Get(client.CrGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
var cr rbacv1.ClusterRole
err = runtime.DefaultUnstructuredConverter.FromUnstructured(cro.(*unstructured.Unstructured).Object, &cr)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil
}
func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
rbo, err := r.getFactory().Get(client.RobGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
var rb rbacv1.RoleBinding
if e := runtime.DefaultUnstructuredConverter.FromUnstructured(rbo.(*unstructured.Unstructured).Object, &rb); e != nil {
return nil, e
}
if rb.RoleRef.Kind == "ClusterRole" {
cro, e := r.getFactory().Get(client.CrGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything())
if e != nil {
return nil, e
}
var cr rbacv1.ClusterRole
err = runtime.DefaultUnstructuredConverter.FromUnstructured(cro.(*unstructured.Unstructured).Object, &cr)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil
}
ro, err := r.getFactory().Get(client.RoGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
var role rbacv1.Role
err = runtime.DefaultUnstructuredConverter.FromUnstructured(ro.(*unstructured.Unstructured).Object, &role)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", role.Rules)), nil
}
func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) {
o, err := r.getFactory().Get(client.CrGVR, fqn, true, labels.Everything())
if err != nil {
return nil, err
}
var cr rbacv1.ClusterRole
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil
}
func (r *Rbac) loadRole(path string) ([]runtime.Object, error) {
o, err := r.getFactory().Get(client.RoGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
var ro rbacv1.Role
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro)
if err != nil {
return nil, err
}
return asRuntimeObjects(parseRules(client.ClusterScope, "-", ro.Rules)), nil
}
func asRuntimeObjects(rr render.Policies) []runtime.Object {
oo := make([]runtime.Object, len(rr))
for i, r := range rr {
oo[i] = r
}
return oo
}
func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies {
pp := make(render.Policies, 0, len(rules))
for _, rule := range rules {
for _, grp := range rule.APIGroups {
if grp == "" {
grp = "core"
}
for _, res := range rule.Resources {
for _, na := range rule.ResourceNames {
pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs))
}
pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(grp, res), grp, rule.Verbs))
}
}
for _, nres := range rule.NonResourceURLs {
if nres[0] != '/' {
nres = "/" + nres
}
pp = pp.Upsert(render.NewPolicyRes(ns, binding, nres, client.NA, rule.Verbs))
}
}
return pp
}
================================================
FILE: internal/dao/rbac_policy.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"log/slog"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Policy)(nil)
_ Nuker = (*Policy)(nil)
)
// Policy represent rbac policy.
type Policy struct {
Resource
}
// List returns available policies.
func (p *Policy) List(ctx context.Context, _ string) ([]runtime.Object, error) {
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
if !ok {
return nil, fmt.Errorf("expecting a context subject kind")
}
name, ok := ctx.Value(internal.KeySubjectName).(string)
if !ok {
return nil, fmt.Errorf("expecting a context subject name")
}
crps, err := p.loadClusterRoleBinding(kind, name)
if err != nil {
return nil, err
}
rps, err := p.loadRoleBinding(kind, name)
if err != nil {
return nil, err
}
oo := make([]runtime.Object, 0, len(crps)+len(rps))
for _, p := range crps {
oo = append(oo, p)
}
for _, p := range rps {
oo = append(oo, p)
}
return oo, nil
}
func (p *Policy) loadClusterRoleBinding(kind, name string) (render.Policies, error) {
crbs, err := fetchClusterRoleBindings(p.Factory)
if err != nil {
return nil, err
}
ns, n := client.Namespaced(name)
var nn []string
for i := range crbs {
for _, s := range crbs[i].Subjects {
if isSameSubject(kind, ns, crbs[i].Namespace, n, &s) {
nn = append(nn, crbs[i].RoleRef.Name)
}
}
}
crs, err := p.fetchClusterRoles()
if err != nil {
return nil, err
}
rows := make(render.Policies, 0, len(nn))
for i := range crs {
if !inList(nn, crs[i].Name) {
continue
}
rows = append(rows, parseRules(client.NotNamespaced, "CR:"+crs[i].Name, crs[i].Rules)...)
}
return rows, nil
}
func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) {
rbsMap, err := p.fetchRoleBindingNamespaces(kind, name)
if err != nil {
return nil, err
}
crs, err := p.fetchClusterRoles()
if err != nil {
return nil, err
}
rows := make(render.Policies, 0, len(crs))
for i := range crs {
if rbNs, ok := rbsMap["ClusterRole:"+crs[i].Name]; ok {
slog.Debug("Loading rules for clusterrole",
slogs.Namespace, rbNs,
slogs.ResName, crs[i].Name,
)
rows = append(rows, parseRules(rbNs, "CR:"+crs[i].Name, crs[i].Rules)...)
}
}
ros, err := p.fetchRoles()
if err != nil {
return nil, err
}
for i := range ros {
if _, ok := rbsMap["Role:"+ros[i].Name]; !ok {
continue
}
slog.Debug("Loading rules for role",
slogs.Namespace, ros[i].Namespace,
slogs.ResName, ros[i].Name,
)
rows = append(rows, parseRules(ros[i].Namespace, "RO:"+ros[i].Name, ros[i].Rules)...)
}
return rows, nil
}
func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
oo, err := f.List(client.CrbGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
crbs := make([]rbacv1.ClusterRoleBinding, len(oo))
for i, o := range oo {
var crb rbacv1.ClusterRoleBinding
if e := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb); e != nil {
return nil, e
}
crbs[i] = crb
}
return crbs, nil
}
func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, error) {
oo, err := f.List(client.RobGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
rbs := make([]rbacv1.RoleBinding, 0, len(oo))
for _, o := range oo {
var rb rbacv1.RoleBinding
if e := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb); e != nil {
return nil, e
}
rbs = append(rbs, rb)
}
return rbs, nil
}
func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]string, error) {
rbs, err := fetchRoleBindings(p.Factory)
if err != nil {
return nil, err
}
ns, n := client.Namespaced(name)
ss := make(map[string]string, len(rbs))
for i := range rbs {
for _, s := range rbs[i].Subjects {
if isSameSubject(kind, ns, rbs[i].Namespace, n, &s) {
ss[rbs[i].RoleRef.Kind+":"+rbs[i].RoleRef.Name] = rbs[i].Namespace
}
}
}
return ss, nil
}
// isSameSubject verifies if the incoming type name and namespace match a subject from a
// cluster/roleBinding. A ServiceAccount will always have a namespace and needs to be validated to ensure
// we don't display permissions for a ServiceAccount with the same name in a different namespace
func isSameSubject(kind, ns, bns, name string, subject *rbacv1.Subject) bool {
if subject.Kind != kind || subject.Name != name {
return false
}
if kind == rbacv1.ServiceAccountKind {
// Kind and name were checked above, check the namespace
cns := subject.Namespace
if cns == "" {
cns = bns
}
return client.IsAllNamespaces(ns) || cns == ns
}
return true
}
func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
oo, err := p.getFactory().List(client.CrGVR, client.ClusterScope, false, labels.Everything())
if err != nil {
return nil, err
}
crs := make([]rbacv1.ClusterRole, len(oo))
for i, o := range oo {
var cr rbacv1.ClusterRole
if e := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr); e != nil {
return nil, e
}
crs[i] = cr
}
return crs, nil
}
func (p *Policy) fetchRoles() ([]rbacv1.Role, error) {
oo, err := p.getFactory().List(client.RoGVR, client.BlankNamespace, false, labels.Everything())
if err != nil {
return nil, err
}
rr := make([]rbacv1.Role, len(oo))
for i, o := range oo {
var ro rbacv1.Role
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro); err != nil {
return nil, err
}
rr[i] = ro
}
return rr, nil
}
================================================
FILE: internal/dao/rbac_policy_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"testing"
"github.com/stretchr/testify/assert"
rbacv1 "k8s.io/api/rbac/v1"
)
func TestIsSameSubject(t *testing.T) {
uu := map[string]struct {
kind string
namespace string
name string
subject rbacv1.Subject
want bool
}{
"kind-name-match": {
kind: rbacv1.UserKind,
name: "foo",
subject: rbacv1.Subject{
Kind: rbacv1.UserKind,
Name: "foo",
},
want: true,
},
"name-does-not-match": {
kind: rbacv1.UserKind,
name: "foo",
subject: rbacv1.Subject{
Kind: rbacv1.UserKind,
Name: "bar",
},
want: false,
},
"kind-does-not-match": {
kind: rbacv1.GroupKind,
name: "foo",
subject: rbacv1.Subject{
Kind: rbacv1.UserKind,
Name: "foo",
},
want: false,
},
"serviceAccount-all-match": {
kind: rbacv1.ServiceAccountKind,
name: "foo",
namespace: "bar",
subject: rbacv1.Subject{
Kind: rbacv1.ServiceAccountKind,
Name: "foo",
Namespace: "bar",
},
want: true,
},
"serviceAccount-namespace-no-match": {
kind: rbacv1.ServiceAccountKind,
name: "foo",
namespace: "bar",
subject: rbacv1.Subject{
Kind: rbacv1.ServiceAccountKind,
Name: "foo",
Namespace: "bazz",
},
want: false,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
same := isSameSubject(u.kind, u.namespace, u.namespace, u.name, &u.subject)
assert.Equal(t, u.want, same)
})
}
}
================================================
FILE: internal/dao/rbac_subject.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Subject)(nil)
_ Nuker = (*Subject)(nil)
)
// Subject represents a subject model.
type Subject struct {
Resource
}
// List returns a collection of subjects.
func (s *Subject) List(ctx context.Context, _ string) ([]runtime.Object, error) {
kind, ok := ctx.Value(internal.KeySubjectKind).(string)
if !ok {
return nil, errors.New("expecting a SubjectKind")
}
crbs, err := s.listClusterRoleBindings(kind)
if err != nil {
return nil, err
}
rbs, err := s.listRoleBindings(kind)
if err != nil {
return nil, err
}
for _, rb := range rbs {
crbs = crbs.Upsert(rb)
}
oo := make([]runtime.Object, len(crbs))
for i, o := range crbs {
oo[i] = o
}
return oo, nil
}
func (s *Subject) listClusterRoleBindings(kind string) (render.Subjects, error) {
crbs, err := fetchClusterRoleBindings(s.Factory)
if err != nil {
return nil, err
}
oo := make(render.Subjects, 0, len(crbs))
for i := range crbs {
for _, su := range crbs[i].Subjects {
if su.Kind != kind {
continue
}
oo = oo.Upsert(render.SubjectRes{
Name: su.Name,
Kind: "ClusterRoleBinding",
FirstLocation: crbs[i].Name,
})
}
}
return oo, nil
}
func (s *Subject) listRoleBindings(kind string) (render.Subjects, error) {
rbs, err := fetchRoleBindings(s.Factory)
if err != nil {
return nil, err
}
oo := make(render.Subjects, 0, len(rbs))
for i := range rbs {
for _, su := range rbs[i].Subjects {
if su.Kind != kind {
continue
}
oo = oo.Upsert(render.SubjectRes{
Name: su.Name,
Kind: "RoleBinding",
FirstLocation: rbs[i].Name,
})
}
}
return oo, nil
}
================================================
FILE: internal/dao/recorder.go
================================================
package dao
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/cache"
)
var MxRecorder *Recorder
const (
seriesCacheSize = 600
seriesCacheExpiry = 3 * time.Hour
seriesRecordRate = 1 * time.Minute
nodeMetrics = "node"
podMetrics = "pod"
)
type MetricsChan chan TimeSeries
type TimeSeries []Point
type Point struct {
Time time.Time
Tags map[string]string
Value client.NodeMetrics
}
type Recorder struct {
conn client.Connection
series *cache.LRUExpireCache
mxChan MetricsChan
mx sync.RWMutex
}
func DialRecorder(c client.Connection) *Recorder {
if MxRecorder != nil {
return MxRecorder
}
MxRecorder = &Recorder{
conn: c,
series: cache.NewLRUExpireCache(seriesCacheSize),
}
return MxRecorder
}
func ResetRecorder(c client.Connection) {
MxRecorder = nil
DialRecorder(c)
}
func (r *Recorder) Clear() {
r.mx.Lock()
defer r.mx.Unlock()
kk := r.series.Keys()
for _, k := range kk {
r.series.Remove(k)
}
}
func (r *Recorder) dispatchSeries(kind, ns string) {
if r.mxChan == nil {
return
}
kk := r.series.Keys()
hour := time.Now().Add(-1 * time.Hour)
ts := make(TimeSeries, 0, len(kk))
for _, k := range kk {
if v, ok := r.series.Get(k); ok {
if pt, cool := v.(Point); cool {
if pt.Tags["type"] != kind || pt.Time.Sub(hour) < 0 {
continue
}
switch kind {
case nodeMetrics:
ts = append(ts, pt)
case podMetrics:
if client.IsAllNamespaces(ns) || pt.Tags["namespace"] == ns {
ts = append(ts, pt)
}
}
}
}
}
if len(ts) > 0 {
r.mxChan <- ts
}
}
func (r *Recorder) Watch(ctx context.Context, ns string) MetricsChan {
r.mx.Lock()
if r.mxChan != nil {
close(r.mxChan)
r.mxChan = nil
}
r.mxChan = make(MetricsChan, 2)
r.mx.Unlock()
go func() {
kind := podMetrics
if client.IsAllNamespaces(ns) {
kind = nodeMetrics
}
switch kind {
case podMetrics:
if err := r.recordPodMetrics(ctx, ns); err != nil {
slog.Error("Record pod metrics failed", slogs.Error, err)
}
case nodeMetrics:
if err := r.recordNodeMetrics(ctx); err != nil {
slog.Error("Record node metrics failed", slogs.Error, err)
}
}
r.dispatchSeries(kind, ns)
<-ctx.Done()
r.mx.Lock()
if r.mxChan != nil {
close(r.mxChan)
r.mxChan = nil
}
r.mx.Unlock()
}()
return r.mxChan
}
func (r *Recorder) Record(ctx context.Context) error {
if err := r.recordNodeMetrics(ctx); err != nil {
return err
}
return r.recordPodMetrics(ctx, client.NamespaceAll)
}
func (r *Recorder) recordNodeMetrics(ctx context.Context) error {
f, ok := ctx.Value(internal.KeyFactory).(Factory)
if !ok {
return errors.New("expecting factory in context")
}
nn, err := FetchNodes(ctx, f, "")
if err != nil {
return err
}
go func() {
r.recordClusterMetrics(ctx, nn)
for {
select {
case <-ctx.Done():
return
case <-time.After(seriesRecordRate):
r.recordClusterMetrics(ctx, nn)
}
}
}()
return nil
}
func (r *Recorder) recordClusterMetrics(ctx context.Context, nn *v1.NodeList) {
dial := client.DialMetrics(r.conn)
nmx, err := dial.FetchNodesMetrics(ctx)
if err != nil {
slog.Error("Fetch node metrics failed", slogs.Error, err)
return
}
mx := make(client.NodesMetrics, len(nn.Items))
dial.NodesMetrics(nn, nmx, mx)
var cmx client.NodeMetrics
for _, m := range mx {
cmx.CurrentCPU += m.CurrentCPU
cmx.CurrentMEM += m.CurrentMEM
cmx.AllocatableCPU += m.AllocatableCPU
cmx.AllocatableMEM += m.AllocatableMEM
cmx.TotalCPU += m.TotalCPU
cmx.TotalMEM += m.TotalMEM
}
pt := Point{
Time: time.Now(),
Value: cmx,
Tags: map[string]string{
"type": nodeMetrics,
},
}
if len(nn.Items) > 0 {
r.series.Add(pt.Time, pt, seriesCacheExpiry)
}
r.mx.Lock()
defer r.mx.Unlock()
if r.mxChan != nil {
r.mxChan <- TimeSeries{pt}
}
}
func (r *Recorder) recordPodMetrics(ctx context.Context, ns string) error {
go func() {
if err := r.recordPodsMetrics(ctx, ns); err != nil {
slog.Error("Record pod metrics failed", slogs.Error, err)
}
for {
select {
case <-ctx.Done():
return
case <-time.After(seriesRecordRate):
// case <-time.After(5 * time.Second):
if err := r.recordPodsMetrics(ctx, ns); err != nil {
slog.Error("Record pod metrics failed", slogs.Error, err)
}
}
}
}()
return nil
}
func (r *Recorder) recordPodsMetrics(ctx context.Context, ns string) error {
f, ok := ctx.Value(internal.KeyFactory).(Factory)
if !ok {
return errors.New("expecting factory in context")
}
pp, err := FetchPods(ctx, f, ns)
if err != nil {
return err
}
pt := Point{
Time: time.Now(),
Value: client.NodeMetrics{},
Tags: map[string]string{
"namespace": ns,
"type": podMetrics,
},
}
dial := client.DialMetrics(r.conn)
for i := range pp.Items {
p := pp.Items[i]
fqn := client.FQN(p.Namespace, p.Name)
pmx, err := dial.FetchPodMetrics(ctx, fqn)
if err != nil {
continue
}
for _, c := range pmx.Containers {
pt.Value.CurrentCPU += c.Usage.Cpu().MilliValue()
pt.Value.CurrentMEM += client.ToMB(c.Usage.Memory().Value())
}
}
if len(pp.Items) > 0 {
pt.Value.AllocatableCPU = pt.Value.CurrentCPU
pt.Value.AllocatableMEM = pt.Value.CurrentMEM
r.series.Add(pt.Time, pt, seriesCacheExpiry)
r.mx.Lock()
defer r.mx.Unlock()
if r.mxChan != nil {
r.mxChan <- TimeSeries{pt}
}
}
return nil
}
// FetchPods retrieves all pods in a given namespace.
func FetchPods(_ context.Context, f Factory, ns string) (*v1.PodList, error) {
auth, err := f.Client().CanI(ns, client.PodGVR, "pods", []string{client.ListVerb})
if err != nil {
return nil, err
}
if !auth {
return nil, fmt.Errorf("user is not authorized to list pods")
}
oo, err := f.List(client.PodGVR, ns, false, labels.Everything())
if err != nil {
return nil, err
}
pp := make([]v1.Pod, 0, len(oo))
for _, o := range oo {
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
if err != nil {
return nil, err
}
pp = append(pp, pod)
}
return &v1.PodList{Items: pp}, nil
}
================================================
FILE: internal/dao/reference.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
)
var _ Accessor = (*Reference)(nil)
// Reference represents cluster resource references.
type Reference struct {
NonResource
}
// List collects all references.
func (r *Reference) List(ctx context.Context, _ string) ([]runtime.Object, error) {
gvr, ok := ctx.Value(internal.KeyGVR).(*client.GVR)
if !ok {
return nil, errors.New("no context for gvr found")
}
switch gvr {
case client.SaGVR:
return r.ScanSA(ctx)
default:
return r.Scan(ctx)
}
}
// Get fetch a given reference.
func (*Reference) Get(context.Context, string) (runtime.Object, error) {
panic("NYI")
}
// Scan scan cluster resources for references.
func (r *Reference) Scan(ctx context.Context) ([]runtime.Object, error) {
refs, err := ScanForRefs(ctx, r.Factory)
if err != nil {
return nil, err
}
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("expecting context Path")
}
ns, _ := client.Namespaced(fqn)
oo := make([]runtime.Object, 0, len(refs))
for _, ref := range refs {
_, n := client.Namespaced(ref.FQN)
oo = append(oo, render.ReferenceRes{
Namespace: ns,
Name: n,
GVR: ref.GVR,
})
}
return oo, nil
}
// ScanSA scans for serviceaccount refs.
func (r *Reference) ScanSA(ctx context.Context) ([]runtime.Object, error) {
refs, err := ScanForSARefs(ctx, r.Factory)
if err != nil {
return nil, err
}
fqn, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("expecting context Path")
}
ns, _ := client.Namespaced(fqn)
oo := make([]runtime.Object, 0, len(refs))
for _, ref := range refs {
_, n := client.Namespaced(ref.FQN)
oo = append(oo, render.ReferenceRes{
Namespace: ns,
Name: n,
GVR: ref.GVR,
})
}
return oo, nil
}
================================================
FILE: internal/dao/registry.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"fmt"
"log/slog"
"maps"
"slices"
"strings"
"sync"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
)
const (
crdCat = "crd"
k9sCat = "k9s"
helmCat = "helm"
scaleCat = "scale"
)
var stdGroups = sets.New[string](
"apps/v1",
"autoscaling/v1",
"autoscaling/v2",
"autoscaling/v2beta1",
"autoscaling/v2beta2",
"batch/v1",
"batch/v1beta1",
"extensions/v1beta1",
"policy/v1beta1",
"policy/v1",
"v1",
)
var scalableRes = sets.New(client.DpGVR, client.StsGVR, client.RsGVR, client.RcGVR)
// ResourceMetas represents a collection of resource metadata.
type ResourceMetas map[*client.GVR]*metav1.APIResource
func (m ResourceMetas) clear() {
for k := range m {
delete(m, k)
}
}
// MetaAccess tracks resources metadata.
var MetaAccess = NewMeta()
// Meta represents available resource metas.
type Meta struct {
resMetas ResourceMetas
mx sync.RWMutex
}
// NewMeta returns a resource meta.
func NewMeta() *Meta {
return &Meta{resMetas: make(ResourceMetas)}
}
func (m *Meta) Lookup(cmd string) *client.GVR {
m.mx.RLock()
defer m.mx.RUnlock()
for gvr, meta := range m.resMetas {
if slices.Contains(meta.ShortNames, cmd) {
return gvr
}
if meta.Name == cmd || meta.SingularName == cmd || meta.Kind == cmd {
return gvr
}
}
return client.NoGVR
}
// RegisterMeta registers a new resource meta object.
func (m *Meta) RegisterMeta(gvr string, res *metav1.APIResource) {
m.mx.Lock()
defer m.mx.Unlock()
m.resMetas[client.NewGVR(gvr)] = res
}
// AllGVRs returns all sorted cluster resources.
func (m *Meta) AllGVRs() client.GVRs {
m.mx.RLock()
defer m.mx.RUnlock()
kk := slices.Collect(maps.Keys(m.resMetas))
return client.GVRs(kk)
}
// GVK2GVR convert gvk to gvr
func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (*client.GVR, bool, bool) {
m.mx.RLock()
defer m.mx.RUnlock()
for gvr, meta := range m.resMetas {
if gv.Group == meta.Group && gv.Version == meta.Version && kind == meta.Kind {
return gvr, meta.Namespaced, true
}
}
return client.NoGVR, false, false
}
// IsNamespaced checks if a given resource is namespaced.
func (m *Meta) IsNamespaced(gvr *client.GVR) (bool, error) {
res, err := m.MetaFor(gvr)
if err != nil {
return false, err
}
return res.Namespaced, nil
}
// MetaFor returns a resource metadata for a given gvr.
func (m *Meta) MetaFor(gvr *client.GVR) (*metav1.APIResource, error) {
m.mx.RLock()
defer m.mx.RUnlock()
if meta, ok := m.resMetas[gvr]; ok {
return meta, nil
}
return new(metav1.APIResource), fmt.Errorf("no resource meta defined for\n %q", gvr)
}
// IsCRD checks if resource represents a CRD
func IsCRD(r *metav1.APIResource) bool {
return slices.Contains(r.Categories, crdCat)
}
// IsK8sMeta checks for non resource meta.
func IsK8sMeta(m *metav1.APIResource) bool {
return !slices.ContainsFunc(m.Categories, func(category string) bool {
return category == k9sCat || category == helmCat
})
}
// IsK9sMeta checks for non resource meta.
func IsK9sMeta(m *metav1.APIResource) bool {
return slices.Contains(m.Categories, k9sCat)
}
// IsScalable check if the resource can be scaled
func IsScalable(m *metav1.APIResource) bool {
return slices.Contains(m.Categories, scaleCat)
}
// LoadResources hydrates server preferred+CRDs resource metadata.
func (m *Meta) LoadResources(f Factory) error {
m.mx.Lock()
defer m.mx.Unlock()
m.resMetas.clear()
if err := loadPreferred(f, m.resMetas); err != nil {
return err
}
loadNonResource(m.resMetas)
// We've actually loaded all the CRDs in loadPreferred, and we're now adding
// some additional CRD properties on top of that.
loadCRDs(f, m.resMetas)
return nil
}
// BOZO!! Need countermeasures for direct commands!
func loadNonResource(m ResourceMetas) {
loadK9s(m)
loadRBAC(m)
loadHelm(m)
}
func loadK9s(m ResourceMetas) {
m[client.WkGVR] = &metav1.APIResource{
Name: "workloads",
Kind: "Workload",
SingularName: "workload",
Namespaced: true,
ShortNames: []string{"wk"},
Categories: []string{k9sCat},
}
m[client.PuGVR] = &metav1.APIResource{
Name: "pulses",
Kind: "Pulse",
SingularName: "pulse",
ShortNames: []string{"hz", "pu"},
Categories: []string{k9sCat},
}
m[client.DirGVR] = &metav1.APIResource{
Name: "dirs",
Kind: "Dir",
SingularName: "dir",
Categories: []string{k9sCat},
}
m[client.XGVR] = &metav1.APIResource{
Name: "xrays",
Kind: "XRays",
SingularName: "xray",
Categories: []string{k9sCat},
}
m[client.RefGVR] = &metav1.APIResource{
Name: "references",
Kind: "References",
SingularName: "reference",
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.AliGVR] = &metav1.APIResource{
Name: "aliases",
Kind: "Aliases",
SingularName: "alias",
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.CtGVR] = &metav1.APIResource{
Name: client.CtGVR.String(),
Kind: "Contexts",
SingularName: "context",
ShortNames: []string{"ctx"},
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.SdGVR] = &metav1.APIResource{
Name: "screendumps",
Kind: "ScreenDumps",
SingularName: "screendump",
ShortNames: []string{"sd"},
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
m[client.BeGVR] = &metav1.APIResource{
Name: "benchmarks",
Kind: "Benchmarks",
SingularName: "benchmark",
ShortNames: []string{"be"},
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
m[client.PfGVR] = &metav1.APIResource{
Name: "portforwards",
Namespaced: true,
Kind: "PortForwards",
SingularName: "portforward",
ShortNames: []string{"pf"},
Verbs: []string{"delete"},
Categories: []string{k9sCat},
}
m[client.CoGVR] = &metav1.APIResource{
Name: "containers",
Kind: "Containers",
SingularName: "container",
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.ScnGVR] = &metav1.APIResource{
Name: "scans",
Kind: "Scans",
SingularName: "scan",
Verbs: []string{},
Categories: []string{k9sCat},
}
}
func loadHelm(m ResourceMetas) {
m[client.HmGVR] = &metav1.APIResource{
Name: "helm",
Kind: "Helm",
Namespaced: true,
Verbs: []string{"delete"},
Categories: []string{helmCat},
}
m[client.HmhGVR] = &metav1.APIResource{
Name: "history",
Kind: "History",
Namespaced: true,
Verbs: []string{"delete"},
Categories: []string{helmCat},
}
}
func loadRBAC(m ResourceMetas) {
m[client.RbacGVR] = &metav1.APIResource{
Name: "rbacs",
Kind: "Rules",
Categories: []string{k9sCat},
}
m[client.PolGVR] = &metav1.APIResource{
Name: "policies",
Kind: "Rules",
Namespaced: true,
Categories: []string{k9sCat},
}
m[client.UsrGVR] = &metav1.APIResource{
Name: "users",
Kind: "User",
Categories: []string{k9sCat},
}
m[client.GrpGVR] = &metav1.APIResource{
Name: "groups",
Kind: "Group",
Categories: []string{k9sCat},
}
}
func loadPreferred(f Factory, m ResourceMetas) error {
if f == nil || f.Client() == nil || !f.Client().ConnectionOK() {
slog.Error("Load cluster resources - No API server connection")
return nil
}
dial, err := f.Client().CachedDiscovery()
if err != nil {
return err
}
rr, err := dial.ServerPreferredResources()
if err != nil {
slog.Debug("Failed to load preferred resources", slogs.Error, err)
}
for _, r := range rr {
for i := range r.APIResources {
res := r.APIResources[i]
gvr := client.FromGVAndR(r.GroupVersion, res.Name)
if isDeprecated(gvr) {
continue
}
res.Group, res.Version = gvr.G(), gvr.V()
if res.SingularName == "" {
res.SingularName = strings.ToLower(res.Kind)
}
if !isStandardGroup(r.GroupVersion) {
res.Categories = append(res.Categories, crdCat)
}
if isScalable(gvr) {
res.Categories = append(res.Categories, scaleCat)
}
m[gvr] = &res
}
}
return nil
}
func isStandardGroup(gv string) bool {
return stdGroups.Has(gv) || strings.Contains(gv, ".k8s.io")
}
func isScalable(gvr *client.GVR) bool {
return scalableRes.Has(gvr)
}
var deprecatedGVRs = sets.New(
client.NewGVR("v1/events"),
client.NewGVR("extensions/v1beta1/ingresses"),
)
func isDeprecated(gvr *client.GVR) bool {
return deprecatedGVRs.Has(gvr) || gvr.V() == ""
}
// loadCRDs Wait for the cache to synced and then add some additional properties to CRD.
func loadCRDs(f Factory, m ResourceMetas) {
if f == nil || f.Client() == nil || !f.Client().ConnectionOK() {
return
}
oo, err := f.List(client.CrdGVR, client.ClusterScope, true, labels.Everything())
if err != nil {
slog.Warn("CRDs load Fail", slogs.Error, err)
return
}
for _, o := range oo {
var crd apiext.CustomResourceDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crd)
if err != nil {
slog.Error("CRD conversion failed", slogs.Error, err)
continue
}
for gvr, version := range client.NewGVRFromCRD(&crd) {
if meta, ok := m[gvr]; ok && version.Subresources != nil && version.Subresources.Scale != nil {
if !slices.Contains(meta.Categories, scaleCat) {
meta.Categories = append(meta.Categories, scaleCat)
m[gvr] = meta
}
}
}
}
}
================================================
FILE: internal/dao/registry_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"errors"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestMetaFor(t *testing.T) {
uu := map[string]struct {
gvr *client.GVR
err error
e metav1.APIResource
}{
"xray-gvr": {
gvr: client.XGVR,
e: metav1.APIResource{
Name: "xrays",
Kind: "XRays",
SingularName: "xray",
Categories: []string{k9sCat},
},
},
"xray": {
gvr: client.NewGVR("xrays"),
e: metav1.APIResource{
Name: "xrays",
Kind: "XRays",
SingularName: "xray",
Categories: []string{k9sCat},
},
},
"policy": {
gvr: client.NewGVR("policy"),
e: metav1.APIResource{
Name: "policies",
Kind: "Rules",
Namespaced: true,
Categories: []string{k9sCat},
},
},
"helm": {
gvr: client.NewGVR("helm"),
e: metav1.APIResource{
Name: "helm",
Kind: "Helm",
Namespaced: true,
Verbs: []string{"delete"},
Categories: []string{helmCat},
},
},
"toast": {
gvr: client.NewGVR("blah"),
err: errors.New("no resource meta defined for\n \"blah\""),
},
}
m := NewMeta()
require.NoError(t, m.LoadResources(nil))
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
meta, err := m.MetaFor(u.gvr)
assert.Equal(t, u.err, err)
if err == nil {
assert.Equal(t, &u.e, meta)
}
})
}
}
================================================
FILE: internal/dao/resource.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"fmt"
"github.com/derailed/k9s/internal"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Resource)(nil)
_ Describer = (*Resource)(nil)
_ Nuker = (*Resource)(nil)
)
// Resource represents an informer based resource.
type Resource struct {
Generic
}
// List returns a collection of resources.
func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error) {
lsel := labels.Everything()
if sel, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
lsel = sel
}
return r.getFactory().List(r.gvr, ns, false, lsel)
}
// Get returns a resource instance if found, else an error.
func (r *Resource) Get(_ context.Context, path string) (runtime.Object, error) {
return r.getFactory().Get(r.gvr, path, true, labels.Everything())
}
// ToYAML returns a resource yaml.
func (r *Resource) ToYAML(path string, showManaged bool) (string, error) {
o, err := r.Get(context.Background(), path)
if err != nil {
return "", err
}
raw, err := ToYAML(o, showManaged)
if err != nil {
return "", fmt.Errorf("unable to marshal resource %w", err)
}
return raw, nil
}
================================================
FILE: internal/dao/rest_mapper.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"fmt"
"strings"
"github.com/derailed/k9s/internal/client"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/restmapper"
)
// RestMapping holds k8s resource mapping.
var RestMapping = &RestMapper{}
// RestMapper map resource to REST mapping ie kind, group, version.
type RestMapper struct {
client.Connection
}
// ToRESTMapper map resources to kind, and map kind and version to interfaces for manipulating K8s objects.
func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) {
dial, err := r.CachedDiscovery()
if err != nil {
return nil, err
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(dial)
expander := restmapper.NewShortcutExpander(mapper, dial, nil)
return expander, nil
}
// ResourceFor produces a rest mapping from a given resource.
// Support full res name ie deployment.v1.apps.
func (r *RestMapper) ResourceFor(resourceArg, kind string) (*meta.RESTMapping, error) {
res, err := r.resourceFor(resourceArg)
if err != nil {
return nil, err
}
return r.toRESTMapping(res, kind), nil
}
func (r *RestMapper) resourceFor(resourceArg string) (schema.GroupVersionResource, error) {
if resourceArg == "*" {
return schema.GroupVersionResource{Resource: resourceArg}, nil
}
var (
gvr schema.GroupVersionResource
err error
)
mapper, err := r.ToRESTMapper()
if err != nil {
return gvr, err
}
fullGVR, gr := schema.ParseResourceArg(strings.ToLower(resourceArg))
if fullGVR != nil {
return mapper.ResourceFor(*fullGVR)
}
gvr, err = mapper.ResourceFor(gr.WithVersion(""))
if err != nil {
if gr.Group == "" {
return gvr, fmt.Errorf("the server doesn't have a resource type '%s'", gr.Resource)
}
return gvr, fmt.Errorf("the server doesn't have a resource type '%s' in group '%s'", gr.Resource, gr.Group)
}
return gvr, nil
}
func (*RestMapper) toRESTMapping(gvr schema.GroupVersionResource, kind string) *meta.RESTMapping {
return &meta.RESTMapping{
Resource: gvr,
GroupVersionKind: schema.GroupVersionKind{
Group: gvr.Group,
Version: gvr.Version,
Kind: kind,
},
Scope: RestMapping,
}
}
// Name protocol returns rest scope name.
func (*RestMapper) Name() meta.RESTScopeName {
return meta.RESTScopeNameNamespace
}
================================================
FILE: internal/dao/rs.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
var (
_ ImageLister = (*ReplicaSet)(nil)
)
// ReplicaSet represents a replicaset K8s resource.
type ReplicaSet struct {
Resource
}
// ListImages lists container images.
func (r *ReplicaSet) ListImages(_ context.Context, fqn string) ([]string, error) {
rs, err := r.Load(r.Factory, fqn)
if err != nil {
return nil, err
}
return render.ExtractImages(&rs.Spec.Template.Spec), nil
}
// Load returns a given instance.
func (*ReplicaSet) Load(f Factory, path string) (*appsv1.ReplicaSet, error) {
o, err := f.Get(client.RsGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
var rs appsv1.ReplicaSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rs)
if err != nil {
return nil, err
}
return &rs, nil
}
func getRSRevision(rs *appsv1.ReplicaSet) (int64, error) {
revision := rs.Annotations["deployment.kubernetes.io/revision"]
if rs.Status.Replicas != 0 {
return 0, errors.New("can not rollback current replica")
}
vers, err := strconv.Atoi(revision)
if err != nil {
return 0, errors.New("revision conversion failed")
}
return int64(vers), nil
}
func controllerInfo(rs *appsv1.ReplicaSet) (name, kind, group string, err error) {
for _, ref := range rs.OwnerReferences {
if ref.Controller == nil {
continue
}
group, tokens := ref.APIVersion, strings.Split(ref.APIVersion, "/")
if len(tokens) == 2 {
group = tokens[0]
}
return ref.Name, ref.Kind, group, nil
}
return "", "", "", fmt.Errorf("unable to find controller for replicaset: %s", rs.Name)
}
// Rollback reverses the last deployment.
func (r *ReplicaSet) Rollback(fqn string) error {
rs, err := r.Load(r.Factory, fqn)
if err != nil {
return err
}
version, err := getRSRevision(rs)
if err != nil {
return err
}
name, kind, apiGroup, err := controllerInfo(rs)
if err != nil {
return err
}
dial, err := r.Client().Dial()
if err != nil {
return err
}
rb, err := polymorphichelpers.RollbackerFor(schema.GroupKind{
Group: apiGroup,
Kind: kind,
},
dial,
)
if err != nil {
return err
}
var ddp Deployment
ddp.Init(r.Factory, client.DpGVR)
dp, err := ddp.GetInstance(client.FQN(rs.Namespace, name))
if err != nil {
return err
}
_, err = rb.Rollback(dp, map[string]string{}, version, cmdutil.DryRunNone)
if err != nil {
return err
}
return nil
}
================================================
FILE: internal/dao/scalable.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"log/slog"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/scale"
)
var (
_ Scalable = (*Scaler)(nil)
_ ReplicasGetter = (*Scaler)(nil)
)
// Scaler represents a generic resource with scaling.
type Scaler struct {
Generic
}
// Replicas returns the number of replicas for the resource located at the given path.
func (s *Scaler) Replicas(ctx context.Context, path string) (int32, error) {
scaleClient, err := s.scaleClient()
if err != nil {
return 0, err
}
ns, name := client.Namespaced(path)
currScale, err := scaleClient.Scales(ns).Get(ctx, *s.gvr.GR(), name, metav1.GetOptions{})
if err != nil {
return 0, err
}
return currScale.Spec.Replicas, nil
}
// Scale modifies the number of replicas for a given resource specified by the path.
func (s *Scaler) Scale(ctx context.Context, path string, replicas int32) error {
ns, name := client.Namespaced(path)
scaleClient, err := s.scaleClient()
if err != nil {
return err
}
currentScale, err := scaleClient.Scales(ns).Get(ctx, *s.gvr.GR(), name, metav1.GetOptions{})
if err != nil {
return err
}
currentScale.Spec.Replicas = replicas
updatedScale, err := scaleClient.Scales(ns).Update(ctx, *s.gvr.GR(), currentScale, metav1.UpdateOptions{})
if err != nil {
return err
}
slog.Debug("Scaled resource",
slogs.FQN, path,
slogs.Replicas, updatedScale.Spec.Replicas,
)
return nil
}
func (s *Scaler) scaleClient() (scale.ScalesGetter, error) {
cfg, err := s.Client().RestConfig()
if err != nil {
return nil, err
}
discoveryClient, err := s.Client().CachedDiscovery()
if err != nil {
return nil, err
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
scaleKindResolver := scale.NewDiscoveryScaleKindResolver(discoveryClient)
return scale.NewForConfig(cfg, mapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver)
}
================================================
FILE: internal/dao/screen_dump.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"context"
"errors"
"os"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*ScreenDump)(nil)
_ Nuker = (*ScreenDump)(nil)
)
// ScreenDump represents a scraped resources.
type ScreenDump struct {
NonResource
}
// Delete a ScreenDump.
func (*ScreenDump) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
return os.Remove(path)
}
// List returns a collection of screen dumps.
func (*ScreenDump) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyDir).(string)
if !ok {
return nil, errors.New("no screendump dir found in context")
}
ff, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
oo := make([]runtime.Object, len(ff))
for i, f := range ff {
if fi, err := f.Info(); err == nil {
oo[i] = render.FileRes{File: fi, Dir: dir}
}
}
return oo, nil
}
================================================
FILE: internal/dao/secret.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
import (
"bytes"
"context"
"fmt"
"log/slog"
"strings"
"github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
)
// Secret represents a secret K8s resource.
type Secret struct {
Resource
decodeData bool
}
// Describe describes a secret that can be encoded or decoded.
func (s *Secret) Describe(path string) (string, error) {
encodedDescription, err := s.Generic.Describe(path)
if err != nil {
return "", err
}
if s.decodeData {
return s.Decode(encodedDescription, path)
}
return encodedDescription, nil
}
// ToYAML returns a resource yaml.
func (s *Secret) ToYAML(path string, showManaged bool) (string, error) {
if s.decodeData {
return s.decodeYAML(path, showManaged)
}
return s.Generic.ToYAML(path, showManaged)
}
func (s *Secret) decodeYAML(path string, showManaged bool) (string, error) {
o, err := s.Get(context.Background(), path)
if err != nil {
return "", err
}
o = o.DeepCopyObject()
u, ok := o.(*unstructured.Unstructured)
if !ok {
return "", fmt.Errorf("expecting unstructured but got %T", o)
}
if u.Object == nil {
return "", fmt.Errorf("expecting unstructured object but got nil")
}
if !showManaged {
if meta, ok := u.Object["metadata"].(map[string]any); ok {
delete(meta, "managedFields")
}
}
if decoded, err := ExtractSecrets(o); err == nil {
u.Object["data"] = decoded
}
var (
buff bytes.Buffer
p printers.YAMLPrinter
)
if err := p.PrintObj(o, &buff); err != nil {
slog.Error("PrintObj failed", slogs.Error, err)
return "", err
}
return buff.String(), nil
}
// SetDecodeData toggles decode mode.
func (s *Secret) SetDecodeData(b bool) {
s.decodeData = b
}
// Decode removes the encoded part from the secret's description and appends the
// secret's decoded data.
func (s *Secret) Decode(encodedDescription, path string) (string, error) {
dataEndIndex := strings.Index(encodedDescription, "====")
if dataEndIndex == -1 {
return "", fmt.Errorf("unable to find data section in secret description")
}
dataEndIndex += 4
if dataEndIndex >= len(encodedDescription) {
return "", fmt.Errorf("data section in secret description is invalid")
}
// Remove the encoded part from k8s's describe API
// More details about the reasoning of index: https://github.com/kubernetes/kubectl/blob/v0.29.0/pkg/describe/describe.go#L2542
body := encodedDescription[0:dataEndIndex]
o, err := s.Get(context.Background(), path)
if err != nil {
return "", err
}
data, err := ExtractSecrets(o)
if err != nil {
return "", err
}
decodedSecrets := make([]string, 0, len(data))
for k, v := range data {
line := fmt.Sprintf("%s: %s", k, v)
decodedSecrets = append(decodedSecrets, strings.TrimSpace(line))
}
return body + "\n" + strings.Join(decodedSecrets, "\n"), nil
}
// ExtractSecrets takes an unstructured object and attempts to convert it into a
// Kubernetes Secret.
// It returns a map where the keys are the secret data keys and the values are
// the corresponding secret data values.
// If the conversion fails, it returns an error.
func ExtractSecrets(o runtime.Object) (map[string]string, error) {
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got %T", o)
}
var secret v1.Secret
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &secret)
if err != nil {
return nil, err
}
secretData := make(map[string]string, len(secret.Data))
for k, val := range secret.Data {
secretData[k] = string(val)
}
return secretData, nil
}
================================================
FILE: internal/dao/secret_test.go
================================================
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao_test
import (
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEncodedSecretDescribe(t *testing.T) {
var s dao.Secret
s.Init(makeFactory(), client.SecGVR)
encodedString := `
Name: bootstrap-token-abcdef
Namespace: kube-system
Labels:
([nicolaka/netshoot](2)) | containers | Shift-d | |
| dive.yaml | Dive image layers | containers | d | [Dive](https://github.com/wagoodman/dive) |
| dup.yaml | Duplicate, edit and Debug resources | all | Shift-d/e/v | [dup](https://github.com/vash/dup) |
| external-secrets.yaml | Refresh external/push-secrets | externalsecrets/pushsecrets | Shift-R | [External Secrets](https://external-secrets.io) |
| get-all-namespace-resources.yaml | List all namespace resources (using standard kubectl) | all | m | [kubectl](https://kubernetes.io/docs/tasks/tools/) |
| get-all.yaml | get all resources in a namespace | all | g | [Krew](https://krew.sigs.k8s.io/), [ketall](https://github.com/corneliusweig/ketall/) |
| helm-diff.yaml | Diff with previous revision / current revision | helm/history | Shift-D/Q | [helm-diff](https://github.com/databus23/helm-diff) |
| job-suspend.yaml | Suspends a running cronjob | cronjobs | Ctrl-s | |
| k3d-root-shell.yaml | Root shell to k3d container | containers | Shift-s | [jq](https://stedolan.github.io/jq/) |
| keda-toggle.yaml | Enable/disable [keda](3) ScaledObject autoscaler | scaledobjects | Ctrl-N | |
| kube-metrics.yaml | Visualize live pod/node metric graphs (Memory/CPU) | pods/nodes | m | [kube-metics](https://github.com/bakito/kube-metrics) |
| log-stern.yaml | View resource logs using stern | pods | Ctrl-l | |
| log-jq.yaml | View resource logs using jq | pods | Ctrl-j | kubectl-plugins/kubectl-jq |
| log-bunyan.yaml | View pods, service, deployment logs using bunyan | pods, service, deployment | Ctrl-l | [Bunyan](https://www.npmjs.com/package/bunyan) |
| log-full.yaml | get full logs from pod/container | pods/containers | Ctrl-l | |
| pvc-debug-container.yaml | Add ephemeral debug container with pvc mounted | pods | s | kubectl |
| resource-recommendations.yaml | View recommendations for CPU/Memory requests based on historical data | deployments/daemonsets/statefulsets | Shift-k | [Robusta KRR](https://github.com/robusta-dev/krr) |
| szero.yaml | Temporarily scale down/up all deployments, statefulsets, and daemonsets | namespaces | Shift-d/u | [szero](https://github.com/jadolg/szero) |
| trace-dns.yaml | Trace DNS resolution using Inspektor Gadget (4) | containers/pods/nodes | Shift-d | |
| vector-dev-top.yaml | Run `vector top` in vector.dev container | pods/container | h | [vector top](https://vector.dev/highlights/2020-12-23-vector-top/) |
| start-alpine.yaml | Starts a deployment for the `alpine:latest` docker image in the current namespace/context | deployments/pods | Ctrl-T | |
[1]: https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/#ephemeral-container
[2]: https://github.com/nicolaka/netshoot
[3]: https://keda.sh/
[4]: https://inspektor-gadget.io/
================================================
FILE: plugins/ai-incident-investigation.yaml
================================================
plugins:
# Author: Pavan Gudiwada
# Investigate incidents in your cluster to quickly find the root cause using HolmesGPT
# Requires HolmesGPT to be installed and configured (https://github.com/robusta-dev/holmesgpt) on your system
# Open any K9s view, then:
# Shift+H to run an investigation with default ask command
# Shift+O to customize the question before running an investigation.
holmesgpt:
shortCut: Shift-H
description: Ask HolmesGPT
scopes:
- all
command: bash
background: false
confirm: false
args:
- -c
- |
holmes ask "why is $NAME of $RESOURCE_NAME in -n $NAMESPACE not working as expected"
echo "Press 'q' to exit"
while : ; do
read -n 1 k <&1
if [[ $k = q ]] ; then
break
fi
done
custom-holmesgpt:
shortCut: Shift-Q
description: Custom HolmesGPT Ask
scopes:
- all
command: bash
background: false
confirm: false
args:
- -c
- |
INSTRUCTIONS="# Edit the line below. Lines starting with '#' will be ignored."
DEFAULT_ASK_COMMAND="why is $NAME of $RESOURCE_NAME in -n $NAMESPACE not working as expected"
QUESTION_FILE=$(mktemp)
echo "$INSTRUCTIONS" > "$QUESTION_FILE"
echo "$DEFAULT_ASK_COMMAND" >> "$QUESTION_FILE"
# Open the line in the default text editor
${EDITOR:-nano} "$QUESTION_FILE"
# Read the modified line, ignoring lines starting with '#'
user_input=$(grep -v '^#' "$QUESTION_FILE")
echo running: holmes ask "\"$user_input\""
holmes ask "$user_input"
echo "Press 'q' to exit"
while : ; do
read -n 1 k <&1
if [[ $k = q ]] ; then
break
fi
done
================================================
FILE: plugins/argo-rollouts-powershell.yaml
================================================
# Manage argo-rollouts from PowerShell
# See https://argoproj.github.io/argo-rollouts/
#