K9s Release v0.29.0 (#2326)
* Feat: Move shell pod cluster config to general config > BREAKING CHANGE! K9s configuration breaking change! Shellpod specification will no longer reside with a cluster configuration. It is now part of the global K9s configuration object. Shellpod configuration should be part of k9s config. Clusters admins will most likely use the same image and config to run shells on their nodes. Each cluster in turn will have the option to either enable/disable shelling into nodes. This not only DRYs up the k9s config but also allows user to consolidate their shell pod configuration in one central place. * Fix #2290 - Add freebsd assets * Maintenance cleaning up * Fix #2166 - Add taint tracking column to node view * Fix #2009: Update screendump file names to contain resource info * Maintenance: Cleanup errror messages * Fix #1513: Change log default to tail vs last 5min * Fix #2166: Add taint indicator on node view * Fix #2165: Track init co restarts * Fix #2308: Fix rbac auth checks * Fix #2036: Fix npe on filtering CRDs * Fix #2219: Turn on TTY option on shellpod * Fix #2167: Update color escape sequence on copy * Fix #2297: Enable multi select on nodes * Cleanup headers * Fix #2162: Allow edit when describing/viewing * Feat: Add helm release history support * Fix #2039: Command Arrow up/down + enter support * Small refactor * Add img vulenerability scans support * Change skin loading and support - Move skin specification to k9s cluster config section - Load skins for skins dir * Release v0.29.0 docsmine
parent
4e37faf383
commit
a44cb6135c
|
|
@ -1,10 +1,10 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
concurrency: 8
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 1m
|
||||
timeout: 5m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ before:
|
|||
- go generate ./...
|
||||
release:
|
||||
prerelease: false
|
||||
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
- id: linux
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
|
@ -27,15 +29,40 @@ builds:
|
|||
- -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}}
|
||||
- -s -w -X github.com/derailed/k9s/cmd.date={{.Date}}
|
||||
|
||||
- id: osx
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}}
|
||||
- -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}}
|
||||
- -s -w -X github.com/derailed/k9s/cmd.date={{.Date}}
|
||||
|
||||
- id: windows
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}}
|
||||
- -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}}
|
||||
- -s -w -X github.com/derailed/k9s/cmd.date={{.Date}}
|
||||
|
||||
archives:
|
||||
- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
bit: Arm
|
||||
bitv6: Arm6
|
||||
bitv7: Arm7
|
||||
- name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}amd64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
|
@ -55,7 +82,7 @@ changelog:
|
|||
|
||||
brews:
|
||||
- name: k9s
|
||||
tap:
|
||||
repository:
|
||||
owner: derailed
|
||||
name: homebrew-k9s
|
||||
commit_author:
|
||||
|
|
@ -69,8 +96,6 @@ brews:
|
|||
|
||||
nfpms:
|
||||
- file_name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
|
||||
replacements:
|
||||
linux: Linux
|
||||
maintainer: Fernand Galiana
|
||||
homepage: https://k9scli.io
|
||||
description: Kubernetes CLI To Manage Your Clusters In Style!
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
|||
else
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
endif
|
||||
VERSION ?= v0.28.2
|
||||
VERSION ?= v0.29.0
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
84
README.md
84
README.md
|
|
@ -313,7 +313,7 @@ K9s uses aliases to navigate most K8s resources.
|
|||
|
||||
## K9s Configuration
|
||||
|
||||
K9s keeps its configurations inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from.
|
||||
K9s keeps its configurations as YAML files inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from.
|
||||
|
||||
| Unix | macOS | Windows |
|
||||
|-----------------|------------------------------------|-----------------------|
|
||||
|
|
@ -321,6 +321,8 @@ K9s uses aliases to navigate most K8s resources.
|
|||
|
||||
> NOTE: This is still in flux and will change while in pre-release stage!
|
||||
|
||||
> NOTE! Thanks to [Mr Alexandru Placenta](https://github.com/placintaalexandru) the config files can now use either `.yml` or `.yaml` mimes.
|
||||
|
||||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yml
|
||||
k9s:
|
||||
|
|
@ -350,8 +352,8 @@ K9s uses aliases to navigate most K8s resources.
|
|||
tail: 200
|
||||
# Defines the total number of log lines to allow in the view. Default 1000
|
||||
buffer: 500
|
||||
# Represents how far to go back in the log timeline in seconds. Setting to -1 will show all available logs. Default is 5min.
|
||||
sinceSeconds: 300
|
||||
# Represents how far to go back in the log timeline in seconds. Setting to -1 will tail logs. Default is -1.
|
||||
sinceSeconds: 300 # => tail the last 5 mins.
|
||||
# Go full screen while displaying logs. Default false
|
||||
fullScreenLogs: false
|
||||
# Toggles log line wrap. Default false
|
||||
|
|
@ -364,6 +366,18 @@ K9s uses aliases to navigate most K8s resources.
|
|||
currentCluster: minikube
|
||||
# KeepMissingClusters will keep clusters in the config if they are missing from the current kubeconfig file. Default false
|
||||
KeepMissingClusters: false
|
||||
# Provide shell pod customization when nodeShell feature gate is enabled!
|
||||
shellPod:
|
||||
# The shell pod image to use.
|
||||
image: killerAdmin
|
||||
# The namespace to launch to shell pod into.
|
||||
namespace: default
|
||||
# The resource limit to set on the shell pod.
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
# Enable TTY
|
||||
tty: true
|
||||
# Persists per cluster preferences for favorite namespaces and view.
|
||||
clusters:
|
||||
coolio:
|
||||
|
|
@ -378,22 +392,7 @@ K9s uses aliases to navigate most K8s resources.
|
|||
active: po
|
||||
featureGates:
|
||||
# Toggles NodeShell support. Allow K9s to shell into nodes if needed. Default false.
|
||||
nodeShell: false
|
||||
# Provide shell pod customization of feature gate is enabled
|
||||
shellPod:
|
||||
# The shell pod image to use.
|
||||
image: killerAdmin
|
||||
# The namespace to launch to shell pod into.
|
||||
namespace: fred
|
||||
# imagePullPolicy defaults to Always
|
||||
imagePullPolicy: Always
|
||||
# imagePullSecrets defaults to no secret
|
||||
imagePullSecrets:
|
||||
- name: my-regcred
|
||||
# The resource limit to set on the shell pod.
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
nodeShell: true
|
||||
# The IP Address to use when launching a port-forward.
|
||||
portForwardAddress: 1.2.3.4
|
||||
kind:
|
||||
|
|
@ -424,25 +423,19 @@ By enabling the nodeShell feature gate on a given cluster, K9s allows you to she
|
|||
```yaml
|
||||
# $XDG_CONFIG_HOME/k9s/config.yml
|
||||
k9s:
|
||||
# You can also further tune the shell pod specification
|
||||
shellPod:
|
||||
image: cool_kid_admin:42
|
||||
namespace: blee
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
clusters:
|
||||
# Configures node shell on cluster blee
|
||||
blee:
|
||||
featureGates:
|
||||
# You must enable the nodeShell feature gate to enable shelling into nodes
|
||||
nodeShell: true
|
||||
# You can also further tune the shell pod specification
|
||||
shellPod:
|
||||
image: cool_kid_admin:42
|
||||
namespace: blee
|
||||
# imagePullPolicy defaults to Always
|
||||
imagePullPolicy: Always
|
||||
# imagePullSecrets defaults to no secret
|
||||
imagePullSecrets:
|
||||
- name: my-regcred
|
||||
# The resource limit to set on the shell pod.
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -809,21 +802,36 @@ Example: Dracula Skin ;)
|
|||
|
||||
<img src="assets/skins/dracula.png" alt="Dracula Skin">
|
||||
|
||||
You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s skins are loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin would be loaded if not the current stock skin remains in effect.
|
||||
You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s default skin is loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin will be loaded if not the current stock skin remains in effect.
|
||||
|
||||
You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify the skin file name as `$XDG_CONFIG_HOME/k9s/mycontext_skin.yml`
|
||||
Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your user's home dir as `skin.yml`.
|
||||
You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin!) and copy this repo skins/dracula.yml to `$XDG_CONFIG_HOME/k9s/skins` directory.
|
||||
Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your k9s home dir as `skin.yml`.
|
||||
|
||||
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.
|
||||
|
||||
```yaml
|
||||
# Make cluster fred display in_the_navy skin when loaded...
|
||||
k9s:
|
||||
...
|
||||
clusters:
|
||||
fred:
|
||||
# Override the default skin and use this skin for this cluster.
|
||||
# NOTE: Just the skin file name to extension!
|
||||
skin: in_the_navy # -> Look for a skin file in ~/.config/k9s/skins/in_the_navy.yml
|
||||
namespace:
|
||||
...
|
||||
view:
|
||||
active: pod
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
portForwardAddress: localhost
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Skin InTheNavy...
|
||||
# in_the_navy.yml: Skin InTheNavy...
|
||||
k9s:
|
||||
# General K9s styles
|
||||
body:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
|
||||
|
||||
# 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 malignent 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
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
208
go.mod
208
go.mod
|
|
@ -1,9 +1,13 @@
|
|||
module github.com/derailed/k9s
|
||||
|
||||
go 1.21
|
||||
go 1.21.1
|
||||
|
||||
toolchain go1.21.4
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
|
||||
github.com/anchore/grype v0.73.4
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/derailed/popeye v0.11.1
|
||||
|
|
@ -13,7 +17,8 @@ require (
|
|||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/petergtz/pegomock v2.9.0+incompatible
|
||||
github.com/rakyll/hey v0.1.4
|
||||
github.com/rs/zerolog v1.31.0
|
||||
|
|
@ -35,126 +40,281 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/storage v1.35.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.2 // indirect
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/acobaugh/osrelease v0.1.0 // indirect
|
||||
github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 // indirect
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a // indirect
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect
|
||||
github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 // indirect
|
||||
github.com/anchore/syft v0.98.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.38.49 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.288 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd v1.7.6 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.7.8 // indirect
|
||||
github.com/containerd/continuity v0.4.2 // indirect
|
||||
github.com/containerd/fifo v1.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/ttrpc v1.2.2 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/cli v24.0.6+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/facebookincubator/nvdtools v0.1.5 // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/github/go-spdx/v2 v2.2.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.10.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.10.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-containerregistry v0.16.1 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/licensecheck v0.3.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-getter v1.7.3 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // 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/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // 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.17.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // 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/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.1 // indirect
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.27.6 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/gomega v1.27.10 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/openvex/go-vex v0.2.5 // indirect
|
||||
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect
|
||||
github.com/package-url/packageurl-go v0.1.1 // 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.1.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/rubenv/sql-migrate v1.5.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/saferwall/pe v1.4.7 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spdx/tools-golang v0.5.3 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.17.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.5 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/go-mtree v0.5.3 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // 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/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/xeipuuv/gojsonschema v1.2.0 // 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-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zclconf/go-cty v1.14.0 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.152.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/gorm v1.25.5 // indirect
|
||||
k8s.io/apiserver v0.28.3 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
modernc.org/libc v1.29.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.27.0 // indirect
|
||||
oras.land/oras-go v1.2.4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
metricsapi "k8s.io/metrics/pkg/apis/metrics"
|
||||
"k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
|
@ -457,9 +456,7 @@ func (a *APIClient) supportsMetricsResources() error {
|
|||
a.cache.Add(cacheMXAPIKey, supported, cacheExpiry)
|
||||
}()
|
||||
|
||||
cfg := cmdutil.NewMatchVersionFlags(a.config.flags)
|
||||
f := cmdutil.NewFactory(cfg)
|
||||
dial, err := f.ToDiscoveryClient()
|
||||
dial, err := a.CachedDiscovery()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("Unable to dial discovery API")
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
|
|||
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 mx, fmt.Errorf("expected PodMetricsList but got %T", entry)
|
||||
}
|
||||
return mxList, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,13 @@ const (
|
|||
var (
|
||||
// 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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ const DefaultPFAddress = "localhost"
|
|||
type Cluster struct {
|
||||
Namespace *Namespace `yaml:"namespace"`
|
||||
View *View `yaml:"view"`
|
||||
Skin string `yaml:"skin,omitempty"`
|
||||
FeatureGates *FeatureGates `yaml:"featureGates"`
|
||||
ShellPod *ShellPod `yaml:"shellPod"`
|
||||
PortForwardAddress string `yaml:"portForwardAddress"`
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ func NewCluster() *Cluster {
|
|||
View: NewView(),
|
||||
PortForwardAddress: DefaultPFAddress,
|
||||
FeatureGates: NewFeatureGates(),
|
||||
ShellPod: NewShellPod(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,9 +48,4 @@ func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) {
|
|||
c.View = NewView()
|
||||
}
|
||||
c.View.Validate()
|
||||
|
||||
if c.ShellPod == nil {
|
||||
c.ShellPod = NewShellPod()
|
||||
}
|
||||
c.ShellPod.Validate(conn, ks)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/tcell/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultColor represents a default color.
|
||||
DefaultColor Color = "default"
|
||||
|
||||
// TransparentColor represents the terminal bg color.
|
||||
TransparentColor Color = "-"
|
||||
)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -23,6 +23,10 @@ const K9sConfig = "K9SCONFIG"
|
|||
var (
|
||||
// K9sConfigFile represents K9s config file location.
|
||||
K9sConfigFile = filepath.Join(K9sHome(), "config.yml")
|
||||
|
||||
// K9sSkinDir represent K9s skin dir
|
||||
K9sSkinDir = filepath.Join(K9sHome(), "skins")
|
||||
|
||||
// K9sDefaultScreenDumpDir represents a default directory where K9s screen dumps will be persisted.
|
||||
K9sDefaultScreenDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser()))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -284,17 +284,24 @@ var expectedConfig = `k9s:
|
|||
refreshRate: 100
|
||||
maxConnRetry: 5
|
||||
enableMouse: false
|
||||
enableImageScan: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
readOnly: true
|
||||
noExitOnCtrlC: false
|
||||
noIcons: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
skipLatestRevCheck: false
|
||||
logger:
|
||||
tail: 500
|
||||
buffer: 800
|
||||
sinceSeconds: 300
|
||||
sinceSeconds: -1
|
||||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
|
|
@ -312,15 +319,6 @@ var expectedConfig = `k9s:
|
|||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
command: []
|
||||
args: []
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
labels: {}
|
||||
portForwardAddress: localhost
|
||||
fred:
|
||||
namespace:
|
||||
|
|
@ -336,15 +334,6 @@ var expectedConfig = `k9s:
|
|||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
command: []
|
||||
args: []
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
labels: {}
|
||||
portForwardAddress: localhost
|
||||
minikube:
|
||||
namespace:
|
||||
|
|
@ -360,15 +349,6 @@ var expectedConfig = `k9s:
|
|||
active: ctx
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
command: []
|
||||
args: []
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
labels: {}
|
||||
portForwardAddress: localhost
|
||||
thresholds:
|
||||
cpu:
|
||||
|
|
@ -386,17 +366,24 @@ var resetConfig = `k9s:
|
|||
refreshRate: 2
|
||||
maxConnRetry: 5
|
||||
enableMouse: false
|
||||
enableImageScan: false
|
||||
headless: false
|
||||
logoless: false
|
||||
crumbsless: false
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
noIcons: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
skipLatestRevCheck: false
|
||||
logger:
|
||||
tail: 200
|
||||
buffer: 2000
|
||||
sinceSeconds: 300
|
||||
sinceSeconds: -1
|
||||
fullScreenLogs: false
|
||||
textWrap: false
|
||||
showTime: false
|
||||
|
|
@ -414,15 +401,6 @@ var resetConfig = `k9s:
|
|||
active: po
|
||||
featureGates:
|
||||
nodeShell: false
|
||||
shellPod:
|
||||
image: busybox:1.35.0
|
||||
command: []
|
||||
args: []
|
||||
namespace: default
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
labels: {}
|
||||
portForwardAddress: localhost
|
||||
thresholds:
|
||||
cpu:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const (
|
|||
DefaultFileMod os.FileMode = 0600
|
||||
)
|
||||
|
||||
var invalidPathCharsRX = regexp.MustCompile(`[:]+`)
|
||||
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
|
||||
|
||||
// SanitizeFilename sanitizes the dump filename.
|
||||
func SanitizeFilename(name string) string {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ type K9s struct {
|
|||
RefreshRate int `yaml:"refreshRate"`
|
||||
MaxConnRetry int `yaml:"maxConnRetry"`
|
||||
EnableMouse bool `yaml:"enableMouse"`
|
||||
EnableImageScan bool `yaml:"enableImageScan"`
|
||||
Headless bool `yaml:"headless"`
|
||||
Logoless bool `yaml:"logoless"`
|
||||
Crumbsless bool `yaml:"crumbsless"`
|
||||
ReadOnly bool `yaml:"readOnly"`
|
||||
NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"`
|
||||
NoIcons bool `yaml:"noIcons"`
|
||||
ShellPod *ShellPod `yaml:"shellPod"`
|
||||
SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"`
|
||||
Logger *Logger `yaml:"logger"`
|
||||
CurrentContext string `yaml:"currentContext"`
|
||||
|
|
@ -51,6 +53,7 @@ func NewK9s() *K9s {
|
|||
Clusters: make(map[string]*Cluster),
|
||||
Thresholds: NewThreshold(),
|
||||
ScreenDumpDir: K9sDefaultScreenDumpDir,
|
||||
ShellPod: NewShellPod(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +240,11 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
|
|||
}
|
||||
k.validateClusters(c, ks)
|
||||
|
||||
if k.ShellPod == nil {
|
||||
k.ShellPod = NewShellPod()
|
||||
}
|
||||
k.ShellPod.Validate(c, ks)
|
||||
|
||||
if k.Logger == nil {
|
||||
k.Logger = NewLogger()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ import (
|
|||
const (
|
||||
// DefaultLoggerTailCount tracks default log tail size.
|
||||
DefaultLoggerTailCount = 100
|
||||
|
||||
// MaxLogThreshold sets the max value for log size.
|
||||
MaxLogThreshold = 5000
|
||||
|
||||
// DefaultSinceSeconds tracks default log age.
|
||||
DefaultSinceSeconds = 300 // all logs
|
||||
DefaultSinceSeconds = -1 // tail logs by default
|
||||
)
|
||||
|
||||
// Logger tracks logger options.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
|
@ -52,6 +51,7 @@ func (p Plugins) Load() error {
|
|||
for _, dataDir := range xdg.DataDirs {
|
||||
pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory))
|
||||
}
|
||||
|
||||
return p.LoadPlugins(K9sPluginsFilePath, pluginDirs)
|
||||
}
|
||||
|
||||
|
|
@ -61,15 +61,18 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pp Plugins
|
||||
if err := yaml.Unmarshal(f, &pp); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range pp.Plugin {
|
||||
p.Plugin[k] = v
|
||||
}
|
||||
|
||||
for _, pluginDir := range pluginDirs {
|
||||
pluginFiles, err := os.ReadDir(pluginDir)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("Failed reading plugin path %s; %s", pluginDir, err)
|
||||
continue
|
||||
}
|
||||
for _, file := range pluginFiles {
|
||||
|
|
@ -88,9 +91,5 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
for k, v := range pp.Plugin {
|
||||
p.Plugin[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,15 @@ type Limits map[v1.ResourceName]string
|
|||
|
||||
// ShellPod represents k9s shell configuration.
|
||||
type ShellPod struct {
|
||||
Image string `json:"image"`
|
||||
ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"`
|
||||
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
|
||||
Command []string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Namespace string `json:"namespace"`
|
||||
Limits Limits `json:"resources,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Image string `yaml:"image"`
|
||||
Command []string `yaml:"command,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Limits Limits `yaml:"limits,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
ImagePullSecrets []v1.LocalObjectReference `yaml:"imagePullSecrets,omitempty"`
|
||||
ImagePullPolicy v1.PullPolicy `yaml:"imagePullPolicy,omitempty"`
|
||||
TTY bool `yaml:"tty,omitempty"`
|
||||
}
|
||||
|
||||
// NewShellPod returns a new instance.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -224,57 +223,6 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultColor represents a default color.
|
||||
DefaultColor Color = "default"
|
||||
|
||||
// TransparentColor represents the terminal bg color.
|
||||
TransparentColor Color = "-"
|
||||
)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func newStyle() Style {
|
||||
return Style{
|
||||
Body: newBody(),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
@ -12,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -27,8 +25,9 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
_ Accessor = (*CronJob)(nil)
|
||||
_ Runnable = (*CronJob)(nil)
|
||||
_ Accessor = (*CronJob)(nil)
|
||||
_ Runnable = (*CronJob)(nil)
|
||||
_ ImageLister = (*CronJob)(nil)
|
||||
)
|
||||
|
||||
// CronJob represents a cronjob K8s resource.
|
||||
|
|
@ -36,6 +35,16 @@ type CronJob struct {
|
|||
Generic
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (c *CronJob) ListImages(ctx 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, _ := client.Namespaced(path)
|
||||
|
|
@ -116,6 +125,22 @@ func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, erro
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
@ -12,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -33,6 +31,7 @@ var (
|
|||
_ Scalable = (*Deployment)(nil)
|
||||
_ Controller = (*Deployment)(nil)
|
||||
_ ContainsPodSpec = (*Deployment)(nil)
|
||||
_ ImageLister = (*Deployment)(nil)
|
||||
)
|
||||
|
||||
// Deployment represents a deployment K8s resource.
|
||||
|
|
@ -40,6 +39,16 @@ type Deployment struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (d *Deployment) ListImages(ctx 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
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (d *Deployment) IsHappy(dp appsv1.Deployment) bool {
|
||||
return dp.Status.Replicas == dp.Status.AvailableReplicas
|
||||
|
|
@ -121,7 +130,7 @@ func (d *Deployment) Restart(ctx context.Context, path string) error {
|
|||
|
||||
// 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(d.Factory, opts.Path)
|
||||
dp, err := d.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -134,7 +143,7 @@ func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan,
|
|||
|
||||
// Pod returns a pod victim by name.
|
||||
func (d *Deployment) Pod(fqn string) (string, error) {
|
||||
dp, err := d.GetInstance(d.Factory, fqn)
|
||||
dp, err := d.GetInstance(fqn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -143,8 +152,8 @@ func (d *Deployment) Pod(fqn string) (string, error) {
|
|||
}
|
||||
|
||||
// GetInstance fetch a matching deployment.
|
||||
func (*Deployment) GetInstance(f Factory, fqn string) (*appsv1.Deployment, error) {
|
||||
o, err := f.Get("apps/v1/deployments", fqn, true, labels.Everything())
|
||||
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
|
||||
}
|
||||
|
|
@ -246,7 +255,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs
|
|||
|
||||
// GetPodSpec returns a pod spec given a resource.
|
||||
func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) {
|
||||
dp, err := d.GetInstance(d.Factory, path)
|
||||
dp, err := d.GetInstance(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
|
|
@ -32,6 +33,7 @@ var (
|
|||
_ Restartable = (*DaemonSet)(nil)
|
||||
_ Controller = (*DaemonSet)(nil)
|
||||
_ ContainsPodSpec = (*DaemonSet)(nil)
|
||||
_ ImageLister = (*DaemonSet)(nil)
|
||||
)
|
||||
|
||||
// DaemonSet represents a K8s daemonset.
|
||||
|
|
@ -39,6 +41,16 @@ type DaemonSet struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (d *DaemonSet) ListImages(ctx 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
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool {
|
||||
return ds.Status.DesiredNumberScheduled == ds.Status.CurrentNumberScheduled
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
@ -12,7 +9,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/render/helm"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
|
|
@ -21,19 +18,19 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
_ Accessor = (*Helm)(nil)
|
||||
_ Nuker = (*Helm)(nil)
|
||||
_ Describer = (*Helm)(nil)
|
||||
_ Accessor = (*HelmChart)(nil)
|
||||
_ Nuker = (*HelmChart)(nil)
|
||||
_ Describer = (*HelmChart)(nil)
|
||||
)
|
||||
|
||||
// Helm represents a helm chart.
|
||||
type Helm struct {
|
||||
// HelmChart represents a helm chart.
|
||||
type HelmChart struct {
|
||||
NonResource
|
||||
}
|
||||
|
||||
// List returns a collection of resources.
|
||||
func (h *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
func (h *HelmChart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -48,16 +45,16 @@ func (h *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
|
||||
oo := make([]runtime.Object, 0, len(rr))
|
||||
for _, r := range rr {
|
||||
oo = append(oo, render.HelmRes{Release: r})
|
||||
oo = append(oo, helm.ReleaseRes{Release: r})
|
||||
}
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
|
||||
// Get returns a resource.
|
||||
func (h *Helm) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||
func (h *HelmChart) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -66,13 +63,13 @@ func (h *Helm) Get(_ context.Context, path string) (runtime.Object, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return render.HelmRes{Release: resp}, nil
|
||||
return helm.ReleaseRes{Release: resp}, nil
|
||||
}
|
||||
|
||||
// GetValues returns values for a release
|
||||
func (h *Helm) GetValues(path string, allValues bool) ([]byte, error) {
|
||||
func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -87,9 +84,9 @@ func (h *Helm) GetValues(path string, allValues bool) ([]byte, error) {
|
|||
}
|
||||
|
||||
// Describe returns the chart notes.
|
||||
func (h *Helm) Describe(path string) (string, error) {
|
||||
func (h *HelmChart) Describe(path string) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -102,9 +99,9 @@ func (h *Helm) Describe(path string) (string, error) {
|
|||
}
|
||||
|
||||
// ToYAML returns the chart manifest.
|
||||
func (h *Helm) ToYAML(path string, showManaged bool) (string, error) {
|
||||
func (h *HelmChart) ToYAML(path string, showManaged bool) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -116,15 +113,20 @@ func (h *Helm) ToYAML(path string, showManaged bool) (string, error) {
|
|||
return resp.Manifest, nil
|
||||
}
|
||||
|
||||
// Delete uninstall a Helm.
|
||||
func (h *Helm) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error {
|
||||
// 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)
|
||||
cfg, err := h.EnsureHelmConfig(ns)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := action.NewUninstall(cfg)
|
||||
u.KeepHistory = true
|
||||
u.KeepHistory = keepHist
|
||||
res, err := u.Run(n)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -136,10 +138,10 @@ func (h *Helm) Delete(_ context.Context, path string, _ *metav1.DeletionPropagat
|
|||
return nil
|
||||
}
|
||||
|
||||
// EnsureHelmConfig return a new configuration.
|
||||
func (h *Helm) EnsureHelmConfig(ns string) (*action.Configuration, error) {
|
||||
// ensureHelmConfig return a new configuration.
|
||||
func ensureHelmConfig(c client.Connection, ns string) (*action.Configuration, error) {
|
||||
cfg := new(action.Configuration)
|
||||
err := cfg.Init(h.Client().Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger)
|
||||
err := cfg.Init(c.Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger)
|
||||
|
||||
return cfg, err
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// 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/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)
|
||||
)
|
||||
|
||||
// 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(), 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) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := ensureHelmConfig(h.Client(), 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
|
||||
}
|
||||
|
||||
// Describe returns the chart notes.
|
||||
func (h *HelmHistory) Describe(path string) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := ensureHelmConfig(h.Client(), 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 *HelmHistory) ToYAML(path string, showManaged bool) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := ensureHelmConfig(h.Client(), ns)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := action.NewGet(cfg).Run(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.Manifest, nil
|
||||
}
|
||||
|
||||
func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := ensureHelmConfig(h.Client(), 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)
|
||||
}
|
||||
client := action.NewRollback(cfg)
|
||||
client.Version = ver
|
||||
|
||||
return client.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(), 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
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// 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
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -18,9 +19,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
_ Accessor = (*Job)(nil)
|
||||
_ Nuker = (*Job)(nil)
|
||||
_ Loggable = (*Job)(nil)
|
||||
_ Accessor = (*Job)(nil)
|
||||
_ Nuker = (*Job)(nil)
|
||||
_ Loggable = (*Job)(nil)
|
||||
_ ImageLister = (*Deployment)(nil)
|
||||
)
|
||||
|
||||
// Job represents a K8s job resource.
|
||||
|
|
@ -28,6 +30,16 @@ type Job struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (j *Job) ListImages(ctx 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)
|
||||
|
|
@ -79,6 +91,21 @@ func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
|
|||
return podLogs(ctx, job.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
|
||||
o, err := j.GetFactory().Get(j.gvr.String(), 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(ctx context.Context, fqn string, wait bool) (Refs, error) {
|
||||
ns, n := client.Namespaced(fqn)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
|
|
@ -36,6 +33,7 @@ var (
|
|||
_ Loggable = (*Pod)(nil)
|
||||
_ Controller = (*Pod)(nil)
|
||||
_ ContainsPodSpec = (*Pod)(nil)
|
||||
_ ImageLister = (*Pod)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -79,6 +77,16 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
return &render.PodWithMetrics{Raw: u, MX: pmx}, nil
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (p *Pod) ListImages(ctx 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)
|
||||
|
|
@ -526,12 +534,19 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) {
|
|||
}
|
||||
log.Debug().Msgf("Pod status: %q", render.PodStatus(&pod))
|
||||
switch render.PodStatus(&pod) {
|
||||
case render.PhaseCompleted, render.PhaseCrashLoop, render.PhaseError, render.PhaseImagePullBackOff, render.PhaseOOMKilled:
|
||||
case render.PhaseCompleted:
|
||||
fallthrough
|
||||
case render.PhaseCrashLoop:
|
||||
fallthrough
|
||||
case render.PhaseError:
|
||||
fallthrough
|
||||
case render.PhaseImagePullBackOff:
|
||||
fallthrough
|
||||
case render.PhaseOOMKilled:
|
||||
log.Debug().Msgf("Sanitizing %s:%s", pod.Namespace, pod.Name)
|
||||
fqn := client.FQN(pod.Namespace, pod.Name)
|
||||
if err := p.Resource.Delete(ctx, fqn, nil, NowGrace); err != nil {
|
||||
log.Warn().Err(err).Msgf("Pod %s deletion failed", fqn)
|
||||
continue
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// CRD identifies a CRD.
|
||||
const CRD = "crd"
|
||||
const (
|
||||
crdCat = "crd"
|
||||
k9sCat = "k9s"
|
||||
helmCat = "helm"
|
||||
)
|
||||
|
||||
// MetaAccess tracks resources metadata.
|
||||
var MetaAccess = NewMeta()
|
||||
|
|
@ -82,6 +85,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
|||
m := Accessors{
|
||||
client.NewGVR("contexts"): &Context{},
|
||||
client.NewGVR("containers"): &Container{},
|
||||
client.NewGVR("scans"): &ImageScan{},
|
||||
client.NewGVR("screendumps"): &ScreenDump{},
|
||||
client.NewGVR("benchmarks"): &Benchmark{},
|
||||
client.NewGVR("portforwards"): &PortForward{},
|
||||
|
|
@ -91,21 +95,19 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
|||
client.NewGVR("apps/v1/deployments"): &Deployment{},
|
||||
client.NewGVR("apps/v1/daemonsets"): &DaemonSet{},
|
||||
client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
|
||||
client.NewGVR("apps/v1/replicasets"): &ReplicaSet{},
|
||||
client.NewGVR("batch/v1/cronjobs"): &CronJob{},
|
||||
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
|
||||
client.NewGVR("batch/v1/jobs"): &Job{},
|
||||
client.NewGVR("v1/namespaces"): &Namespace{},
|
||||
// BOZO!! Revamp with latest...
|
||||
// client.NewGVR("openfaas"): &OpenFaas{},
|
||||
client.NewGVR("popeye"): &Popeye{},
|
||||
client.NewGVR("sanitizer"): &Popeye{},
|
||||
client.NewGVR("helm"): &Helm{},
|
||||
client.NewGVR("dir"): &Dir{},
|
||||
client.NewGVR("popeye"): &Popeye{},
|
||||
client.NewGVR("helm"): &HelmChart{},
|
||||
client.NewGVR("dir"): &Dir{},
|
||||
}
|
||||
|
||||
r, ok := m[gvr]
|
||||
if !ok {
|
||||
r = &Generic{}
|
||||
r = new(Generic)
|
||||
log.Debug().Msgf("No DAO registry entry for %q. Using generics!", gvr)
|
||||
}
|
||||
r.Init(f, gvr)
|
||||
|
|
@ -138,7 +140,7 @@ func (m *Meta) AllGVRs() client.GVRs {
|
|||
// IsCRD checks if resource represents a CRD
|
||||
func IsCRD(r metav1.APIResource) bool {
|
||||
for _, c := range r.Categories {
|
||||
if c == CRD {
|
||||
if c == crdCat {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -160,7 +162,7 @@ func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) {
|
|||
// IsK8sMeta checks for non resource meta.
|
||||
func IsK8sMeta(m metav1.APIResource) bool {
|
||||
for _, c := range m.Categories {
|
||||
if c == "k9s" || c == "helm" || c == "faas" {
|
||||
if c == k9sCat || c == helmCat {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +173,7 @@ func IsK8sMeta(m metav1.APIResource) bool {
|
|||
// IsK9sMeta checks for non resource meta.
|
||||
func IsK9sMeta(m metav1.APIResource) bool {
|
||||
for _, c := range m.Categories {
|
||||
if c == "k9s" {
|
||||
if c == k9sCat {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -199,10 +201,6 @@ func loadNonResource(m ResourceMetas) {
|
|||
loadK9s(m)
|
||||
loadRBAC(m)
|
||||
loadHelm(m)
|
||||
// BOZO!! Revamp with latest...
|
||||
// if IsOpenFaasEnabled() {
|
||||
// loadOpenFaas(m)
|
||||
// }
|
||||
}
|
||||
|
||||
func loadK9s(m ResourceMetas) {
|
||||
|
|
@ -211,33 +209,33 @@ func loadK9s(m ResourceMetas) {
|
|||
Kind: "Pulse",
|
||||
SingularName: "pulses",
|
||||
ShortNames: []string{"hz", "pu"},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("dir")] = metav1.APIResource{
|
||||
Name: "dir",
|
||||
Kind: "Dir",
|
||||
SingularName: "dir",
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("xrays")] = metav1.APIResource{
|
||||
Name: "xray",
|
||||
Kind: "XRays",
|
||||
SingularName: "xray",
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("references")] = metav1.APIResource{
|
||||
Name: "references",
|
||||
Kind: "References",
|
||||
SingularName: "reference",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("aliases")] = metav1.APIResource{
|
||||
Name: "aliases",
|
||||
Kind: "Aliases",
|
||||
SingularName: "alias",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("popeye")] = metav1.APIResource{
|
||||
Name: "popeye",
|
||||
|
|
@ -245,14 +243,14 @@ func loadK9s(m ResourceMetas) {
|
|||
SingularName: "popeye",
|
||||
Namespaced: true,
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("sanitizer")] = metav1.APIResource{
|
||||
Name: "sanitizer",
|
||||
Kind: "Sanitizer",
|
||||
SingularName: "sanitizer",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("contexts")] = metav1.APIResource{
|
||||
Name: "contexts",
|
||||
|
|
@ -260,7 +258,7 @@ func loadK9s(m ResourceMetas) {
|
|||
SingularName: "context",
|
||||
ShortNames: []string{"ctx"},
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("screendumps")] = metav1.APIResource{
|
||||
Name: "screendumps",
|
||||
|
|
@ -268,7 +266,7 @@ func loadK9s(m ResourceMetas) {
|
|||
SingularName: "screendump",
|
||||
ShortNames: []string{"sd"},
|
||||
Verbs: []string{"delete"},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("benchmarks")] = metav1.APIResource{
|
||||
Name: "benchmarks",
|
||||
|
|
@ -276,7 +274,7 @@ func loadK9s(m ResourceMetas) {
|
|||
SingularName: "benchmark",
|
||||
ShortNames: []string{"be"},
|
||||
Verbs: []string{"delete"},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("portforwards")] = metav1.APIResource{
|
||||
Name: "portforwards",
|
||||
|
|
@ -285,60 +283,62 @@ func loadK9s(m ResourceMetas) {
|
|||
SingularName: "portforward",
|
||||
ShortNames: []string{"pf"},
|
||||
Verbs: []string{"delete"},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("containers")] = metav1.APIResource{
|
||||
Name: "containers",
|
||||
Kind: "Containers",
|
||||
SingularName: "container",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("scans")] = metav1.APIResource{
|
||||
Name: "scans",
|
||||
Kind: "Scans",
|
||||
SingularName: "scan",
|
||||
Verbs: []string{},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
}
|
||||
|
||||
func loadHelm(m ResourceMetas) {
|
||||
m[client.NewGVR("helm")] = metav1.APIResource{
|
||||
Name: "helm",
|
||||
Kind: "Helm",
|
||||
Name: "chart",
|
||||
Kind: "Chart",
|
||||
Namespaced: true,
|
||||
Verbs: []string{"delete"},
|
||||
Categories: []string{"helm"},
|
||||
Categories: []string{helmCat},
|
||||
}
|
||||
m[client.NewGVR("helm-history")] = metav1.APIResource{
|
||||
Name: "history",
|
||||
Kind: "History",
|
||||
Namespaced: true,
|
||||
Verbs: []string{"delete"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
}
|
||||
|
||||
// BOZO!! revamp with latest...
|
||||
// func loadOpenFaas(m ResourceMetas) {
|
||||
// m[client.NewGVR("openfaas")] = metav1.APIResource{
|
||||
// Name: "openfaas",
|
||||
// Kind: "OpenFaaS",
|
||||
// ShortNames: []string{"ofaas", "ofa"},
|
||||
// Namespaced: true,
|
||||
// Verbs: []string{"delete"},
|
||||
// Categories: []string{"faas"},
|
||||
// }
|
||||
// }
|
||||
|
||||
func loadRBAC(m ResourceMetas) {
|
||||
m[client.NewGVR("rbac")] = metav1.APIResource{
|
||||
Name: "rbacs",
|
||||
Kind: "Rules",
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("policy")] = metav1.APIResource{
|
||||
Name: "policies",
|
||||
Kind: "Rules",
|
||||
Namespaced: true,
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("users")] = metav1.APIResource{
|
||||
Name: "users",
|
||||
Kind: "User",
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
m[client.NewGVR("groups")] = metav1.APIResource{
|
||||
Name: "groups",
|
||||
Kind: "Group",
|
||||
Categories: []string{"k9s"},
|
||||
Categories: []string{k9sCat},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +367,7 @@ func loadPreferred(f Factory, m ResourceMetas) error {
|
|||
res.SingularName = strings.ToLower(res.Kind)
|
||||
}
|
||||
if !isStandardGroup(res.Group) {
|
||||
res.Categories = append(res.Categories, CRD)
|
||||
res.Categories = append(res.Categories, crdCat)
|
||||
}
|
||||
m[gvr] = res
|
||||
}
|
||||
|
|
@ -412,7 +412,7 @@ func loadCRDs(f Factory, m ResourceMetas) {
|
|||
log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs))
|
||||
continue
|
||||
}
|
||||
meta.Categories = append(meta.Categories, CRD)
|
||||
meta.Categories = append(meta.Categories, crdCat)
|
||||
gvr := client.NewGVRFromMeta(meta)
|
||||
m[gvr] = meta
|
||||
}
|
||||
|
|
@ -426,7 +426,7 @@ func extractMeta(o runtime.Object) (metav1.APIResource, []error) {
|
|||
|
||||
crd, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return m, append(errs, fmt.Errorf("Expected Unstructured, but got %T", o))
|
||||
return m, append(errs, fmt.Errorf("expected unstructured, but got %T", o))
|
||||
}
|
||||
|
||||
var spec map[string]interface{}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
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"
|
||||
|
|
@ -19,11 +21,25 @@ import (
|
|||
"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(ctx 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 (r *ReplicaSet) Load(f Factory, path string) (*appsv1.ReplicaSet, error) {
|
||||
o, err := f.Get("apps/v1/replicasets", path, true, labels.Everything())
|
||||
|
|
@ -98,7 +114,8 @@ func (r *ReplicaSet) Rollback(fqn string) error {
|
|||
}
|
||||
|
||||
var ddp Deployment
|
||||
dp, err := ddp.GetInstance(r.Factory, client.FQN(rs.Namespace, name))
|
||||
ddp.Init(r.Factory, client.NewGVR("apps/v1/deployments"))
|
||||
dp, err := ddp.GetInstance(client.FQN(rs.Namespace, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -31,6 +32,7 @@ var (
|
|||
_ Scalable = (*StatefulSet)(nil)
|
||||
_ Controller = (*StatefulSet)(nil)
|
||||
_ ContainsPodSpec = (*StatefulSet)(nil)
|
||||
_ ImageLister = (*StatefulSet)(nil)
|
||||
)
|
||||
|
||||
// StatefulSet represents a K8s sts.
|
||||
|
|
@ -38,6 +40,16 @@ type StatefulSet struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// ListImages lists container images.
|
||||
func (s *StatefulSet) ListImages(ctx context.Context, fqn string) ([]string, error) {
|
||||
sts, err := s.GetInstance(s.Factory, fqn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return render.ExtractImages(&sts.Spec.Template.Spec), nil
|
||||
}
|
||||
|
||||
// IsHappy check for happy sts.
|
||||
func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool {
|
||||
return sts.Status.Replicas == sts.Status.ReadyReplicas
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ type Factory interface {
|
|||
Forwarders() watch.Forwarders
|
||||
}
|
||||
|
||||
// ImageLister tracks resources with container images.
|
||||
type ImageLister interface {
|
||||
// ListImages lists container images.
|
||||
ListImages(ctx context.Context, path string) ([]string, error)
|
||||
}
|
||||
|
||||
// Getter represents a resource getter.
|
||||
type Getter interface {
|
||||
// Get return a given resource.
|
||||
|
|
|
|||
|
|
@ -8,30 +8,32 @@ type ContextKey string
|
|||
|
||||
// A collection of context keys.
|
||||
const (
|
||||
KeyFactory ContextKey = "factory"
|
||||
KeyLabels ContextKey = "labels"
|
||||
KeyFields ContextKey = "fields"
|
||||
KeyTable ContextKey = "table"
|
||||
KeyDir ContextKey = "dir"
|
||||
KeyPath ContextKey = "path"
|
||||
KeySubject ContextKey = "subject"
|
||||
KeyGVR ContextKey = "gvr"
|
||||
KeyForwards ContextKey = "forwards"
|
||||
KeyContainers ContextKey = "containers"
|
||||
KeyBenchCfg ContextKey = "benchcfg"
|
||||
KeyAliases ContextKey = "aliases"
|
||||
KeyUID ContextKey = "uid"
|
||||
KeySubjectKind ContextKey = "subjectKind"
|
||||
KeySubjectName ContextKey = "subjectName"
|
||||
KeyNamespace ContextKey = "namespace"
|
||||
KeyCluster ContextKey = "cluster"
|
||||
KeyApp ContextKey = "app"
|
||||
KeyStyles ContextKey = "styles"
|
||||
KeyMetrics ContextKey = "metrics"
|
||||
KeyHasMetrics ContextKey = "has-metrics"
|
||||
KeyToast ContextKey = "toast"
|
||||
KeyWithMetrics ContextKey = "withMetrics"
|
||||
KeyViewConfig ContextKey = "viewConfig"
|
||||
KeyWait ContextKey = "wait"
|
||||
KeyPodCounting ContextKey = "podCounting"
|
||||
KeyFactory ContextKey = "factory"
|
||||
KeyLabels ContextKey = "labels"
|
||||
KeyFields ContextKey = "fields"
|
||||
KeyTable ContextKey = "table"
|
||||
KeyDir ContextKey = "dir"
|
||||
KeyPath ContextKey = "path"
|
||||
KeySubject ContextKey = "subject"
|
||||
KeyGVR ContextKey = "gvr"
|
||||
KeyFQN ContextKey = "fqn"
|
||||
KeyForwards ContextKey = "forwards"
|
||||
KeyContainers ContextKey = "containers"
|
||||
KeyBenchCfg ContextKey = "benchcfg"
|
||||
KeyAliases ContextKey = "aliases"
|
||||
KeyUID ContextKey = "uid"
|
||||
KeySubjectKind ContextKey = "subjectKind"
|
||||
KeySubjectName ContextKey = "subjectName"
|
||||
KeyNamespace ContextKey = "namespace"
|
||||
KeyCluster ContextKey = "cluster"
|
||||
KeyApp ContextKey = "app"
|
||||
KeyStyles ContextKey = "styles"
|
||||
KeyMetrics ContextKey = "metrics"
|
||||
KeyHasMetrics ContextKey = "has-metrics"
|
||||
KeyToast ContextKey = "toast"
|
||||
KeyWithMetrics ContextKey = "withMetrics"
|
||||
KeyViewConfig ContextKey = "viewConfig"
|
||||
KeyWait ContextKey = "wait"
|
||||
KeyPodCounting ContextKey = "podCounting"
|
||||
KeyEnableImgScan ContextKey = "vulScan"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ func NewDescribe(gvr client.GVR, path string) *Describe {
|
|||
}
|
||||
}
|
||||
|
||||
// GVR returns the resource gvr.
|
||||
func (d *Describe) GVR() client.GVR {
|
||||
return d.gvr
|
||||
}
|
||||
|
||||
// GetPath returns the active resource path.
|
||||
func (d *Describe) GetPath() string {
|
||||
return d.path
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package model
|
|||
import (
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/render/helm"
|
||||
"github.com/derailed/k9s/internal/xray"
|
||||
)
|
||||
|
||||
|
|
@ -25,19 +26,22 @@ var Registry = map[string]ResourceMeta{
|
|||
DAO: &dao.Pulse{},
|
||||
},
|
||||
"helm": {
|
||||
DAO: &dao.Helm{},
|
||||
Renderer: &render.Helm{},
|
||||
DAO: &dao.HelmChart{},
|
||||
Renderer: &helm.Chart{},
|
||||
},
|
||||
"helm-history": {
|
||||
DAO: &dao.HelmHistory{},
|
||||
Renderer: &helm.History{},
|
||||
},
|
||||
// BOZO!! revamp with latest...
|
||||
// "openfaas": {
|
||||
// DAO: &dao.OpenFaas{},
|
||||
// Renderer: &render.OpenFaas{},
|
||||
// },
|
||||
"containers": {
|
||||
DAO: &dao.Container{},
|
||||
Renderer: &render.Container{},
|
||||
TreeRenderer: &xray.Container{},
|
||||
},
|
||||
"scans": {
|
||||
DAO: &dao.ImageScan{},
|
||||
Renderer: &render.ImageScan{},
|
||||
},
|
||||
"contexts": {
|
||||
DAO: &dao.Context{},
|
||||
Renderer: &render.Context{},
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ type ViewerToggleOpts map[string]bool
|
|||
type ResourceViewer interface {
|
||||
GetPath() string
|
||||
Filter(string)
|
||||
GVR() client.GVR
|
||||
ClearFilter()
|
||||
Peek() []string
|
||||
SetOptions(context.Context, ViewerToggleOpts)
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ func NewValues(gvr client.GVR, path string) *Values {
|
|||
}
|
||||
}
|
||||
|
||||
func getHelmDao() *dao.Helm {
|
||||
return Registry["helm"].DAO.(*dao.Helm)
|
||||
func getHelmDao() *dao.HelmChart {
|
||||
return Registry["helm"].DAO.(*dao.HelmChart)
|
||||
}
|
||||
|
||||
func getValues(path string, allValues bool) []string {
|
||||
|
|
@ -51,6 +51,11 @@ func getValues(path string, allValues bool) []string {
|
|||
return strings.Split(string(vals), "\n")
|
||||
}
|
||||
|
||||
// GVR returns the resource gvr.
|
||||
func (v *Values) GVR() client.GVR {
|
||||
return v.gvr
|
||||
}
|
||||
|
||||
// ToggleValues toggles between user supplied values and computed values.
|
||||
func (v *Values) ToggleValues() {
|
||||
v.allValues = !v.allValues
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ func NewYAML(gvr client.GVR, path string) *YAML {
|
|||
}
|
||||
}
|
||||
|
||||
// GVR returns the resource gvr.
|
||||
func (y *YAML) GVR() client.GVR {
|
||||
return y.gvr
|
||||
}
|
||||
|
||||
// GetPath returns the active resource path.
|
||||
func (y *YAML) GetPath() string {
|
||||
return y.path
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ func (Benchmark) Header(ns string) Header {
|
|||
func (b Benchmark) Render(o interface{}, ns string, r *Row) error {
|
||||
bench, ok := o.(BenchInfo)
|
||||
if !ok {
|
||||
return fmt.Errorf("No benchmarks available %T", o)
|
||||
return fmt.Errorf("no benchmarks available %T", o)
|
||||
}
|
||||
|
||||
data, err := b.readFile(bench.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load bench file %s", bench.Path)
|
||||
return fmt.Errorf("unable to load bench file %s", bench.Path)
|
||||
}
|
||||
|
||||
r.ID = bench.Path
|
||||
|
|
@ -75,7 +75,7 @@ func (b Benchmark) Render(o interface{}, ns string, r *Row) error {
|
|||
return err
|
||||
}
|
||||
b.augmentRow(r.Fields, data)
|
||||
r.Fields[8] = asStatus(b.diagnose(ns, r.Fields))
|
||||
r.Fields[8] = AsStatus(b.diagnose(ns, r.Fields))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ func (Benchmark) readFile(file string) (string, error) {
|
|||
func (b Benchmark) initRow(row Fields, f os.FileInfo) error {
|
||||
tokens := strings.Split(f.Name(), "_")
|
||||
if len(tokens) < 2 {
|
||||
return fmt.Errorf("Invalid file name %s", f.Name())
|
||||
return fmt.Errorf("invalid file name %s", f.Name())
|
||||
}
|
||||
row[0] = tokens[0]
|
||||
row[1] = tokens[1]
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func (Container) Header(ns string) Header {
|
|||
func (c Container) Render(o interface{}, name string, r *Row) error {
|
||||
co, ok := o.(ContainerRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected ContainerRes, but got %T", o)
|
||||
return fmt.Errorf("expected ContainerRes, but got %T", o)
|
||||
}
|
||||
|
||||
cur, res := gatherMetrics(co.Container, co.MX)
|
||||
|
|
@ -127,8 +127,8 @@ func (c Container) Render(o interface{}, name string, r *Row) error {
|
|||
client.ToPercentageStr(cur.mem, res.mem),
|
||||
client.ToPercentageStr(cur.mem, res.lmem),
|
||||
ToContainerPorts(co.Container.Ports),
|
||||
asStatus(c.diagnose(state, ready)),
|
||||
toAge(co.Age),
|
||||
AsStatus(c.diagnose(state, ready)),
|
||||
ToAge(co.Age),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func (ClusterRole) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
cr.Name,
|
||||
mapToStr(cr.Labels),
|
||||
toAge(cr.GetCreationTimestamp()),
|
||||
ToAge(cr.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func (ClusterRoleBinding) Header(string) Header {
|
|||
func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected ClusterRoleBinding, but got %T", o)
|
||||
return fmt.Errorf("expected ClusterRoleBinding, but got %T", o)
|
||||
}
|
||||
var crb rbacv1.ClusterRoleBinding
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &crb)
|
||||
|
|
@ -50,7 +50,7 @@ func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error {
|
|||
kind,
|
||||
ss,
|
||||
mapToStr(crb.Labels),
|
||||
toAge(crb.GetCreationTimestamp()),
|
||||
ToAge(crb.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (CustomResourceDefinition) Header(string) Header {
|
|||
func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected CustomResourceDefinition, but got %T", o)
|
||||
return fmt.Errorf("expected CustomResourceDefinition, but got %T", o)
|
||||
}
|
||||
|
||||
var crd v1.CustomResourceDefinition
|
||||
|
|
@ -63,8 +63,8 @@ func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error
|
|||
crd.GetName(),
|
||||
naStrings(versions),
|
||||
mapToIfc(crd.GetLabels()),
|
||||
asStatus(c.diagnose(crd.GetName(), crd.Spec.Versions)),
|
||||
toAge(crd.GetCreationTimestamp()),
|
||||
AsStatus(c.diagnose(crd.GetName(), crd.Spec.Versions)),
|
||||
ToAge(crd.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -87,7 +87,7 @@ func (c CustomResourceDefinition) diagnose(n string, vv []v1.CustomResourceDefin
|
|||
if v.DeprecationWarning != nil {
|
||||
ee = append(ee, fmt.Errorf("%s", *v.DeprecationWarning))
|
||||
} else {
|
||||
ee = append(ee, fmt.Errorf("%s[%s] is deprecated!", n, v.Name))
|
||||
ee = append(ee, fmt.Errorf("%s[%s] is deprecated", n, v.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -22,9 +23,10 @@ type CronJob struct {
|
|||
|
||||
// Header returns a header row.
|
||||
func (CronJob) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "SCHEDULE"},
|
||||
HeaderColumn{Name: "SUSPEND"},
|
||||
HeaderColumn{Name: "ACTIVE"},
|
||||
|
|
@ -36,13 +38,19 @@ func (CronJob) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (c CronJob) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected CronJob, but got %T", o)
|
||||
return fmt.Errorf("expected CronJob, but got %T", o)
|
||||
}
|
||||
var cj batchv1.CronJob
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cj)
|
||||
|
|
@ -52,13 +60,14 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error {
|
|||
|
||||
lastScheduled := "<none>"
|
||||
if cj.Status.LastScheduleTime != nil {
|
||||
lastScheduled = toAge(*cj.Status.LastScheduleTime)
|
||||
lastScheduled = ToAge(*cj.Status.LastScheduleTime)
|
||||
}
|
||||
|
||||
r.ID = client.MetaFQN(cj.ObjectMeta)
|
||||
r.Fields = Fields{
|
||||
cj.Namespace,
|
||||
cj.Name,
|
||||
computeVulScore(&cj.Spec.JobTemplate.Spec.Template.Spec),
|
||||
cj.Spec.Schedule,
|
||||
boolPtrToStr(cj.Spec.Suspend),
|
||||
strconv.Itoa(len(cj.Status.Active)),
|
||||
|
|
@ -68,7 +77,10 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error {
|
|||
podImageNames(cj.Spec.JobTemplate.Spec.Template.Spec, true),
|
||||
mapToStr(cj.Labels),
|
||||
"",
|
||||
toAge(cj.GetCreationTimestamp()),
|
||||
ToAge(cj.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/tview"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -26,9 +27,10 @@ func (d Deployment) ColorerFunc() ColorerFunc {
|
|||
|
||||
// Header returns a header row.
|
||||
func (Deployment) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "READY", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight},
|
||||
|
|
@ -36,13 +38,18 @@ func (Deployment) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (d Deployment) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Deployment, but got %T", o)
|
||||
return fmt.Errorf("expected Deployment, but got %T", o)
|
||||
}
|
||||
|
||||
var dp appsv1.Deployment
|
||||
|
|
@ -55,12 +62,16 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
dp.Namespace,
|
||||
dp.Name,
|
||||
computeVulScore(&dp.Spec.Template.Spec),
|
||||
strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(dp.Status.Replicas)),
|
||||
strconv.Itoa(int(dp.Status.UpdatedReplicas)),
|
||||
strconv.Itoa(int(dp.Status.AvailableReplicas)),
|
||||
mapToStr(dp.Labels),
|
||||
asStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)),
|
||||
toAge(dp.GetCreationTimestamp()),
|
||||
AsStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)),
|
||||
ToAge(dp.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/tview"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -21,9 +22,10 @@ type DaemonSet struct {
|
|||
|
||||
// Header returns a header row.
|
||||
func (DaemonSet) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "DESIRED", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "CURRENT", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "READY", Align: tview.AlignRight},
|
||||
|
|
@ -33,13 +35,18 @@ func (DaemonSet) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (d DaemonSet) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected DaemonSet, but got %T", o)
|
||||
return fmt.Errorf("expected DaemonSet, but got %T", o)
|
||||
}
|
||||
var ds appsv1.DaemonSet
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ds)
|
||||
|
|
@ -51,14 +58,18 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
ds.Namespace,
|
||||
ds.Name,
|
||||
computeVulScore(&ds.Spec.Template.Spec),
|
||||
strconv.Itoa(int(ds.Status.DesiredNumberScheduled)),
|
||||
strconv.Itoa(int(ds.Status.CurrentNumberScheduled)),
|
||||
strconv.Itoa(int(ds.Status.NumberReady)),
|
||||
strconv.Itoa(int(ds.Status.UpdatedNumberScheduled)),
|
||||
strconv.Itoa(int(ds.Status.NumberAvailable)),
|
||||
mapToStr(ds.Labels),
|
||||
asStatus(d.diagnose(ds.Status.DesiredNumberScheduled, ds.Status.NumberReady)),
|
||||
toAge(ds.GetCreationTimestamp()),
|
||||
AsStatus(d.diagnose(ds.Status.DesiredNumberScheduled, ds.Status.NumberReady)),
|
||||
ToAge(ds.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func (Endpoints) Header(ns string) Header {
|
|||
func (e Endpoints) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Endpoints, but got %T", o)
|
||||
return fmt.Errorf("expected Endpoints, but got %T", o)
|
||||
}
|
||||
var ep v1.Endpoints
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ep)
|
||||
|
|
@ -47,7 +47,7 @@ func (e Endpoints) Render(o interface{}, ns string, r *Row) error {
|
|||
ep.Namespace,
|
||||
ep.Name,
|
||||
missing(toEPs(ep.Subsets)),
|
||||
toAge(ep.GetCreationTimestamp()),
|
||||
ToAge(ep.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
)
|
||||
|
|
@ -99,8 +98,6 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
|||
}
|
||||
if d, ok := duration.(string); ok {
|
||||
r.Fields = append(r.Fields, d)
|
||||
} else {
|
||||
log.Warn().Msgf("No Duration detected on age field")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Helm renders a helm chart to screen.
|
||||
type Helm struct{}
|
||||
|
||||
// IsGeneric identifies a generic handler.
|
||||
func (Helm) IsGeneric() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ColorerFunc colors a resource row.
|
||||
func (Helm) ColorerFunc() ColorerFunc {
|
||||
return func(ns string, h Header, re RowEvent) tcell.Color {
|
||||
if !Happy(ns, h, re.Row) {
|
||||
return ErrColor
|
||||
}
|
||||
|
||||
return tcell.ColorMediumSpringGreen
|
||||
}
|
||||
}
|
||||
|
||||
// Header returns a header row.
|
||||
func (Helm) Header(_ string) Header {
|
||||
return Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "REVISION"},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
HeaderColumn{Name: "CHART"},
|
||||
HeaderColumn{Name: "APP VERSION"},
|
||||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders a chart to screen.
|
||||
func (c Helm) Render(o interface{}, ns string, r *Row) error {
|
||||
h, ok := o.(HelmRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected HelmRes, but got %T", o)
|
||||
}
|
||||
|
||||
r.ID = client.FQN(h.Release.Namespace, h.Release.Name)
|
||||
r.Fields = Fields{
|
||||
h.Release.Namespace,
|
||||
h.Release.Name,
|
||||
strconv.Itoa(h.Release.Version),
|
||||
h.Release.Info.Status.String(),
|
||||
h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version,
|
||||
h.Release.Chart.Metadata.AppVersion,
|
||||
asStatus(c.diagnose(h.Release.Info.Status.String())),
|
||||
toAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Helm) diagnose(s string) error {
|
||||
if s != "deployed" {
|
||||
return fmt.Errorf("chart is in an invalid state")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// HelmRes represents an helm chart resource.
|
||||
type HelmRes struct {
|
||||
Release *release.Release
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
func (HelmRes) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a container copy.
|
||||
func (h HelmRes) DeepCopyObject() runtime.Object {
|
||||
return h
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package helm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Chart renders a helm chart to screen.
|
||||
type Chart struct{}
|
||||
|
||||
// IsGeneric identifies a generic handler.
|
||||
func (Chart) IsGeneric() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ColorerFunc colors a resource row.
|
||||
func (Chart) ColorerFunc() render.ColorerFunc {
|
||||
return render.DefaultColorer
|
||||
}
|
||||
|
||||
// Header returns a header row.
|
||||
func (Chart) Header(_ string) render.Header {
|
||||
return render.Header{
|
||||
render.HeaderColumn{Name: "NAMESPACE"},
|
||||
render.HeaderColumn{Name: "NAME"},
|
||||
render.HeaderColumn{Name: "REVISION"},
|
||||
render.HeaderColumn{Name: "STATUS"},
|
||||
render.HeaderColumn{Name: "CHART"},
|
||||
render.HeaderColumn{Name: "APP VERSION"},
|
||||
render.HeaderColumn{Name: "VALID", Wide: true},
|
||||
render.HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders a chart to screen.
|
||||
func (c Chart) Render(o interface{}, ns string, r *render.Row) error {
|
||||
h, ok := o.(ReleaseRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected ReleaseRes, but got %T", o)
|
||||
}
|
||||
|
||||
r.ID = client.FQN(h.Release.Namespace, h.Release.Name)
|
||||
r.Fields = render.Fields{
|
||||
h.Release.Namespace,
|
||||
h.Release.Name,
|
||||
strconv.Itoa(h.Release.Version),
|
||||
h.Release.Info.Status.String(),
|
||||
h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version,
|
||||
h.Release.Chart.Metadata.AppVersion,
|
||||
render.AsStatus(c.diagnose(h.Release.Info.Status.String())),
|
||||
render.ToAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Chart) diagnose(s string) error {
|
||||
if s != "deployed" {
|
||||
return fmt.Errorf("chart is in an invalid state")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// ReleaseRes represents an helm chart resource.
|
||||
type ReleaseRes struct {
|
||||
Release *release.Release
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
func (ReleaseRes) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a container copy.
|
||||
func (h ReleaseRes) DeepCopyObject() runtime.Object {
|
||||
return h
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package helm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
)
|
||||
|
||||
// History renders a History chart to screen.
|
||||
type History struct{}
|
||||
|
||||
// Healthy checks component health.
|
||||
func (History) Healthy(ctx context.Context, o interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsGeneric identifies a generic handler.
|
||||
func (History) IsGeneric() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ColorerFunc colors a resource row.
|
||||
func (History) ColorerFunc() render.ColorerFunc {
|
||||
return render.DefaultColorer
|
||||
}
|
||||
|
||||
// Header returns a header row.
|
||||
func (History) Header(_ string) render.Header {
|
||||
return render.Header{
|
||||
render.HeaderColumn{Name: "REVISION"},
|
||||
render.HeaderColumn{Name: "STATUS"},
|
||||
render.HeaderColumn{Name: "CHART"},
|
||||
render.HeaderColumn{Name: "APP VERSION"},
|
||||
render.HeaderColumn{Name: "DESCRIPTION"},
|
||||
render.HeaderColumn{Name: "VALID", Wide: true},
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders a chart to screen.
|
||||
func (c History) Render(o interface{}, ns string, r *render.Row) error {
|
||||
h, ok := o.(ReleaseRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected HistoryRes, but got %T", o)
|
||||
}
|
||||
|
||||
r.ID = client.FQN(h.Release.Namespace, h.Release.Name)
|
||||
r.ID += ":" + strconv.Itoa(h.Release.Version)
|
||||
r.Fields = render.Fields{
|
||||
strconv.Itoa(h.Release.Version),
|
||||
h.Release.Info.Status.String(),
|
||||
h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version,
|
||||
h.Release.Chart.Metadata.AppVersion,
|
||||
h.Release.Info.Description,
|
||||
render.AsStatus(c.diagnose(h.Release.Info.Status.String())),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c History) diagnose(s string) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -10,16 +10,28 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/tview"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
func computeVulScore(spec *v1.PodSpec) string {
|
||||
if vul.ImgScanner == nil {
|
||||
return "0"
|
||||
}
|
||||
ii := ExtractImages(spec)
|
||||
vul.ImgScanner.Enqueue(ii...)
|
||||
|
||||
return vul.ImgScanner.Score(ii...)
|
||||
}
|
||||
|
||||
func runesToNum(rr []rune) int64 {
|
||||
var r int64
|
||||
var m int64 = 1
|
||||
|
|
@ -85,7 +97,8 @@ func Happy(ns string, h Header, r Row) bool {
|
|||
return strings.TrimSpace(r.Fields[validCol]) == ""
|
||||
}
|
||||
|
||||
func asStatus(err error) string {
|
||||
// AsStatus returns error as string.
|
||||
func AsStatus(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -204,7 +217,8 @@ func boolToStr(b bool) string {
|
|||
}
|
||||
}
|
||||
|
||||
func toAge(t metav1.Time) string {
|
||||
// ToAge converts time to human duration.
|
||||
func ToAge(t metav1.Time) string {
|
||||
if t.IsZero() {
|
||||
return UnknownValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func TestToAge(t *testing.T) {
|
|||
for k := range uu {
|
||||
uc := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t}))
|
||||
assert.Equal(t, uc.e, ToAge(metav1.Time{Time: uc.t}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
CVEParseIdx = 5
|
||||
sevColName = "SEVERITY"
|
||||
)
|
||||
|
||||
// ImageScan renders scans report table.
|
||||
type ImageScan struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// ColorerFunc colors a resource row.
|
||||
func (c ImageScan) ColorerFunc() ColorerFunc {
|
||||
return func(ns string, h Header, re RowEvent) tcell.Color {
|
||||
c := DefaultColorer(ns, h, re)
|
||||
|
||||
sevCol := h.IndexOf(sevColName, true)
|
||||
if sevCol == -1 {
|
||||
return c
|
||||
}
|
||||
sev := strings.TrimSpace(re.Row.Fields[sevCol])
|
||||
switch sev {
|
||||
case vul.Sev1:
|
||||
c = tcell.ColorRed
|
||||
case vul.Sev2:
|
||||
c = tcell.ColorDarkOrange
|
||||
case vul.Sev3:
|
||||
c = tcell.ColorYellow
|
||||
case vul.Sev4:
|
||||
c = tcell.ColorDeepSkyBlue
|
||||
case vul.Sev5:
|
||||
c = tcell.ColorCadetBlue
|
||||
default:
|
||||
c = tcell.ColorDarkOliveGreen
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Header returns a header row.
|
||||
func (ImageScan) Header(ns string) Header {
|
||||
return Header{
|
||||
HeaderColumn{Name: "SEVERITY"},
|
||||
HeaderColumn{Name: "VULNERABILITY"},
|
||||
HeaderColumn{Name: "IMAGE"},
|
||||
HeaderColumn{Name: "LIBRARY"},
|
||||
HeaderColumn{Name: "VERSION"},
|
||||
HeaderColumn{Name: "FIXED-IN"},
|
||||
HeaderColumn{Name: "TYPE"},
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (is ImageScan) Render(o interface{}, name string, r *Row) error {
|
||||
res, ok := o.(ImageScanRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected ImageScanRes, but got %T", o)
|
||||
}
|
||||
|
||||
r.ID = fmt.Sprintf("%s|%s", res.Image, strings.Join(res.Row, "|"))
|
||||
r.Fields = Fields{
|
||||
res.Row.Severity(),
|
||||
res.Row.Vulnerability(),
|
||||
res.Image,
|
||||
res.Row.Name(),
|
||||
res.Row.Version(),
|
||||
res.Row.Fix(),
|
||||
res.Row.Type(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// ImageScanRes represents a container and its metrics.
|
||||
type ImageScanRes struct {
|
||||
Image string
|
||||
Row vul.Row
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
func (ImageScanRes) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a container copy.
|
||||
func (is ImageScanRes) DeepCopyObject() runtime.Object {
|
||||
return is
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -25,9 +26,10 @@ type Job struct {
|
|||
|
||||
// Header returns a header row.
|
||||
func (Job) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "COMPLETIONS"},
|
||||
HeaderColumn{Name: "DURATION"},
|
||||
HeaderColumn{Name: "SELECTOR", Wide: true},
|
||||
|
|
@ -36,13 +38,18 @@ func (Job) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (j Job) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Job, but got %T", o)
|
||||
return fmt.Errorf("expected Job, but got %T", o)
|
||||
}
|
||||
var job batchv1.Job
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &job)
|
||||
|
|
@ -57,13 +64,17 @@ func (j Job) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
job.Namespace,
|
||||
job.Name,
|
||||
computeVulScore(&job.Spec.Template.Spec),
|
||||
ready,
|
||||
toDuration(job.Status),
|
||||
jobSelector(job.Spec),
|
||||
cc,
|
||||
ii,
|
||||
asStatus(j.diagnose(ready, job.Status.CompletionTime)),
|
||||
toAge(job.GetCreationTimestamp()),
|
||||
AsStatus(j.diagnose(ready, job.Status.CompletionTime)),
|
||||
ToAge(job.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ func (Node) Header(_ string) Header {
|
|||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
HeaderColumn{Name: "ROLE"},
|
||||
HeaderColumn{Name: "TAINTS"},
|
||||
HeaderColumn{Name: "VERSION"},
|
||||
HeaderColumn{Name: "KERNEL", Wide: true},
|
||||
HeaderColumn{Name: "INTERNAL-IP", Wide: true},
|
||||
|
|
@ -89,6 +90,7 @@ func (n Node) Render(o interface{}, ns string, r *Row) error {
|
|||
no.Name,
|
||||
join(statuses, ","),
|
||||
join(roles, ","),
|
||||
strconv.Itoa(len(no.Spec.Taints)),
|
||||
no.Status.NodeInfo.KubeletVersion,
|
||||
no.Status.NodeInfo.KernelVersion,
|
||||
iIP,
|
||||
|
|
@ -101,8 +103,8 @@ func (n Node) Render(o interface{}, ns string, r *Row) error {
|
|||
toMc(a.cpu),
|
||||
toMi(a.mem),
|
||||
mapToStr(no.Labels),
|
||||
asStatus(n.diagnose(statuses)),
|
||||
toAge(no.GetCreationTimestamp()),
|
||||
AsStatus(n.diagnose(statuses)),
|
||||
ToAge(no.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func TestNodeRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "minikube", r.ID)
|
||||
e := render.Fields{"minikube", "Ready", "master", "v1.15.2", "4.15.0", "192.168.64.107", "<none>", "0", "10", "20", "0", "0", "4000", "7874"}
|
||||
assert.Equal(t, e, r.Fields[:14])
|
||||
e := render.Fields{"minikube", "Ready", "master", "0", "v1.15.2", "4.15.0", "192.168.64.107", "<none>", "0", "10", "20", "0", "0", "4000", "7874"}
|
||||
assert.Equal(t, e, r.Fields[:15])
|
||||
}
|
||||
|
||||
func BenchmarkNodeRender(b *testing.B) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (NetworkPolicy) Header(ns string) Header {
|
|||
func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected NetworkPolicy, but got %T", o)
|
||||
return fmt.Errorf("expected NetworkPolicy, but got %T", o)
|
||||
}
|
||||
var np netv1.NetworkPolicy
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &np)
|
||||
|
|
@ -63,7 +63,7 @@ func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error {
|
|||
eb,
|
||||
mapToStr(np.Labels),
|
||||
"",
|
||||
toAge(np.GetCreationTimestamp()),
|
||||
ToAge(np.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (Namespace) Header(string) Header {
|
|||
func (n Namespace) Render(o interface{}, _ string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Namespace, but got %T", o)
|
||||
return fmt.Errorf("expected Namespace, but got %T", o)
|
||||
}
|
||||
var ns v1.Namespace
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ns)
|
||||
|
|
@ -68,8 +68,8 @@ func (n Namespace) Render(o interface{}, _ string, r *Row) error {
|
|||
ns.Name,
|
||||
string(ns.Status.Phase),
|
||||
mapToStr(ns.Labels),
|
||||
asStatus(n.diagnose(ns.Status.Phase)),
|
||||
toAge(ns.GetCreationTimestamp()),
|
||||
AsStatus(n.diagnose(ns.Status.Phase)),
|
||||
ToAge(ns.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package render
|
||||
|
||||
// BOZO!! revamp with latest...
|
||||
|
||||
// import (
|
||||
// "errors"
|
||||
// "fmt"
|
||||
// "strconv"
|
||||
// "time"
|
||||
|
||||
// "github.com/derailed/k9s/internal/client"
|
||||
// "github.com/derailed/tview"
|
||||
// "github.com/derailed/tcell/v2"
|
||||
|
||||
// ofaas "github.com/openfaas/faas-provider/types"
|
||||
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
// "k8s.io/apimachinery/pkg/runtime"
|
||||
// "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
// )
|
||||
|
||||
// const (
|
||||
// fnStatusReady = "Ready"
|
||||
// fnStatusNotReady = "Not Ready"
|
||||
// )
|
||||
|
||||
// // OpenFaas renders an openfaas function to screen.
|
||||
// type OpenFaas struct{}
|
||||
|
||||
// // ColorerFunc colors a resource row.
|
||||
// func (o OpenFaas) ColorerFunc() ColorerFunc {
|
||||
// return func(ns string, h Header, re RowEvent) tcell.Color {
|
||||
// if !Happy(ns, h, re.Row) {
|
||||
// return ErrColor
|
||||
// }
|
||||
|
||||
// return tcell.ColorPaleTurquoise
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Header returns a header row.
|
||||
// func (OpenFaas) Header(ns string) Header {
|
||||
// return Header{
|
||||
// HeaderColumn{Name: "NAMESPACE"},
|
||||
// HeaderColumn{Name: "NAME"},
|
||||
// HeaderColumn{Name: "STATUS"},
|
||||
// HeaderColumn{Name: "IMAGE"},
|
||||
// HeaderColumn{Name: "LABELS"},
|
||||
// HeaderColumn{Name: "INVOCATIONS", Align: tview.AlignRight},
|
||||
// HeaderColumn{Name: "REPLICAS", Align: tview.AlignRight},
|
||||
// HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight},
|
||||
// HeaderColumn{Name: "VALID", Wide: true},
|
||||
// HeaderColumn{Name: "AGE", Time: true},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Render renders a chart to screen.
|
||||
// func (o OpenFaas) Render(i interface{}, ns string, r *Row) error {
|
||||
// fn, ok := i.(OpenFaasRes)
|
||||
// if !ok {
|
||||
// return fmt.Errorf("expected OpenFaasRes, but got %T", o)
|
||||
// }
|
||||
|
||||
// var labels string
|
||||
// if fn.Function.Labels != nil {
|
||||
// labels = mapToStr(*fn.Function.Labels)
|
||||
// }
|
||||
// status := fnStatusReady
|
||||
// if fn.Function.AvailableReplicas == 0 {
|
||||
// status = fnStatusNotReady
|
||||
// }
|
||||
|
||||
// r.ID = client.FQN(fn.Function.Namespace, fn.Function.Name)
|
||||
// r.Fields = Fields{
|
||||
// fn.Function.Namespace,
|
||||
// fn.Function.Name,
|
||||
// status,
|
||||
// fn.Function.Image,
|
||||
// labels,
|
||||
// strconv.Itoa(int(fn.Function.InvocationCount)),
|
||||
// strconv.Itoa(int(fn.Function.Replicas)),
|
||||
// strconv.Itoa(int(fn.Function.AvailableReplicas)),
|
||||
// asStatus(o.diagnose(status)),
|
||||
// toAge(metav1.Time{Time: time.Now()}),
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (OpenFaas) diagnose(status string) error {
|
||||
// if status != "Ready" {
|
||||
// return errors.New("function not ready")
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // ----------------------------------------------------------------------------
|
||||
// // Helpers...
|
||||
|
||||
// // OpenFaasRes represents an openfaas function resource.
|
||||
// type OpenFaasRes struct {
|
||||
// Function ofaas.FunctionStatus `json:"function"`
|
||||
// }
|
||||
|
||||
// // GetObjectKind returns a schema object.
|
||||
// func (OpenFaasRes) GetObjectKind() schema.ObjectKind {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // DeepCopyObject returns a container copy.
|
||||
// func (h OpenFaasRes) DeepCopyObject() runtime.Object {
|
||||
// return h
|
||||
// }
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package render_test
|
||||
|
||||
// BOZO!! revamp with latest...
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/render"
|
||||
// ofaas "github.com/openfaas/faas-provider/types"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
// func TestOpenFaasRender(t *testing.T) {
|
||||
// c := render.OpenFaas{}
|
||||
// r := render.NewRow(9)
|
||||
// c.Render(makeFn("blee"), "", &r)
|
||||
|
||||
// assert.Equal(t, "default/blee", r.ID)
|
||||
// assert.Equal(t, render.Fields{"default", "blee", "Ready", "nginx:0", "fred=blee", "10", "1", "1"}, r.Fields[:8])
|
||||
// }
|
||||
|
||||
// // Helpers...
|
||||
|
||||
// func makeFn(n string) render.OpenFaasRes {
|
||||
// return render.OpenFaasRes{
|
||||
// Function: ofaas.FunctionStatus{
|
||||
// Name: n,
|
||||
// Namespace: "default",
|
||||
// Image: "nginx:0",
|
||||
// InvocationCount: 10,
|
||||
// Replicas: 1,
|
||||
// AvailableReplicas: 1,
|
||||
// Labels: &map[string]string{"fred": "blee"},
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
|
|
@ -41,7 +41,7 @@ func (PodDisruptionBudget) Header(ns string) Header {
|
|||
func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected PodDisruptionBudget, but got %T", o)
|
||||
return fmt.Errorf("expected PodDisruptionBudget, but got %T", o)
|
||||
}
|
||||
var pdb v1beta1.PodDisruptionBudget
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pdb)
|
||||
|
|
@ -60,8 +60,8 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
|
|||
strconv.Itoa(int(pdb.Status.DesiredHealthy)),
|
||||
strconv.Itoa(int(pdb.Status.ExpectedPods)),
|
||||
mapToStr(pdb.Labels),
|
||||
asStatus(p.diagnose(pdb.Spec.MinAvailable, pdb.Status.CurrentHealthy)),
|
||||
toAge(pdb.GetCreationTimestamp()),
|
||||
AsStatus(p.diagnose(pdb.Spec.MinAvailable, pdb.Status.CurrentHealthy)),
|
||||
ToAge(pdb.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ import (
|
|||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodeUnreachablePodReason is reason and message set on a pod when its state
|
||||
// cannot be confirmed as kubelet is unresponsive on the node it is (was) running.
|
||||
NodeUnreachablePodReason = "NodeLost" // k8s.io/kubernetes/pkg/util/node.NodeUnreachablePodReason
|
||||
vulIdx = 2
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -83,9 +85,10 @@ func (p Pod) ColorerFunc() ColorerFunc {
|
|||
|
||||
// Header returns a header row.
|
||||
func (Pod) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "PF"},
|
||||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
|
|
@ -107,6 +110,22 @@ func (Pod) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// ExtractImages returns a collection of container images.
|
||||
// !!BOZO!! If this has any legs?? enable scans on other container types.
|
||||
func ExtractImages(spec *v1.PodSpec) []string {
|
||||
ii := make([]string, 0, len(spec.Containers))
|
||||
for _, c := range spec.Containers {
|
||||
ii = append(ii, c.Image)
|
||||
}
|
||||
|
||||
return ii
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
|
|
@ -121,8 +140,10 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ss := po.Status.ContainerStatuses
|
||||
cr, _, rc := p.Statuses(ss)
|
||||
ics := po.Status.InitContainerStatuses
|
||||
_, _, irc := p.Statuses(ics)
|
||||
cs := po.Status.ContainerStatuses
|
||||
cr, _, rc := p.Statuses(cs)
|
||||
|
||||
c, r := p.gatherPodMX(&po, pwm.MX)
|
||||
phase := p.Phase(&po)
|
||||
|
|
@ -130,10 +151,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error {
|
|||
row.Fields = Fields{
|
||||
po.Namespace,
|
||||
po.ObjectMeta.Name,
|
||||
computeVulScore(&po.Spec),
|
||||
"●",
|
||||
strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)),
|
||||
phase,
|
||||
strconv.Itoa(rc),
|
||||
strconv.Itoa(rc + irc),
|
||||
na(po.Status.PodIP),
|
||||
na(po.Spec.NodeName),
|
||||
asNominated(po.Status.NominatedNodeName),
|
||||
|
|
@ -148,8 +170,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error {
|
|||
client.ToPercentageStr(c.mem, r.lmem),
|
||||
p.mapQOS(po.Status.QOSClass),
|
||||
mapToStr(po.Labels),
|
||||
asStatus(p.diagnose(phase, cr, len(ss))),
|
||||
toAge(po.GetCreationTimestamp()),
|
||||
AsStatus(p.diagnose(phase, cr, len(cs))),
|
||||
ToAge(po.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func (PersistentVolume) Header(string) Header {
|
|||
func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected PersistentVolume, but got %T", o)
|
||||
return fmt.Errorf("expected PersistentVolume, but got %T", o)
|
||||
}
|
||||
var pv v1.PersistentVolume
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pv)
|
||||
|
|
@ -105,8 +105,8 @@ func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error {
|
|||
pv.Status.Reason,
|
||||
p.volumeMode(pv.Spec.VolumeMode),
|
||||
mapToStr(pv.Labels),
|
||||
asStatus(p.diagnose(phase)),
|
||||
toAge(pv.GetCreationTimestamp()),
|
||||
AsStatus(p.diagnose(phase)),
|
||||
ToAge(pv.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (PersistentVolumeClaim) Header(ns string) Header {
|
|||
func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected PersistentVolumeClaim, but got %T", o)
|
||||
return fmt.Errorf("expected PersistentVolumeClaim, but got %T", o)
|
||||
}
|
||||
var pvc v1.PersistentVolumeClaim
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pvc)
|
||||
|
|
@ -74,8 +74,8 @@ func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error {
|
|||
accessModes,
|
||||
class,
|
||||
mapToStr(pvc.Labels),
|
||||
asStatus(p.diagnose(string(phase))),
|
||||
toAge(pvc.GetCreationTimestamp()),
|
||||
AsStatus(p.diagnose(string(phase))),
|
||||
ToAge(pvc.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func (Role) Header(ns string) Header {
|
|||
func (r Role) Render(o interface{}, ns string, row *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Role, but got %T", o)
|
||||
return fmt.Errorf("expected Role, but got %T", o)
|
||||
}
|
||||
var ro rbacv1.Role
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ro)
|
||||
|
|
@ -53,7 +53,7 @@ func (r Role) Render(o interface{}, ns string, row *Row) error {
|
|||
ro.Name,
|
||||
mapToStr(ro.Labels),
|
||||
"",
|
||||
toAge(ro.GetCreationTimestamp()),
|
||||
ToAge(ro.GetCreationTimestamp()),
|
||||
)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (RoleBinding) Header(ns string) Header {
|
|||
func (r RoleBinding) Render(o interface{}, ns string, row *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected RoleBinding, but got %T", o)
|
||||
return fmt.Errorf("expected RoleBinding, but got %T", o)
|
||||
}
|
||||
var rb rbacv1.RoleBinding
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rb)
|
||||
|
|
@ -62,7 +62,7 @@ func (r RoleBinding) Render(o interface{}, ns string, row *Row) error {
|
|||
ss,
|
||||
mapToStr(rb.Labels),
|
||||
"",
|
||||
toAge(rb.GetCreationTimestamp()),
|
||||
ToAge(rb.GetCreationTimestamp()),
|
||||
)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -193,19 +193,16 @@ func (s RowSorter) Less(i, j int) bool {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// Less return true if c1 < c2.
|
||||
// Less return true if c1 <= c2.
|
||||
func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool {
|
||||
var less bool
|
||||
switch {
|
||||
case isNumber:
|
||||
v1, v2 = strings.Replace(v1, ",", "", -1), strings.Replace(v2, ",", "", -1)
|
||||
less = sortorder.NaturalLess(v1, v2)
|
||||
less = lessNumber(v1, v2)
|
||||
case isDuration:
|
||||
d1, d2 := durationToSeconds(v1), durationToSeconds(v2)
|
||||
less = d1 <= d2
|
||||
less = lessDuration(v1, v2)
|
||||
case isCapacity:
|
||||
c1, c2 := capacityToNumber(v1), capacityToNumber(v2)
|
||||
less = c1 <= c2
|
||||
less = lessCapacity(v1, v2)
|
||||
default:
|
||||
less = sortorder.NaturalLess(v1, v2)
|
||||
}
|
||||
|
|
@ -215,3 +212,20 @@ func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool {
|
|||
|
||||
return less
|
||||
}
|
||||
|
||||
func lessDuration(s1, s2 string) bool {
|
||||
d1, d2 := durationToSeconds(s1), durationToSeconds(s2)
|
||||
return d1 <= d2
|
||||
}
|
||||
|
||||
func lessCapacity(s1, s2 string) bool {
|
||||
c1, c2 := capacityToNumber(s1), capacityToNumber(s2)
|
||||
|
||||
return c1 <= c2
|
||||
}
|
||||
|
||||
func lessNumber(s1, s2 string) bool {
|
||||
v1, v2 := strings.Replace(s1, ",", "", -1), strings.Replace(s2, ",", "", -1)
|
||||
|
||||
return sortorder.NaturalLess(v1, v2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,50 +244,3 @@ func (r RowEventSorter) Less(i, j int) bool {
|
|||
|
||||
return !less
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// // IdSorter sorts row events by a given id.
|
||||
// type IdSorter struct {
|
||||
// Ids map[string]int
|
||||
// Events RowEvents
|
||||
// }
|
||||
|
||||
// func (s IdSorter) Len() int {
|
||||
// return len(s.Events)
|
||||
// }
|
||||
|
||||
// func (s IdSorter) Swap(i, j int) {
|
||||
// s.Events[i], s.Events[j] = s.Events[j], s.Events[i]
|
||||
// }
|
||||
|
||||
// func (s IdSorter) Less(i, j int) bool {
|
||||
// return s.Ids[s.Events[i].Row.ID] < s.Ids[s.Events[j].Row.ID]
|
||||
// }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// // StringSet represents a collection of unique strings.
|
||||
// type StringSet []string
|
||||
|
||||
// // Add adds a new item in the set.
|
||||
// func (ss StringSet) Add(item string) StringSet {
|
||||
// if ss.In(item) {
|
||||
// return ss
|
||||
// }
|
||||
// return append(ss, item)
|
||||
// }
|
||||
|
||||
// // In checks if a string is in the set.
|
||||
// func (ss StringSet) In(item string) bool {
|
||||
// return ss.indexOf(item) >= 0
|
||||
// }
|
||||
|
||||
// func (ss StringSet) indexOf(item string) int {
|
||||
// for i, s := range ss {
|
||||
// if s == item {
|
||||
// return i
|
||||
// }
|
||||
// }
|
||||
// return -1
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/tview"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -26,9 +27,10 @@ func (r ReplicaSet) ColorerFunc() ColorerFunc {
|
|||
|
||||
// Header returns a header row.
|
||||
func (ReplicaSet) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "DESIRED", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "CURRENT", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "READY", Align: tview.AlignRight},
|
||||
|
|
@ -36,13 +38,18 @@ func (ReplicaSet) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected ReplicaSet, but got %T", o)
|
||||
return fmt.Errorf("expected ReplicaSet, but got %T", o)
|
||||
}
|
||||
var rs appsv1.ReplicaSet
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rs)
|
||||
|
|
@ -54,12 +61,16 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error {
|
|||
row.Fields = Fields{
|
||||
rs.Namespace,
|
||||
rs.Name,
|
||||
computeVulScore(&rs.Spec.Template.Spec),
|
||||
strconv.Itoa(int(*rs.Spec.Replicas)),
|
||||
strconv.Itoa(int(rs.Status.Replicas)),
|
||||
strconv.Itoa(int(rs.Status.ReadyReplicas)),
|
||||
mapToStr(rs.Labels),
|
||||
asStatus(r.diagnose(rs)),
|
||||
toAge(rs.GetCreationTimestamp()),
|
||||
AsStatus(r.diagnose(rs)),
|
||||
ToAge(rs.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func (ServiceAccount) Header(ns string) Header {
|
|||
func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected ServiceAccount, but got %T", o)
|
||||
return fmt.Errorf("expected ServiceAccount, but got %T", o)
|
||||
}
|
||||
var sa v1.ServiceAccount
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sa)
|
||||
|
|
@ -49,7 +49,7 @@ func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error {
|
|||
strconv.Itoa(len(sa.Secrets)),
|
||||
mapToStr(sa.Labels),
|
||||
"",
|
||||
toAge(sa.GetCreationTimestamp()),
|
||||
ToAge(sa.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (StorageClass) Header(ns string) Header {
|
|||
func (s StorageClass) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected StorageClass, but got %T", o)
|
||||
return fmt.Errorf("expected StorageClass, but got %T", o)
|
||||
}
|
||||
var sc storagev1.StorageClass
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sc)
|
||||
|
|
@ -54,7 +54,7 @@ func (s StorageClass) Render(o interface{}, ns string, r *Row) error {
|
|||
boolPtrToStr(sc.AllowVolumeExpansion),
|
||||
mapToStr(sc.Labels),
|
||||
"",
|
||||
toAge(sc.GetCreationTimestamp()),
|
||||
ToAge(sc.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/derailed/tcell/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
// ScreenDump renders a screendumps to screen.
|
||||
|
|
@ -58,7 +59,7 @@ func (b ScreenDump) Render(o interface{}, ns string, r *Row) error {
|
|||
// Helpers...
|
||||
|
||||
func timeToAge(timestamp time.Time) string {
|
||||
return time.Since(timestamp).String()
|
||||
return duration.HumanDuration(time.Since(timestamp))
|
||||
}
|
||||
|
||||
// FileRes represents a file resource.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
@ -20,9 +21,10 @@ type StatefulSet struct {
|
|||
|
||||
// Header returns a header row.
|
||||
func (StatefulSet) Header(ns string) Header {
|
||||
return Header{
|
||||
h := Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "VS"},
|
||||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "SELECTOR", Wide: true},
|
||||
HeaderColumn{Name: "SERVICE"},
|
||||
|
|
@ -32,13 +34,18 @@ func (StatefulSet) Header(ns string) Header {
|
|||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true},
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
h = append(h[:vulIdx], h[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (s StatefulSet) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected StatefulSet, but got %T", o)
|
||||
return fmt.Errorf("expected StatefulSet, but got %T", o)
|
||||
}
|
||||
var sts appsv1.StatefulSet
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sts)
|
||||
|
|
@ -50,14 +57,18 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
sts.Namespace,
|
||||
sts.Name,
|
||||
computeVulScore(&sts.Spec.Template.Spec),
|
||||
strconv.Itoa(int(sts.Status.ReadyReplicas)) + "/" + strconv.Itoa(int(sts.Status.Replicas)),
|
||||
asSelector(sts.Spec.Selector),
|
||||
na(sts.Spec.ServiceName),
|
||||
podContainerNames(sts.Spec.Template.Spec, true),
|
||||
podImageNames(sts.Spec.Template.Spec, true),
|
||||
mapToStr(sts.Labels),
|
||||
asStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)),
|
||||
toAge(sts.GetCreationTimestamp()),
|
||||
AsStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)),
|
||||
ToAge(sts.GetCreationTimestamp()),
|
||||
}
|
||||
if vul.ImgScanner == nil {
|
||||
r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (Subject) Header(ns string) Header {
|
|||
func (s Subject) Render(o interface{}, ns string, r *Row) error {
|
||||
res, ok := o.(SubjectRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected SubjectRes, but got %T", s)
|
||||
return fmt.Errorf("expected SubjectRes, but got %T", s)
|
||||
}
|
||||
|
||||
r.ID = res.Name
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (Service) Header(ns string) Header {
|
|||
func (s Service) Render(o interface{}, ns string, r *Row) error {
|
||||
raw, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Service, but got %T", o)
|
||||
return fmt.Errorf("expected Service, but got %T", o)
|
||||
}
|
||||
var svc v1.Service
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &svc)
|
||||
|
|
@ -58,8 +58,8 @@ func (s Service) Render(o interface{}, ns string, r *Row) error {
|
|||
mapToStr(svc.Spec.Selector),
|
||||
ToPorts(svc.Spec.Ports),
|
||||
mapToStr(svc.Labels),
|
||||
asStatus(s.diagnose()),
|
||||
toAge(svc.GetCreationTimestamp()),
|
||||
AsStatus(s.diagnose()),
|
||||
ToAge(svc.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package ui
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -100,6 +99,7 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error
|
|||
select {
|
||||
case evt := <-w.Events:
|
||||
if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod {
|
||||
log.Debug().Msgf("Skin changed: %s", c.skinFile)
|
||||
s.QueueUpdateDraw(func() {
|
||||
c.RefreshStyles(c.Config.K9s.CurrentCluster)
|
||||
})
|
||||
|
|
@ -117,8 +117,12 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error
|
|||
}
|
||||
}()
|
||||
|
||||
log.Debug().Msgf("SkinWatcher watching `%s", c.skinFile)
|
||||
return w.Add(config.K9sHome())
|
||||
log.Debug().Msgf("SkinWatcher watching %q", config.K9sHome())
|
||||
if err := w.Add(config.K9sHome()); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug().Msgf("SkinWatcher watching %q", config.K9sSkinDir)
|
||||
return w.Add(config.K9sSkinDir)
|
||||
}
|
||||
|
||||
// BenchConfig location of the benchmarks configuration file.
|
||||
|
|
@ -130,21 +134,35 @@ func BenchConfig(context string) string {
|
|||
func (c *Configurator) RefreshStyles(context string) {
|
||||
c.BenchFile = BenchConfig(context)
|
||||
|
||||
clusterSkins := config.YamlExtension(filepath.Join(config.K9sHome(), fmt.Sprintf("%s_skin.yml", context)))
|
||||
if c.Styles == nil {
|
||||
c.Styles = config.NewStyles()
|
||||
} else {
|
||||
c.Styles.Reset()
|
||||
}
|
||||
if err := c.Styles.Load(clusterSkins); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn().Msgf("No context specific skin file found -- %s", clusterSkins)
|
||||
} else {
|
||||
log.Error().Msgf("Failed to parse context specific skin file -- %s. %s.", clusterSkins, err)
|
||||
|
||||
var skin string
|
||||
if c.Config != nil {
|
||||
cl, ok := c.Config.K9s.Clusters[context]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
skin = cl.Skin
|
||||
}
|
||||
|
||||
var (
|
||||
skinFile = filepath.Join(config.K9sSkinDir, skin+".yml")
|
||||
)
|
||||
if skin != "" {
|
||||
if err := c.Styles.Load(skinFile); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.K9sSkinDir)
|
||||
} else {
|
||||
log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err)
|
||||
}
|
||||
} else {
|
||||
c.updateStyles(skinFile)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.updateStyles(clusterSkins)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Styles.Load(config.K9sStylesFile); err != nil {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func ShowError(styles config.Dialog, pages *ui.Pages, msg string) {
|
|||
}
|
||||
|
||||
func cowTalk(says string) string {
|
||||
msg := fmt.Sprintf("< Ruroh? %s >", says)
|
||||
msg := fmt.Sprintf("< Ruroh? %s >", strings.TrimSuffix(says, "\n"))
|
||||
buff := make([]string, 0, len(cow)+3)
|
||||
buff = append(buff, msg)
|
||||
buff = append(buff, cow...)
|
||||
|
|
|
|||
|
|
@ -149,24 +149,31 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
switch evt.Key() {
|
||||
case tcell.KeyBackspace2, tcell.KeyBackspace, tcell.KeyDelete:
|
||||
p.model.Delete()
|
||||
|
||||
case tcell.KeyRune:
|
||||
p.model.Add(evt.Rune())
|
||||
|
||||
case tcell.KeyEscape:
|
||||
p.model.ClearText(true)
|
||||
p.model.SetActive(false)
|
||||
|
||||
case tcell.KeyEnter, tcell.KeyCtrlE:
|
||||
p.model.SetText(p.model.GetText(), "")
|
||||
p.model.SetActive(false)
|
||||
|
||||
case tcell.KeyCtrlW, tcell.KeyCtrlU:
|
||||
p.model.ClearText(true)
|
||||
|
||||
case tcell.KeyUp:
|
||||
if s, ok := m.NextSuggestion(); ok {
|
||||
p.suggest(p.model.GetText(), s)
|
||||
p.model.SetText(s, "")
|
||||
}
|
||||
|
||||
case tcell.KeyDown:
|
||||
if s, ok := m.PrevSuggestion(); ok {
|
||||
p.suggest(p.model.GetText(), s)
|
||||
p.model.SetText(s, "")
|
||||
}
|
||||
|
||||
case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF:
|
||||
if s, ok := m.CurrentSuggestion(); ok {
|
||||
p.model.SetText(p.model.GetText()+s, "")
|
||||
|
|
|
|||
|
|
@ -165,14 +165,12 @@ func rxFilter(q string, inverse bool, data *render.TableData) (*render.TableData
|
|||
RowEvents: make(render.RowEvents, 0, len(data.RowEvents)),
|
||||
Namespace: data.Namespace,
|
||||
}
|
||||
ageIndex := -1
|
||||
if data.Header.HasAge() {
|
||||
ageIndex = data.Header.IndexOf("AGE", true)
|
||||
}
|
||||
ageIndex := data.Header.IndexOf("AGE", true)
|
||||
|
||||
const spacer = " "
|
||||
for _, re := range data.RowEvents {
|
||||
ff := re.Row.Fields
|
||||
if ageIndex > 0 {
|
||||
if ageIndex >= 0 && ageIndex+1 <= len(ff) {
|
||||
ff = append(ff[0:ageIndex], ff[ageIndex+1:]...)
|
||||
}
|
||||
fields := strings.Join(ff, spacer)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
"github.com/derailed/k9s/internal/vul"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
|
|
@ -120,9 +121,28 @@ func (a *App) Init(version string, rate int) error {
|
|||
a.layout(ctx)
|
||||
a.initSignals()
|
||||
|
||||
if a.Config.K9s.EnableImageScan {
|
||||
a.initImgScanner(version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) stopImgScanner() {
|
||||
if vul.ImgScanner != nil {
|
||||
vul.ImgScanner.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) initImgScanner(version string) {
|
||||
defer func(t time.Time) {
|
||||
log.Debug().Msgf("Scanner init time %s", time.Since(t))
|
||||
}(time.Now())
|
||||
|
||||
vul.ImgScanner = vul.NewImageScanner()
|
||||
go vul.ImgScanner.Init("k9s", version)
|
||||
}
|
||||
|
||||
func (a *App) layout(ctx context.Context) {
|
||||
flash := ui.NewFlash(a.App)
|
||||
go flash.Watch(ctx, a.Flash().Channel())
|
||||
|
|
@ -492,6 +512,8 @@ func (a *App) BailOut() {
|
|||
if err := nukeK9sShell(a); err != nil {
|
||||
log.Error().Err(err).Msgf("nuking k9s shell pod")
|
||||
}
|
||||
|
||||
a.stopImgScanner()
|
||||
a.factory.Terminate()
|
||||
a.App.BailOut()
|
||||
}
|
||||
|
|
@ -686,8 +708,7 @@ func (a *App) gotoResource(cmd, path string, clearStack bool) {
|
|||
func (a *App) inject(c model.Component, clearStack bool) error {
|
||||
ctx := context.WithValue(context.Background(), internal.KeyApp, a)
|
||||
if err := c.Init(ctx); err != nil {
|
||||
log.Error().Err(err).Msgf("component init failed for %q", c.Name())
|
||||
//dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error())
|
||||
log.Error().Err(err).Msgf("Component init failed for %q", c.Name())
|
||||
return err
|
||||
}
|
||||
if clearStack {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) {
|
|||
return
|
||||
}
|
||||
|
||||
details := NewDetails(b.App(), "Results", fileToSubject(path), false).Update(data)
|
||||
details := NewDetails(b.App(), "Results", fileToSubject(path), contentYAML, false).Update(data)
|
||||
if err := app.inject(details, false); err != nil {
|
||||
app.Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (b *Browser) Init(ctx context.Context) error {
|
|||
}
|
||||
ns := client.CleanseNamespace(b.app.Config.ActiveNamespace())
|
||||
if dao.IsK8sMeta(b.meta) && b.app.ConOK() {
|
||||
if _, e := b.app.factory.CanForResource(ns, b.GVR().String(), client.MonitorAccess); e != nil {
|
||||
if _, e := b.app.factory.CanForResource(ns, b.GVR().String(), client.ListAccess); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
|
@ -262,7 +262,7 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
v := NewLiveView(b.app, "YAML", model.NewYAML(b.GVR(), path))
|
||||
v := NewLiveView(b.app, yamlAction, model.NewYAML(b.GVR(), path))
|
||||
if err := v.app.inject(v, false); err != nil {
|
||||
v.app.Flash().Err(err)
|
||||
}
|
||||
|
|
@ -367,33 +367,42 @@ func (b *Browser) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
b.Stop()
|
||||
defer b.Start()
|
||||
if err := editRes(b.app, b.GVR(), path); err != nil {
|
||||
b.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func editRes(app *App, gvr client.GVR, path string) error {
|
||||
if path == "" {
|
||||
return fmt.Errorf("nothing selected %q", path)
|
||||
}
|
||||
ns, n := client.Namespaced(path)
|
||||
if client.IsClusterScoped(ns) {
|
||||
ns = client.AllNamespaces
|
||||
}
|
||||
if b.GVR().String() == "v1/namespaces" {
|
||||
if gvr.String() == "v1/namespaces" {
|
||||
ns = n
|
||||
}
|
||||
if ok, err := b.app.Conn().CanI(ns, b.GVR().String(), []string{"patch"}); !ok || err != nil {
|
||||
b.App().Flash().Errf("Current user can't edit resource %s", b.GVR())
|
||||
return nil
|
||||
if ok, err := app.Conn().CanI(ns, gvr.String(), []string{"patch"}); !ok || err != nil {
|
||||
return fmt.Errorf("current user can't edit resource %s", gvr)
|
||||
}
|
||||
|
||||
b.Stop()
|
||||
defer b.Start()
|
||||
{
|
||||
args := make([]string, 0, 10)
|
||||
args = append(args, "edit")
|
||||
args = append(args, b.GVR().FQN(n))
|
||||
if ns != client.AllNamespaces {
|
||||
args = append(args, "-n", ns)
|
||||
}
|
||||
if err := runK(b.app, shellOpts{clear: true, args: args}); err != nil {
|
||||
b.app.Flash().Errf("Edit command failed: %s", err)
|
||||
}
|
||||
args := make([]string, 0, 10)
|
||||
args = append(args, "edit")
|
||||
args = append(args, gvr.FQN(n))
|
||||
if ns != client.AllNamespaces {
|
||||
args = append(args, "-n", ns)
|
||||
}
|
||||
if err := runK(app, shellOpts{clear: true, args: args}); err != nil {
|
||||
app.Flash().Errf("Edit command failed: %s", err)
|
||||
}
|
||||
|
||||
return evt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -404,7 +413,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
ns := b.namespaces[i]
|
||||
|
||||
auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), client.MonitorAccess)
|
||||
auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), client.ListAccess)
|
||||
if !auth {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("current user can't access namespace %s", ns)
|
||||
|
|
@ -488,7 +497,7 @@ func (b *Browser) refreshActions() {
|
|||
}
|
||||
|
||||
if !dao.IsK9sMeta(b.meta) {
|
||||
aa[ui.KeyY] = ui.NewKeyAction("YAML", b.viewCmd, true)
|
||||
aa[ui.KeyY] = ui.NewKeyAction(yamlAction, b.viewCmd, true)
|
||||
aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,21 +66,17 @@ func (c *Command) Reset(clear bool) error {
|
|||
}
|
||||
|
||||
func allowedXRay(gvr client.GVR) bool {
|
||||
gg := []string{
|
||||
"v1/pods",
|
||||
"v1/services",
|
||||
"apps/v1/deployments",
|
||||
"apps/v1/daemonsets",
|
||||
"apps/v1/statefulsets",
|
||||
"apps/v1/replicasets",
|
||||
}
|
||||
for _, g := range gg {
|
||||
if g == gvr.String() {
|
||||
return true
|
||||
}
|
||||
gg := map[string]struct{}{
|
||||
"v1/pods": {},
|
||||
"v1/services": {},
|
||||
"apps/v1/deployments": {},
|
||||
"apps/v1/daemonsets": {},
|
||||
"apps/v1/statefulsets": {},
|
||||
"apps/v1/replicasets": {},
|
||||
}
|
||||
|
||||
return false
|
||||
_, ok := gg[gvr.String()]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *Command) xrayCmd(cmd string) error {
|
||||
|
|
@ -273,10 +269,11 @@ func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) (
|
|||
return fmt.Errorf("no component found for %s", gvr)
|
||||
}
|
||||
c.app.Flash().Infof("Viewing %s...", client.NewGVR(gvr).R())
|
||||
command := cmd
|
||||
if tokens := strings.Split(cmd, " "); len(tokens) >= 2 {
|
||||
cmd = tokens[0]
|
||||
command = tokens[0]
|
||||
}
|
||||
c.app.Config.SetActiveView(cmd)
|
||||
c.app.Config.SetActiveView(command)
|
||||
if err := c.app.Config.Save(); err != nil {
|
||||
log.Error().Err(err).Msg("Config save failed!")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func cowTalk(says string, w int) string {
|
|||
msg := fmt.Sprintf("[red::]< [::b]Ruroh? %s[::-] >", says)
|
||||
buff := make([]string, 0, len(cow)+3)
|
||||
buff = append(buff, "[red::] "+strings.Repeat("─", len(says)+8))
|
||||
buff = append(buff, msg)
|
||||
buff = append(buff, strings.TrimSuffix(msg, "\n"))
|
||||
buff = append(buff, " "+strings.Repeat("─", len(says)+8))
|
||||
rCount := w/2 - 8
|
||||
if rCount < 0 {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type CronJob struct {
|
|||
|
||||
// NewCronJob returns a new viewer.
|
||||
func NewCronJob(gvr client.GVR) ResourceViewer {
|
||||
c := CronJob{ResourceViewer: NewBrowser(gvr)}
|
||||
c := CronJob{ResourceViewer: NewVulnerabilityExtender(NewBrowser(gvr))}
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
c.GetTable().SetEnterFn(c.showJobs)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@ import (
|
|||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
||||
const (
|
||||
detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
||||
contentTXT = "text"
|
||||
contentYAML = "yaml"
|
||||
)
|
||||
|
||||
// Details represents a generic text viewer.
|
||||
type Details struct {
|
||||
|
|
@ -32,20 +36,22 @@ type Details struct {
|
|||
currentRegion, maxRegions int
|
||||
searchable bool
|
||||
fullScreen bool
|
||||
contentType string
|
||||
}
|
||||
|
||||
// NewDetails returns a details viewer.
|
||||
func NewDetails(app *App, title, subject string, searchable bool) *Details {
|
||||
func NewDetails(app *App, title, subject, contentType string, searchable bool) *Details {
|
||||
d := Details{
|
||||
Flex: tview.NewFlex(),
|
||||
text: tview.NewTextView(),
|
||||
app: app,
|
||||
title: title,
|
||||
subject: subject,
|
||||
actions: make(ui.KeyActions),
|
||||
cmdBuff: model.NewFishBuff('/', model.FilterBuffer),
|
||||
model: model.NewText(),
|
||||
searchable: searchable,
|
||||
Flex: tview.NewFlex(),
|
||||
text: tview.NewTextView(),
|
||||
app: app,
|
||||
title: title,
|
||||
subject: subject,
|
||||
actions: make(ui.KeyActions),
|
||||
cmdBuff: model.NewFishBuff('/', model.FilterBuffer),
|
||||
model: model.NewText(),
|
||||
searchable: searchable,
|
||||
contentType: contentType,
|
||||
}
|
||||
d.AddItem(d.text, 0, 1, true)
|
||||
|
||||
|
|
@ -85,7 +91,12 @@ func (d *Details) InCmdMode() bool {
|
|||
|
||||
// TextChanged notifies the model changed.
|
||||
func (d *Details) TextChanged(lines []string) {
|
||||
d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n")))
|
||||
switch d.contentType {
|
||||
case contentYAML:
|
||||
d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n")))
|
||||
default:
|
||||
d.text.SetText(strings.Join(lines, "\n"))
|
||||
}
|
||||
d.text.ScrollToBeginning()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func (d *Dir) bindKeys(aa ui.KeyActions) {
|
|||
d.bindDangerousKeys(aa)
|
||||
}
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyY: ui.NewKeyAction("YAML", d.viewCmd, true),
|
||||
ui.KeyY: ui.NewKeyAction(yamlAction, d.viewCmd, true),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true),
|
||||
})
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ func (d *Dir) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(d.App(), "YAML", sel, true).Update(string(yaml))
|
||||
details := NewDetails(d.App(), yamlAction, sel, contentYAML, true).Update(string(yaml))
|
||||
if err := d.App().inject(details, false); err != nil {
|
||||
d.App().Flash().Err(err)
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
res = "message:\n" + fmtResults(res)
|
||||
}
|
||||
|
||||
details := NewDetails(d.App(), "Applied Manifest", sel, true).Update(res)
|
||||
details := NewDetails(d.App(), "Applied Manifest", sel, contentYAML, true).Update(res)
|
||||
if err := d.App().inject(details, false); err != nil {
|
||||
d.App().Flash().Err(err)
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ func (d *Dir) delCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
} else {
|
||||
res = "message:\n" + fmtResults(res)
|
||||
}
|
||||
details := NewDetails(d.App(), "Deleted Manifest", sel, true).Update(res)
|
||||
details := NewDetails(d.App(), "Deleted Manifest", sel, contentYAML, true).Update(res)
|
||||
if err := d.App().inject(details, false); err != nil {
|
||||
d.App().Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ type Deploy struct {
|
|||
func NewDeploy(gvr client.GVR) ResourceViewer {
|
||||
var d Deploy
|
||||
d.ResourceViewer = NewPortForwardExtender(
|
||||
NewRestartExtender(
|
||||
NewScaleExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), d.logOptions),
|
||||
NewVulnerabilityExtender(
|
||||
NewRestartExtender(
|
||||
NewScaleExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), d.logOptions),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -89,20 +91,24 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) {
|
|||
return &opts, nil
|
||||
}
|
||||
|
||||
func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, path string) {
|
||||
func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, fqn string) {
|
||||
var ddp dao.Deployment
|
||||
dp, err := ddp.GetInstance(app.factory, path)
|
||||
ddp.Init(d.App().factory, d.GVR())
|
||||
|
||||
dp, err := ddp.GetInstance(fqn)
|
||||
if err != nil {
|
||||
app.Flash().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
showPodsFromSelector(app, path, dp.Spec.Selector)
|
||||
showPodsFromSelector(app, fqn, dp.Spec.Selector)
|
||||
}
|
||||
|
||||
func (d *Deploy) dp(path string) (*appsv1.Deployment, error) {
|
||||
func (d *Deploy) dp(fqn string) (*appsv1.Deployment, error) {
|
||||
var dp dao.Deployment
|
||||
return dp.GetInstance(d.App().factory, path)
|
||||
dp.Init(d.App().factory, d.GVR())
|
||||
|
||||
return dp.GetInstance(fqn)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ type DaemonSet struct {
|
|||
func NewDaemonSet(gvr client.GVR) ResourceViewer {
|
||||
d := DaemonSet{
|
||||
ResourceViewer: NewPortForwardExtender(
|
||||
NewRestartExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), nil),
|
||||
NewVulnerabilityExtender(
|
||||
NewRestartExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), nil),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -249,19 +249,13 @@ func ssh(a *App, node string) error {
|
|||
if err := launchShellPod(a, node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cl := a.Config.K9s.ActiveCluster()
|
||||
if cl == nil {
|
||||
return fmt.Errorf("no active cluster detected")
|
||||
}
|
||||
ns := cl.ShellPod.Namespace
|
||||
ns := a.Config.K9s.ShellPod.Namespace
|
||||
|
||||
return sshIn(a, client.FQN(ns, k9sShellPodName()), k9sShell)
|
||||
}
|
||||
|
||||
func sshIn(a *App, fqn, co string) error {
|
||||
cl := a.Config.K9s.ActiveCluster()
|
||||
cfg := cl.ShellPod
|
||||
cfg := a.Config.K9s.ShellPod
|
||||
os, err := getPodOS(a.factory, fqn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os detect failed: %w", err)
|
||||
|
|
@ -295,8 +289,7 @@ func nukeK9sShell(a *App) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
cl := a.Config.K9s.ActiveCluster()
|
||||
ns := cl.ShellPod.Namespace
|
||||
ns := a.Config.K9s.ShellPod.Namespace
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -315,9 +308,11 @@ func nukeK9sShell(a *App) error {
|
|||
|
||||
func launchShellPod(a *App, node string) error {
|
||||
a.Flash().Infof("Launching node shell on %s...", node)
|
||||
cl := a.Config.K9s.ActiveCluster()
|
||||
ns := cl.ShellPod.Namespace
|
||||
spec := k9sShellPod(node, cl.ShellPod)
|
||||
|
||||
var (
|
||||
spo = a.Config.K9s.ShellPod
|
||||
spec = k9sShellPod(node, spo)
|
||||
)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -325,13 +320,13 @@ func launchShellPod(a *App, node string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := dial.CoreV1().Pods(ns)
|
||||
conn := dial.CoreV1().Pods(spo.Namespace)
|
||||
if _, err := conn.Create(ctx, spec, metav1.CreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < k9sShellRetryCount; i++ {
|
||||
o, err := a.factory.Get("v1/pods", client.FQN(ns, k9sShellPodName()), true, labels.Everything())
|
||||
o, err := a.factory.Get("v1/pods", client.FQN(spo.Namespace, k9sShellPodName()), true, labels.Everything())
|
||||
if err != nil {
|
||||
time.Sleep(k9sShellRetryDelay)
|
||||
continue
|
||||
|
|
@ -372,6 +367,7 @@ func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod {
|
|||
},
|
||||
Resources: asResource(cfg.Limits),
|
||||
Stdin: true,
|
||||
TTY: cfg.TTY,
|
||||
SecurityContext: &v1.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,41 +14,71 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Helm represents a helm chart view.
|
||||
type Helm struct {
|
||||
// HelmChart represents a helm chart view.
|
||||
type HelmChart struct {
|
||||
ResourceViewer
|
||||
|
||||
Values *model.Values
|
||||
}
|
||||
|
||||
// NewHelm returns a new alias view.
|
||||
func NewHelm(gvr client.GVR) ResourceViewer {
|
||||
c := Helm{
|
||||
func NewHelmChart(gvr client.GVR) ResourceViewer {
|
||||
c := HelmChart{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
c.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone))
|
||||
c.GetTable().SetSelectedStyle(tcell.StyleDefault.
|
||||
Foreground(tcell.ColorWhite).
|
||||
Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone))
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
c.GetTable().SetEnterFn(c.viewReleases)
|
||||
c.SetContextFn(c.chartContext)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Helm) chartContext(ctx context.Context) context.Context {
|
||||
func (c *HelmChart) chartContext(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *Helm) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
func (c *HelmChart) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(tcell.KeyCtrlS)
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyShiftN: ui.NewKeyAction("Sort Name", c.GetTable().SortColCmd(nameCol, true), false),
|
||||
ui.KeyR: ui.NewKeyAction("Releases", c.historyCmd, true),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(statusCol, true), false),
|
||||
ui.KeyShiftA: ui.NewKeyAction("Sort Age", c.GetTable().SortColCmd(ageCol, true), false),
|
||||
ui.KeyV: ui.NewKeyAction("Values", c.getValsCmd(), true),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Helm) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (c *HelmChart) viewReleases(app *App, model ui.Tabular, _, path string) {
|
||||
v := NewHistory(client.NewGVR("helm-history"))
|
||||
v.SetContextFn(c.helmContext)
|
||||
if err := app.inject(v, false); err != nil {
|
||||
app.Flash().Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HelmChart) historyCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := c.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
c.viewReleases(c.App(), c.GetTable().GetModel(), c.GVR().String(), path)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *HelmChart) helmContext(ctx context.Context) context.Context {
|
||||
path := c.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return ctx
|
||||
}
|
||||
ctx = context.WithValue(ctx, internal.KeyFQN, path)
|
||||
|
||||
return context.WithValue(ctx, internal.KeyPath, path)
|
||||
}
|
||||
|
||||
func (c *HelmChart) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := c.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
|
|
@ -66,7 +96,7 @@ func (c *Helm) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Helm) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (c *HelmChart) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
c.Values.ToggleValues()
|
||||
if err := c.Values.Refresh(c.defaultCtx()); err != nil {
|
||||
log.Error().Err(err).Msgf("helm refresh failed")
|
||||
|
|
@ -76,6 +106,6 @@ func (c *Helm) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Helm) defaultCtx() context.Context {
|
||||
func (c *HelmChart) defaultCtx() context.Context {
|
||||
return context.WithValue(context.Background(), internal.KeyFactory, c.App().factory)
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/render/helm"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
"github.com/derailed/tcell/v2"
|
||||
)
|
||||
|
||||
// History represents a helm History view.
|
||||
type History struct {
|
||||
ResourceViewer
|
||||
}
|
||||
|
||||
// NewHelm returns a new alias view.
|
||||
func NewHistory(gvr client.GVR) ResourceViewer {
|
||||
h := History{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
h.GetTable().SetColorerFn(helm.History{}.ColorerFunc())
|
||||
h.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
h.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone))
|
||||
h.AddBindKeysFn(h.bindKeys)
|
||||
h.SetContextFn(h.HistoryContext)
|
||||
|
||||
return &h
|
||||
}
|
||||
|
||||
// Init initializes the vie
|
||||
func (h *History) Init(ctx context.Context) error {
|
||||
if err := h.ResourceViewer.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
h.GetTable().SetSortCol("REVISION", false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *History) HistoryContext(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (h *History) bindKeys(aa ui.KeyActions) {
|
||||
if !h.App().Config.K9s.IsReadOnly() {
|
||||
h.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace, tcell.KeyCtrlD)
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyShiftN: ui.NewKeyAction("Sort Revision", h.GetTable().SortColCmd("REVISION", true), false),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Status", h.GetTable().SortColCmd("STATUS", true), false),
|
||||
ui.KeyShiftA: ui.NewKeyAction("Sort Age", h.GetTable().SortColCmd("AGE", true), false),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *History) bindDangerousKeys(aa ui.KeyActions) {
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyR: ui.NewKeyAction("RollBackTo...", h.rollbackCmd, true),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *History) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := h.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, nrev := client.Namespaced(path)
|
||||
tt := strings.Split(nrev, ":")
|
||||
n, rev := nrev, ""
|
||||
if len(tt) == 2 {
|
||||
n, rev = tt[0], tt[1]
|
||||
}
|
||||
|
||||
h.Stop()
|
||||
defer h.Start()
|
||||
msg := fmt.Sprintf("RollingBack chart [yellow::b]%s[-::-] to release <[orangered::b]%s[-::-]>?", n, rev)
|
||||
dialog.ShowConfirmAck(h.App().App, h.App().Content.Pages, n, false, "Confirm Rollback", msg, func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), h.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
if err := h.rollback(ctx, client.FQN(ns, n), rev); err != nil {
|
||||
h.App().Flash().Err(err)
|
||||
} else {
|
||||
h.App().Flash().Infof("Rollout restart in progress for char `%s...", n)
|
||||
}
|
||||
}, func() {})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *History) rollback(ctx context.Context, path, rev string) error {
|
||||
var hm dao.HelmHistory
|
||||
hm.Init(h.App().factory, h.GVR())
|
||||
if err := hm.Rollback(ctx, path, rev); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -26,9 +26,13 @@ func clipboardWrite(text string) error {
|
|||
return clipboard.WriteAll(text)
|
||||
}
|
||||
|
||||
func sanitizeEsc(s string) string {
|
||||
return strings.ReplaceAll(s, "[]", "]")
|
||||
}
|
||||
|
||||
func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if err := clipboardWrite(v.GetText(true)); err != nil {
|
||||
if err := clipboardWrite(sanitizeEsc(v.GetText(true))); err != nil {
|
||||
flash.Err(err)
|
||||
return evt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Authors of K9s
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tcell/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
imgScanTitle = "Scans"
|
||||
browseOSX = "open"
|
||||
browseLinux = "sensible-browser"
|
||||
cveGovURL = "https://nvd.nist.gov/vuln/detail/"
|
||||
ghsaURL = "https://github.com/advisories/"
|
||||
)
|
||||
|
||||
// ImageScan represents an image vulnerability scan view.
|
||||
type ImageScan struct {
|
||||
ResourceViewer
|
||||
}
|
||||
|
||||
// NewImageScan returns a new scans view.
|
||||
func NewImageScan(gvr client.GVR) ResourceViewer {
|
||||
v := ImageScan{}
|
||||
v.ResourceViewer = NewBrowser(gvr)
|
||||
v.AddBindKeysFn(v.bindKeys)
|
||||
v.GetTable().SetEnterFn(v.viewCVE)
|
||||
v.GetTable().SetSortCol("SEVERITY", true)
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
// Name returns the component name.
|
||||
func (s *ImageScan) Name() string { return imgScanTitle }
|
||||
|
||||
func (c *ImageScan) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlZ, tcell.KeyCtrlW)
|
||||
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyShiftL: ui.NewKeyAction("Sort Lib", c.GetTable().SortColCmd("LIBRARY", false), true),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Severity", c.GetTable().SortColCmd("SEVERITY", false), true),
|
||||
ui.KeyShiftF: ui.NewKeyAction("Sort Fixed-in", c.GetTable().SortColCmd("FIXED-IN", false), true),
|
||||
ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerability", c.GetTable().SortColCmd("VULNERABILITY", false), true),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ImageScan) viewCVE(app *App, model ui.Tabular, gvr, path string) {
|
||||
bin := browseLinux
|
||||
if runtime.GOOS == "darwin" {
|
||||
bin = browseOSX
|
||||
}
|
||||
|
||||
tt := strings.Split(path, "|")
|
||||
if len(tt) < 7 {
|
||||
app.Flash().Errf("parse path failed: %s", path)
|
||||
}
|
||||
cve := tt[render.CVEParseIdx]
|
||||
site := cveGovURL
|
||||
if strings.Index(cve, "GHSA") == 0 {
|
||||
site = ghsaURL
|
||||
}
|
||||
site += cve
|
||||
|
||||
ok, errChan := run(app, shellOpts{
|
||||
background: true,
|
||||
binary: bin,
|
||||
args: []string{site},
|
||||
})
|
||||
if !ok {
|
||||
app.Flash().Errf("unable to run browser command")
|
||||
return
|
||||
}
|
||||
for e := range errChan {
|
||||
if e != nil {
|
||||
app.Flash().Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ type Job struct {
|
|||
|
||||
// NewJob returns a new viewer.
|
||||
func NewJob(gvr client.GVR) ResourceViewer {
|
||||
j := Job{ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil)}
|
||||
j := Job{ResourceViewer: NewVulnerabilityExtender(NewLogsExtender(NewBrowser(gvr), nil))}
|
||||
j.GetTable().SetEnterFn(j.showPods)
|
||||
j.GetTable().SetSortCol("AGE", true)
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue