Merge branch 'master' into dont-allow-same-port-twice
commit
22ff38ef0a
|
|
@ -1,6 +1,5 @@
|
|||
.vscode
|
||||
.idea
|
||||
k9s.log
|
||||
.envrc
|
||||
cov.out
|
||||
execs
|
||||
|
|
@ -10,10 +9,9 @@ dist
|
|||
notes
|
||||
vendor
|
||||
go.mod1
|
||||
popeye1.go
|
||||
gen.sh
|
||||
cluster_info_test.go
|
||||
*.test
|
||||
*.log
|
||||
*~
|
||||
pod1.go
|
||||
faas
|
||||
demos
|
||||
|
|
@ -15,23 +15,23 @@ builds:
|
|||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- arm
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
ldflags:
|
||||
- -s -w -X github.com/derailed/k9s/cmd.version={{.Version}} -X github.com/derailed/k9s/cmd.commit={{.Commit}} -X github.com/derailed/k9s/cmd.date={{.Date}}
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
bit: Arm
|
||||
bitv6: Arm6
|
||||
bitv7: Arm7
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
archives:
|
||||
- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
bit: Arm
|
||||
bitv6: Arm6
|
||||
bitv7: Arm7
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
snapshot:
|
||||
|
|
@ -43,7 +43,7 @@ changelog:
|
|||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
# Homebrew
|
||||
# Homebrews
|
||||
brews:
|
||||
- name: k9s
|
||||
github:
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -2,7 +2,7 @@ NAME := k9s
|
|||
PACKAGE := github.com/derailed/$(NAME)
|
||||
GIT := $(shell git rev-parse --short HEAD)
|
||||
DATE := $(shell date +%FT%T%Z)
|
||||
VERSION := v0.12.0
|
||||
VERSION ?= v0.14.0
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
158
README.md
158
README.md
|
|
@ -31,11 +31,11 @@ Wanna discuss K9s features with your fellow `K9sers` or simply show your support
|
|||
|
||||
## Installation
|
||||
|
||||
K9s is available on Linux, OSX and Windows platforms.
|
||||
K9s is available on Linux, macOS and Windows platforms.
|
||||
|
||||
* Binaries for Linux, Windows and Mac are available as tarballs in the [release](https://github.com/derailed/k9s/releases) page.
|
||||
|
||||
* Via Homebrew or LinuxBrew for OSX and Linux
|
||||
* Via Homebrew or LinuxBrew for macOS and Linux
|
||||
|
||||
```shell
|
||||
brew install derailed/k9s/k9s
|
||||
|
|
@ -47,12 +47,16 @@ K9s is available on Linux, OSX and Windows platforms.
|
|||
sudo port install k9s
|
||||
```
|
||||
|
||||
* Archlinux (AUR)
|
||||
|
||||
K9s is available in the Arch User Repository under the name [k9s-bin](https://aur.archlinux.org/packages/k9s-bin/), you can install it with your favorite AUR helper like so:
|
||||
* On Arch Linux
|
||||
|
||||
```shell
|
||||
yay -S k9s-bin
|
||||
pacman -S k9s
|
||||
```
|
||||
|
||||
* Via [Scoop](https://scoop.sh) for Windows
|
||||
|
||||
```shell
|
||||
scoop install k9s
|
||||
```
|
||||
|
||||
* Building from source
|
||||
|
|
@ -95,8 +99,10 @@ K9s is available on Linux, OSX and Windows platforms.
|
|||
|
||||
---
|
||||
|
||||
## Demo Video
|
||||
## Demo Videos/Recordings
|
||||
|
||||
* [K9s Pulses](https://asciinema.org/a/UbXKPal6IWpTaVAjBBFmizcGN)
|
||||
* [K9s v0.15.1](https://youtu.be/7Fx4XQ2ftpM)
|
||||
* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s)
|
||||
* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I)
|
||||
* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8)
|
||||
|
|
@ -136,21 +142,21 @@ K9s uses aliases to navigate most K8s resources.
|
|||
| `:`ns`<ENTER>` | To view and switch to another Kubernetes namespace | `:`+`ns`+`<ENTER>` |
|
||||
| `:screendump`, `:sd` | To view all saved resources | |
|
||||
| `Ctrl-d` | To delete a resource (TAB and ENTER to confirm) | |
|
||||
| `Ctrl-k` | To delete a resource (no confirmation dialog) | |
|
||||
| `Ctrl-k` | To kill a resource (no confirmation dialog!) | |
|
||||
| `:q`, `Ctrl-c` | To bail out of K9s | |
|
||||
|
||||
---
|
||||
|
||||
## K9s config file ($HOME/.k9s/config.yml)
|
||||
## K9s Configuration
|
||||
|
||||
K9s keeps its configurations in a .k9s directory in your home directory.
|
||||
K9s keeps its configurations in a .k9s directory in your home directory `$HOME/.k9s/config.yml`.
|
||||
|
||||
> NOTE: This is still in flux and will change while in pre-release stage!
|
||||
|
||||
```yaml
|
||||
# config.yml
|
||||
k9s:
|
||||
# Indicates api-server poll intervals.
|
||||
# Represents ui poll intervals.
|
||||
refreshRate: 2
|
||||
# Indicates whether modification commands like delete/kill/edit are disabled. Default is false
|
||||
readOnly: false
|
||||
|
|
@ -185,9 +191,9 @@ K9s uses aliases to navigate most K8s resources.
|
|||
|
||||
---
|
||||
|
||||
## Aliases
|
||||
## Command Aliases
|
||||
|
||||
In K9s, you can define your own command aliases (shortnames) to access your resources. In your `$HOME/.k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file:
|
||||
In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file:
|
||||
|
||||
```yaml
|
||||
# $HOME/.k9s/alias.yml
|
||||
|
|
@ -200,9 +206,44 @@ Using this alias file, you can now type pp/crb to list pods or clusterrolebindin
|
|||
|
||||
---
|
||||
|
||||
## HotKey Support
|
||||
|
||||
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps:
|
||||
|
||||
1. Create a file named `$HOME/.k9s/hotkey.yml`
|
||||
2. Add the following to your `hotkey.yml`. You can use resource name/short name to specify a command ie same as typing it while in command mode.
|
||||
|
||||
```yaml
|
||||
# $HOME/.k9s/hotkey.yml
|
||||
hotKey:
|
||||
# Hitting Shift-0 navigates to your pod view
|
||||
shift-0:
|
||||
shortCut: Shift-0
|
||||
description: Viewing pods
|
||||
command: pods
|
||||
# Hitting Shift-1 navigates to your deployments
|
||||
shift-1:
|
||||
shortCut: Shift-1
|
||||
description: View deployments
|
||||
command: dp
|
||||
# Hitting Shift-2 navigates to your xray deployments
|
||||
shift-2:
|
||||
shortCut: Shift-2
|
||||
description: Xray Deployments
|
||||
command: xray deploy
|
||||
```
|
||||
|
||||
Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them.
|
||||
|
||||
You can choose any keyboard shotcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
|
||||
|
||||
> NOTE: This feature/configuration might change in future releases!
|
||||
|
||||
---
|
||||
|
||||
## Plugins
|
||||
|
||||
K9s allows you to define your own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate available plugins. A plugin is defined as follows:
|
||||
K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate all available plugins. A plugin is defined as follows:
|
||||
|
||||
```yaml
|
||||
# $HOME/.k9s/plugin.yml
|
||||
|
|
@ -213,7 +254,7 @@ plugin:
|
|||
description: Pod logs
|
||||
scopes:
|
||||
- po
|
||||
command: /usr/local/bin/kubectl
|
||||
command: kubectl
|
||||
background: false
|
||||
args:
|
||||
- logs
|
||||
|
|
@ -233,6 +274,8 @@ K9s does provide additional environment variables for you to customize your plug
|
|||
|
||||
* `$NAMESPACE` -- the selected resource namespace
|
||||
* `$NAME` -- the selected resource name
|
||||
* `$CONTAINER` -- the current container if applicable
|
||||
* `$FILTER` -- the current filter if any
|
||||
* `$KUBECONFIG` -- the KubeConfig location.
|
||||
* `$CLUSTER` the active cluster name
|
||||
* `$CONTEXT` the active context name
|
||||
|
|
@ -240,13 +283,13 @@ K9s does provide additional environment variables for you to customize your plug
|
|||
* `$GROUPS` the active groups
|
||||
* `$COLX` the column at index X for the viewed resource
|
||||
|
||||
NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies.
|
||||
> NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies.
|
||||
|
||||
---
|
||||
|
||||
## Benchmarking
|
||||
## Benchmark Your Applications
|
||||
|
||||
K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll) of Google fame. Hey is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!).
|
||||
K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll). `Hey` is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!).
|
||||
|
||||
To setup a port-forward, you will need to navigate to the PodView, select a pod and a container that exposes a given port. Using `SHIFT-F` a dialog comes up to allow you to specify a local port to forward. Once acknowledged, you can navigate to the PortForward view (alias `pf`) listing out your active port-forwards. Selecting a port-forward and using `CTRL-B` will run a benchmark on that HTTP endpoint. To view the results of your benchmark runs, go to the Benchmarks view (alias `be`). You should now be able to select a benchmark and view the run stats details by pressing `<ENTER>`. NOTE: Port-forwards only last for the duration of the K9s session and will be terminated upon exit.
|
||||
|
||||
|
|
@ -268,11 +311,11 @@ benchmarks:
|
|||
defaults:
|
||||
# One concurrent connection
|
||||
concurrency: 1
|
||||
# 500 requests will be sent to an endpoint
|
||||
# Number of requests that will be sent to an endpoint
|
||||
requests: 500
|
||||
containers:
|
||||
# Containers section allows you to configure your http container's endpoints and benchmarking settings.
|
||||
# NOTE: the container ID syntax uses namespace/pod_name:container_name
|
||||
# NOTE: the container ID syntax uses namespace/pod-name:container-name
|
||||
default/nginx:nginx:
|
||||
# Benchmark a container named nginx using POST HTTP verb using http://localhost:port/bozo URL and headers.
|
||||
concurrency: 1
|
||||
|
|
@ -291,15 +334,15 @@ benchmarks:
|
|||
# Similary you can Benchmark an HTTP service exposed either via nodeport, loadbalancer types.
|
||||
# Service ID is ns/svc-name
|
||||
default/nginx:
|
||||
# Hit the service with 5 concurrent sessions
|
||||
# Set the concurrency level
|
||||
concurrency: 5
|
||||
# Issues a total of 500 requests
|
||||
# Number of requests to be sent
|
||||
requests: 500
|
||||
http:
|
||||
method: GET
|
||||
# This setting will depend on whether service is nodeport or loadbalancer. Nodeport may require vendor port tuneling setting.
|
||||
# Set this to a node if nodeport or LB if applicable. IP or dns name.
|
||||
host: 10.11.13.14
|
||||
host: A.B.C.D
|
||||
path: /bumblebeetuna
|
||||
auth:
|
||||
user: jean-baptiste-emmanuel
|
||||
|
|
@ -308,41 +351,6 @@ benchmarks:
|
|||
|
||||
---
|
||||
|
||||
## HotKeys
|
||||
|
||||
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps:
|
||||
|
||||
1. In your .k9s home directory create a file named `hotkey.yml`
|
||||
2. Add the following to your `hotkey.yml`. You can use short names or resource name to specify a command ie same as typing it in command mode.
|
||||
|
||||
```yaml
|
||||
hotKey:
|
||||
shift-0:
|
||||
shortCut: Shift-0
|
||||
description: View pods
|
||||
command: pods
|
||||
shift-1:
|
||||
shortCut: Shift-1
|
||||
description: View deployments
|
||||
command: dp
|
||||
shift-2:
|
||||
shortCut: Shift-2
|
||||
description: View services
|
||||
command: service
|
||||
shift-3:
|
||||
shortCut: Shift-3
|
||||
description: View statefulsets
|
||||
command: sts
|
||||
```
|
||||
|
||||
Not feeling so hot? Your custom hotkeys list will be listed in the help view.`<?>`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them.
|
||||
|
||||
You can choose any keyboard shotcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
|
||||
|
||||
NOTE: This feature/configuration might change in future releases!
|
||||
|
||||
---
|
||||
|
||||
## K9s RBAC FU
|
||||
|
||||
On RBAC enabled clusters, you would need to give your users/groups capabilities so that they can use K9s to explore their Kubernetes cluster. K9s needs minimally read privileges at both the cluster and namespace level to display resources and metrics.
|
||||
|
|
@ -450,18 +458,18 @@ You can style K9s based on your own sense of look and style. Skins are YAML file
|
|||
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 `$HOME/.k9s/mycluster_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`.
|
||||
|
||||
Colors can be defined by name or uing an hex representation.
|
||||
Colors can be defined by name or uing an 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!
|
||||
|
||||
```yaml
|
||||
# InTheNavy Skin...
|
||||
# Skin InTheNavy...
|
||||
k9s:
|
||||
# General K9s styles
|
||||
body:
|
||||
fgColor: dodgerblue
|
||||
bgColor: #ffffff
|
||||
logoColor: #0000ff
|
||||
bgColor: '#ffffff'
|
||||
logoColor: '#0000ff'
|
||||
# ClusterInfoView styles.
|
||||
info:
|
||||
fgColor: lightskyblue
|
||||
|
|
@ -484,7 +492,7 @@ k9s:
|
|||
activeColor: skyblue
|
||||
# Resource status and update styles
|
||||
status:
|
||||
newColor: #00ff00
|
||||
newColor: '#00ff00'
|
||||
modifyColor: powderblue
|
||||
addColor: lightskyblue
|
||||
errorColor: indianred
|
||||
|
|
@ -498,17 +506,17 @@ k9s:
|
|||
highlightColor: skyblue
|
||||
counterColor: slateblue
|
||||
filterColor: slategray
|
||||
# TableView attributes.
|
||||
table:
|
||||
fgColor: blue
|
||||
bgColor: darkblue
|
||||
cursorColor: aqua
|
||||
# Header row styles.
|
||||
header:
|
||||
fgColor: white
|
||||
bgColor: darkblue
|
||||
sorterColor: orange
|
||||
views:
|
||||
# TableView attributes.
|
||||
table:
|
||||
fgColor: blue
|
||||
bgColor: darkblue
|
||||
cursorColor: aqua
|
||||
# Header row styles.
|
||||
header:
|
||||
fgColor: white
|
||||
bgColor: darkblue
|
||||
sorterColor: orange
|
||||
# YAML info styles.
|
||||
yaml:
|
||||
keyColor: steelblue
|
||||
|
|
@ -585,8 +593,8 @@ to make this project a reality!
|
|||
## Meet The Core Team!
|
||||
|
||||
* [Fernand Galiana](https://github.com/derailed)
|
||||
* <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io
|
||||
* <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
|
||||
* <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io
|
||||
* <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 226 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 731 KiB |
|
|
@ -0,0 +1,67 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.14.0
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
## Happy Birthday K9s!!
|
||||
|
||||
🎉🥳🎊 Doh! Almost missed it... 🎉🥳🎊
|
||||
|
||||
Yes sir, it's been a year (already...) since K9s was first launched 🎉. I can't tell you what a year this has been 🙀. Difficult? sure. However, you guys are making this project a total gas, by your candor, kindness and for giving back via your creative issues, prs, sponsorships, slack channel help to name a few... I do think, you've all been all too quiet tho 🐭... So if K9s helps make your K8s life bett'a on a day to day basis, please reach out for your shoe-phones and dial up [@kitesurfer](https://twitter.com/kitesurfer) or write an article/blog and share it! Lastly I am so humbled by this... but we're closing on 5k stars/136k downloads in this repo, so please invite 28 of your closest friends soon...
|
||||
|
||||
Major Thanks to all of you for you patience and for making this project a reality to all our K8s friends! You're all redefining awesomeness!!
|
||||
|
||||
Also I'd like to take this opportunity to recognize and thank a few folks that have willingly volunteered their own time to track down issues and help improve K9s for all of us!!
|
||||
|
||||
* [Gustavo Silva Paiva](https://github.com/paivagustavo)
|
||||
* [Joscha Alisch](https://github.com/joscha-alisch)
|
||||
* [Michael Christina](https://github.com/mcristina422)
|
||||
* [Bruno Meneguello](https://github.com/bkmeneguello)
|
||||
* [Tuomo Syvänperä](https://github.com/syvanpera)
|
||||
* [Oskar F](https://github.com/fridokus)
|
||||
* [Bruno Ohms](https://github.com/brunohms)
|
||||
* [IgorRamalho](https://github.com/IgorRamalho)
|
||||
* [Benjamin](https://github.com/binarycoded)
|
||||
* [Norbert Csibra](https://github.com/ncsibra)
|
||||
* [Andrew Roth](https://github.com/RothAndrew)
|
||||
* [Sgandon](https://github.com/sgandon)
|
||||
* [Chris Werner Rau](https://github.com/cwrau)
|
||||
* [Eldad Assis](https://github.com/eldada)
|
||||
* [Tobias](https://github.com/mycrEEpy)
|
||||
* [Helge Sychla](https://github.com/hsychla)
|
||||
* [Markusi75](https://github.com/Makusi75)
|
||||
* [Swe-Covis](https://github.com/swe-covis)
|
||||
* [Evgeniy Shubin](https://github.com/com30n)
|
||||
|
||||
## Search Enabled For Describe/YAML views
|
||||
|
||||
In this drop we made the Describe/YAML views searchable. So you no longer need to plow thru your resource configurations and get directly to the just of it by using the search command ie `/elvis` + `enter`. You can use the familiar keys `n` and `N` to nav back and forth to the next occurrence in a circular buffer fashion once you've reached the BOF/EOF. It's the little things in life...
|
||||
|
||||
## And On Another Note...
|
||||
|
||||
More bugz...😿
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
* [Issue #536](https://github.com/derailed/k9s/issues/536)
|
||||
* [Issue #526](https://github.com/derailed/k9s/issues/526)
|
||||
* [Issue #464](https://github.com/derailed/k9s/issues/464)
|
||||
|
||||
* [PR #532](https://github.com/derailed/k9s/pull/532) Thank you!! [Joscha Alisch](https://github.com/joscha-alisch)
|
||||
* [PR #525](https://github.com/derailed/k9s/pull/525) Big Thanks!! [darklore](https://github.com/darklore)
|
||||
* [PR #524](https://github.com/derailed/k9s/pull/524) Thank you (Again)!! [Joscha Alisch](https://github.com/joscha-alisch)
|
||||
* [PR #514](https://github.com/derailed/k9s/pull/514) ATTA Boy!! [Alexander F. Rødseth](https://github.com/xyproto)
|
||||
* [PR #483](https://github.com/derailed/k9s/pull/483) Thank you!! [Paul Varache](https://github.com/paulvarache)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.14.1
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
## Term Color Part Duh!
|
||||
|
||||
Some folks had reported issues with skins and wanting to preserve their terminal background colors while in K9s. In this drop, we're introducing a new skin setting called `default` that should enable the skin to keep the original terminal background color. Here is a sample skin snippet that should achieve just that:
|
||||
|
||||
```yaml
|
||||
# .k9s/pale_rider.yml
|
||||
|
||||
# Styles...
|
||||
fg: &fg "#ff00ff"
|
||||
bg: &bg "default" # default keeps your current terminal window background color.
|
||||
|
||||
# Skin...
|
||||
k9s:
|
||||
body:
|
||||
fgColor: *fg
|
||||
bgColor: "default"
|
||||
#...
|
||||
```
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
* [Issue #539](https://github.com/derailed/k9s/issues/539)
|
||||
* [Issue #538](https://github.com/derailed/k9s/issues/538) Fingers crossed!
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.15.0
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_fez.png" align="center" width="400" height="auto"/>
|
||||
|
||||
## Seen This Fez Before?
|
||||
|
||||
The awesome and ever so smart and creative [Alex Ellis](https://github.com/alexellis) of [OpenFaas Fame](https://www.openfaas.com) fame, had pinged me when I had launched K9s to add support for OpenFaas functions. It's been a long time coming indeed, but we now have a very (VERY!) primitive integration with this very cool framework.
|
||||
|
||||
The current approach is to enable a few environment variables to tell K9s that you have an OpenFaas cluster available namely:
|
||||
|
||||
```shell
|
||||
OPENFAAS_GATEWAY=http://YOUR_CLUSTER_IP:31112
|
||||
OPENFAAS_TLS_INSECURE=false
|
||||
OPENFAAS_JWT_TOKEN=YOUR_TOKEN
|
||||
```
|
||||
|
||||
These will tell K9s that an OpenFaas gateway is available and exposed on a given nodeport.
|
||||
|
||||
Next you can navigate to your OpenFaas function view by entering command mode `:openfaas` or using aliases `:ofaas` or `ofa`
|
||||
|
||||
If functions are present in the given namespace they will be displayed here just like any other K8s resources.
|
||||
|
||||
The following operations are currently supported:
|
||||
|
||||
* Describe and YAML to view function definitions (Note: currently yields same results!)
|
||||
* Enter to view all pods instances associated with the selected function
|
||||
* Delete a function
|
||||
* Editing, shelling, logs, etc... are all supported by navigating to the underlying pods
|
||||
|
||||
Keep in mind, the paint is way fresh here and this feature could be a complete dud, but figure will give it a rinse on this drop and Alex can pipe in and helps us ironing this out.
|
||||
|
||||
> NOTE! It's been a while since I've played with OpenFaas so if some of you are more versed in this space by all means please do land a hand so we can make this feature more awesome!
|
||||
|
||||
## Moving Forward!
|
||||
|
||||
A few folks had mentioned the eagerness to port-forward directly from a pod or a service. Well now you can! Port Forwarding is now available on both the pod view and services view. Note! at the end of the day, you are still port-fowarding to a container! So the port-forward dialog is a bit different for these views as there might be several container ports available now when looking at this from a pod perspective. So the first field in the dialog is a combo-box that allows one to pick their desired ports. The rest of the dialog works the same as the container port-forward dialog.
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
* [Issue #546](https://github.com/derailed/k9s/issues/546) BREAKING NEWS! Use `t` vs `ctrl-h` now to toggle the K9s header
|
||||
* [Issue #541](https://github.com/derailed/k9s/issues/541)
|
||||
* [Issue #227](https://github.com/derailed/k9s/issues/227)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.15.1
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_fez.png" align="center" width="400" height="auto"/>
|
||||
|
||||
## OpenFeZ Reloaded?
|
||||
|
||||
🙀With feelings and one less bugZ!
|
||||
|
||||
The awesome and ever so smart and creative [Alex Ellis](https://github.com/alexellis) of [OpenFaas Fame](https://www.openfaas.com) fame, had pinged me when I had launched K9s to add support for OpenFaas functions. It's been a long time coming indeed, but we now have a very (VERY!) primitive integration with this very cool framework.
|
||||
|
||||
The current approach is to enable a few environment variables to tell K9s that you have an OpenFaas cluster available namely:
|
||||
|
||||
```shell
|
||||
OPENFAAS_GATEWAY=http://YOUR_CLUSTER_IP:31112
|
||||
OPENFAAS_TLS_INSECURE=false
|
||||
OPENFAAS_JWT_TOKEN=YOUR_TOKEN
|
||||
```
|
||||
|
||||
These will tell K9s that an OpenFaas gateway is available and exposed on a given nodeport.
|
||||
|
||||
Next you can navigate to your OpenFaas function view by entering command mode `:openfaas` or using aliases `:ofaas` or `ofa`
|
||||
|
||||
If functions are present in the given namespace they will be displayed here just like any other K8s resources.
|
||||
|
||||
The following operations are currently supported:
|
||||
|
||||
* Describe and YAML to view function definitions (Note: currently yields same results!)
|
||||
* Enter to view all pods instances associated with the selected function
|
||||
* Delete a function
|
||||
* Editing, shelling, logs, etc... are all supported by navigating to the underlying pods
|
||||
|
||||
Keep in mind, the paint is way fresh here and this feature could be a complete dud, but figure will give it a rinse on this drop and Alex can pipe in and helps us ironing this out.
|
||||
|
||||
> NOTE! It's been a while since I've played with OpenFaas so if some of you are more versed in this space by all means please do land a hand so we can make this feature more awesome!
|
||||
|
||||
## Moving Forward!
|
||||
|
||||
A few folks had mentioned the eagerness to port-forward directly from a pod or a service. Well now you can! Port Forwarding is now available on both the pod view and services view. Note! at the end of the day, you are still port-fowarding to a container! So the port-forward dialog is a bit different for these views as there might be several container ports available now when looking at this from a pod perspective. So the first field in the dialog is a combo-box that allows one to pick their desired ports. The rest of the dialog works the same as the container port-forward dialog.
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.15.2
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
## Mo PortForwards...
|
||||
|
||||
While putting together the [OpenFeeZ video](https://youtu.be/7Fx4XQ2ftpM), I've noticed a few issues with port-forwards and benchmarks. While I was doing surgery on that carp, figured why not go pull a full monty on port-forwards and enable for other controller like resources such as deployments, statefulsets and daemonsets. So now you can set up port-forwards on any of these using `shift-f`. This exhibits the same mechanics as service based port-forwards ie pick a container port from pods matching the controller selector.
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.16.0
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_doc.png" align="center"/>
|
||||
|
||||
This is one of these drops that may make you wonder if you'll go from zero to hero or likely the reverse?? Will see how this goes... Please proceed with caution on this one as there could very well be much distrubances in the force...
|
||||
|
||||
Lots of code churns so could have totally hose some stuff, but like my GranPappy used to say `can't cook without making a mess!`
|
||||
|
||||
## Going Wide?
|
||||
|
||||
In this drop, we've enabled a new shortcut namely `wide` as `Ctrl-w`. On table views, you will be able to see more information about the resources such as labels or others depending on the viewed resource. This mnemonic works as a toggle so you can `narrow` the view by hitting it again.
|
||||
|
||||
## Zoom, Zoom, Zoom!
|
||||
|
||||
While viewing some resources that may contain errors, sorting on columns may not achieve the results you're seeking ie `show me all resources in an error state`. We've added a new option to achieve just that aka `zoom errors` as `ctrl-z`. This works as a toggle and will unveil resources that are need of some TLC on your part ;)
|
||||
|
||||
## Does Your Cluster Have A Pulse 💓?
|
||||
|
||||
In this drop, we're introducing a brand new view aka `K9s Pulses` 💓. This is a summary view listing the most sailient resources in your clusters and their current states. This view tracks two main metrics ie Ok and Toast on a 5sec beat. This view affords cluster activity and failure rates. BTW this is the zero to hero deal 🙀 Hopefully you'll dig it as this was much work to put together and I personally think it's the `ducks nuts`... If you like, please give me some luving on social or via GH sponsors as batteries are running low...
|
||||
|
||||
To active, enter command mode by typing in `:pulse` aliases are `pu`, `pulses` or `hz`
|
||||
To navigate thru the various pulses, you can use `tab`/`backtab` or use the menu index (just like namespaces selectors). Once on a pulse view, you can press `enter` to see the associated resource table view. Pressing `esc` will nav you back.
|
||||
|
||||
As I've may have mentioned before, my front-end/UX FU is weak, so I've also added a way for you to skin the charts via skins yaml to your own liking. Please see the skin section below for an example on how to skin the pulses dials. BONUS you should be able to skin K9s live! How cool is that 😻?
|
||||
|
||||
NOTE: Pulses are very much experimental and could totally bomb on your clusters! So please thread carefully and please do report (kindly!) back.
|
||||
|
||||
## BReaking Bad!
|
||||
|
||||
In this drop I've broken a few things (that I know off...), here is the list as I can recall...
|
||||
|
||||
1. Toggle header aka `my red headed step child`. Key moved (again!) now `Ctrl-e`
|
||||
2. Skin yaml layout CHANGED! Moved table and xray sections under views and added charts section.
|
||||
|
||||
## Skins Updates!
|
||||
|
||||
The skin file format CHANGE! If you are running skins with K9s, please make sure to update your skin file. If not K9s could bomb coming up!
|
||||
|
||||
NOTE: I don't think I'll get around to update all the contributed skins in this repo `skins` dir. If you're looking for a way to help out and are UI inclined, please take a peek and make them cool!
|
||||
|
||||
```yaml
|
||||
# my_cluster_skin.yml
|
||||
# Styles...
|
||||
foreground: &foreground "#f8f8f2"
|
||||
background: &background "#282a36"
|
||||
current_line: ¤t_line "#44475a"
|
||||
selection: &selection "#44475a"
|
||||
comment: &comment "#6272a4"
|
||||
cyan: &cyan "#8be9fd"
|
||||
green: &green "#50fa7b"
|
||||
orange: &orange "#ffb86c"
|
||||
pink: &pink "#ff79c6"
|
||||
purple: &purple "#bd93f9"
|
||||
red: &red "#ff5555"
|
||||
yellow: &yellow "#f1fa8c"
|
||||
|
||||
# Skin...
|
||||
k9s:
|
||||
# General K9s styles
|
||||
body:
|
||||
fgColor: *foreground
|
||||
bgColor: *background
|
||||
logoColor: *purple
|
||||
# ClusterInfoView styles.
|
||||
info:
|
||||
fgColor: *pink
|
||||
sectionColor: *foreground
|
||||
frame:
|
||||
# Borders styles.
|
||||
border:
|
||||
fgColor: *selection
|
||||
focusColor: *current_line
|
||||
menu:
|
||||
fgColor: *foreground
|
||||
keyColor: *pink
|
||||
# Used for favorite namespaces
|
||||
numKeyColor: *purple
|
||||
# CrumbView attributes for history navigation.
|
||||
crumbs:
|
||||
fgColor: *foreground
|
||||
bgColor: *current_line
|
||||
activeColor: *current_line
|
||||
# Resource status and update styles
|
||||
status:
|
||||
newColor: *cyan
|
||||
modifyColor: *purple
|
||||
addColor: *green
|
||||
errorColor: *red
|
||||
highlightcolor: *orange
|
||||
killColor: *comment
|
||||
completedColor: *comment
|
||||
# Border title styles.
|
||||
title:
|
||||
fgColor: *foreground
|
||||
bgColor: *current_line
|
||||
highlightColor: *orange
|
||||
counterColor: *purple
|
||||
filterColor: *pink
|
||||
views:
|
||||
charts:
|
||||
bgColor: *background
|
||||
dialBgColor: "#0A2239"
|
||||
chartBgColor: "#0A2239"
|
||||
defaultDialColors:
|
||||
- "#1E3888"
|
||||
- "#820101"
|
||||
defaultChartColors:
|
||||
- "#1E3888"
|
||||
- "#820101"
|
||||
resourceColors:
|
||||
batch/v1/jobs:
|
||||
- "#5D737E"
|
||||
- "#820101"
|
||||
v1/persistentvolumes:
|
||||
- "#3E554A"
|
||||
- "#820101"
|
||||
cpu:
|
||||
- "#6EA4BF"
|
||||
- "#820101"
|
||||
mem:
|
||||
- "#17505B"
|
||||
- "#820101"
|
||||
v1/events:
|
||||
- "#073B3A"
|
||||
- "#820101"
|
||||
v1/pods:
|
||||
- "#487FFF"
|
||||
- "#820101"
|
||||
# TableView attributes.
|
||||
table:
|
||||
fgColor: *foreground
|
||||
bgColor: *background
|
||||
cursorColor: *current_line
|
||||
# Header row styles.
|
||||
header:
|
||||
fgColor: *foreground
|
||||
bgColor: *background
|
||||
sorterColor: *cyan
|
||||
# Xray view attributes.
|
||||
xray:
|
||||
fgColor: *foreground
|
||||
bgColor: *background
|
||||
cursorColor: *current_line
|
||||
graphicColor: *purple
|
||||
showIcons: true
|
||||
# YAML info styles.
|
||||
yaml:
|
||||
keyColor: *pink
|
||||
colonColor: *purple
|
||||
valueColor: *foreground
|
||||
# Logs styles.
|
||||
logs:
|
||||
fgColor: *foreground
|
||||
bgColor: *background
|
||||
```
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
- [Issue #557](https://github.com/derailed/k9s/issues/557)
|
||||
- [Issue #555](https://github.com/derailed/k9s/issues/555)
|
||||
- [Issue #554](https://github.com/derailed/k9s/issues/554)
|
||||
- [Issue #553](https://github.com/derailed/k9s/issues/553)
|
||||
- [Issue #552](https://github.com/derailed/k9s/issues/552)
|
||||
- [Issue #551](https://github.com/derailed/k9s/issues/551)
|
||||
- [Issue #549](https://github.com/derailed/k9s/issues/549) A start with pulses...
|
||||
- [Issue #540](https://github.com/derailed/k9s/issues/540)
|
||||
- [Issue #421](https://github.com/derailed/k9s/issues/421)
|
||||
- [Issue #351](https://github.com/derailed/k9s/issues/351) Solved by Pulses?
|
||||
- [Issue #25](https://github.com/derailed/k9s/issues/25) Pulses? Oldie but goodie!
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.16.1
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
Maintenance Release!
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
- [Issue #561](https://github.com/derailed/k9s/issues/561)
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
16
go.mod
16
go.mod
|
|
@ -28,20 +28,30 @@ replace (
|
|||
)
|
||||
|
||||
require (
|
||||
fyne.io/fyne v1.2.2 // indirect
|
||||
github.com/GeertJohan/gomatrix v0.0.0-20190924221747-74328b69a02f // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/alexellis/go-execute v0.0.0-20200124154445-8697e4e28c5e // indirect
|
||||
github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de // indirect
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/derailed/tview v0.3.3
|
||||
github.com/derailed/tview v0.3.6
|
||||
github.com/drone/envsubst v1.0.2 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/fatih/color v1.6.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.5
|
||||
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec
|
||||
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c
|
||||
github.com/openfaas/faas-provider v0.15.0
|
||||
github.com/petergtz/pegomock v2.6.0+incompatible
|
||||
github.com/rakyll/hey v0.1.2
|
||||
github.com/rs/zerolog v1.17.2
|
||||
github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1
|
||||
github.com/rs/zerolog v1.18.0
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
|
|
|
|||
95
go.sum
95
go.sum
|
|
@ -3,6 +3,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
fyne.io/fyne v1.2.2 h1:mf7EseASp3CAC5vLWVPLnsoKxvp/ARdu3Seh0HvAQak=
|
||||
fyne.io/fyne v1.2.2/go.mod h1:Ab+3DIB/FVteW0y4DXfmZv4N3JdnCBh2lHkINI02BOU=
|
||||
github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
|
|
@ -27,8 +29,11 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/GeertJohan/gomatrix v0.0.0-20190924221747-74328b69a02f h1:O4XncXE6+qNjZIvermf2/Z4esEl8K1zFVPbl3l14mjM=
|
||||
github.com/GeertJohan/gomatrix v0.0.0-20190924221747-74328b69a02f/go.mod h1:HqtsgfzGADJzbZ+MbYAJ+PJnxIxBwBvYjyqd2wWw0j0=
|
||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
|
|
@ -43,6 +48,7 @@ github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy
|
|||
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
|
|
@ -56,11 +62,17 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko
|
|||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alexellis/go-execute v0.0.0-20200124154445-8697e4e28c5e h1:0cv4CUENL7e67/ZlNrvExWqa6oKH/9iv0KQn0/+hYaY=
|
||||
github.com/alexellis/go-execute v0.0.0-20200124154445-8697e4e28c5e/go.mod h1:zfRbgnPVxXCSpiKrg1CE72hNUWInqxExiaz2D9ppTts=
|
||||
github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de h1:jiPEvtW8VT0KwJxRyjW2VAAvlssjj9SfecsQ3Vgv5tk=
|
||||
github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de/go.mod h1:uAbpy8G7sjNB4qYdY6ymf5OIQ+TLDPApBYiR0Vc3lhk=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
|
|
@ -78,13 +90,18 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bugsnag/bugsnag-go v1.5.0 h1:tP8hiPv1pGGW3LA6LKy5lW6WG+y9J2xWUdPd3WC452k=
|
||||
github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
|
||||
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
|
|
@ -134,9 +151,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||
github.com/deislabs/oras v0.7.0 h1:RnDoFd3tQYODMiUqxgQ8JxlrlWL0/VMKIKRD01MmNYk=
|
||||
github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM=
|
||||
github.com/deislabs/oras v0.8.0 h1:WZqPI25DlEmth2VE/pIcnEh6msL2yHrzS5lV5gwaCsQ=
|
||||
github.com/derailed/tview v0.3.3 h1:tipPwxcDhx0zRBZuc8VKIrNgWL40FL5JeF/30XVieUE=
|
||||
github.com/derailed/tview v0.3.3/go.mod h1:yApPszFU62FoaGkf7swy2nIdV/h7Nid3dhMSVy6+OFI=
|
||||
github.com/derailed/tview v0.3.4 h1:PnF64fLqm48LEjC/XwOS7JufDgFuuPYx85YVt5t3rwE=
|
||||
github.com/derailed/tview v0.3.4/go.mod h1:yApPszFU62FoaGkf7swy2nIdV/h7Nid3dhMSVy6+OFI=
|
||||
github.com/derailed/tview v0.3.5 h1:1vKqcJIiZtLAs5moX9c38+BbBSYhPgFq0ZndnVNVNFc=
|
||||
github.com/derailed/tview v0.3.5/go.mod h1:yApPszFU62FoaGkf7swy2nIdV/h7Nid3dhMSVy6+OFI=
|
||||
github.com/derailed/tview v0.3.6 h1:9PyX6Nu1vs9mCVfvV2q2fwT/dZta0dBGr4ZPjCF1KnU=
|
||||
github.com/derailed/tview v0.3.6/go.mod h1:GJ3k/TIzEE+sj1L09/usk6HrkjsdadSsb03eHgPbcII=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
|
|
@ -145,7 +167,6 @@ github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d/go.mod h1:JLrzqnKDaYBop
|
|||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf h1:+Hdbkr8QbGSQ4dY50mmgZEGtzjhv0we2Ws2XCz3c0Q8=
|
||||
github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
|
||||
|
|
@ -159,12 +180,15 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo=
|
||||
github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs=
|
||||
|
|
@ -185,10 +209,12 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC
|
|||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
|
|
@ -204,6 +230,10 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a
|
|||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
|
|
@ -267,6 +297,7 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
|||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
|
|
@ -274,10 +305,12 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09Vjb
|
|||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
|
|
@ -313,15 +346,16 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
|||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
|
|
@ -358,9 +392,13 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
|||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20190124120936-8611f5a5ff3f/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
|
|
@ -368,6 +406,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
|
|
@ -407,7 +446,9 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
|||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA=
|
||||
|
|
@ -419,11 +460,13 @@ github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV
|
|||
github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY=
|
||||
github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
|
||||
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY=
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
|
|
@ -439,8 +482,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
||||
github.com/mum4k/termdash v0.10.0/go.mod h1:l3tO+lJi9LZqXRq7cu7h5/8rDIK3AzelSuq2v/KncxI=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7PEeBTyK1rj1gABdCO2mbri6GKO1cMDs=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
|
||||
|
|
@ -448,10 +493,13 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
|||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
|
|
@ -468,6 +516,12 @@ github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2
|
|||
github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
|
||||
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec h1:S6wtb5ie7KeMcuEaESj0RoSmpyGfvOSuunmKEdX7wg8=
|
||||
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14=
|
||||
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c h1:9RGaDpUySgRscx5oiagwUtm9vBZti/4+QYq2GM4FegE=
|
||||
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c/go.mod h1:u/KO+e43wkagC0lqM1eaqNEWEBdg08Q1ugP/idj39MM=
|
||||
github.com/openfaas/faas-provider v0.15.0 h1:3x5ma90FL7AqP4NOD6f03AY24y3xBeVF6xGLUx6Xrlc=
|
||||
github.com/openfaas/faas-provider v0.15.0/go.mod h1:8Fagi2UeMfL+gZAqZWSMQg86i+w1+hBOKtwKRP5sLFI=
|
||||
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
|
|
@ -476,6 +530,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
|
|||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/petergtz/pegomock v2.6.0+incompatible h1:gD9YvI42LylIA/il2Cy8lMfg+CncNFMqexYepyEWGaQ=
|
||||
github.com/petergtz/pegomock v2.6.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o=
|
||||
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5 h1:rZQtoozkfsiNs36c7Tdv/gyGNzD1X1XWKO8rptVNZuM=
|
||||
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
|
|
@ -513,6 +568,7 @@ github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H
|
|||
github.com/rakyll/hey v0.1.2 h1:XlGaKcBdmXJaPImiTnE+TGLDUWQ2toYuHCwdrylLjmg=
|
||||
github.com/rakyll/hey v0.1.2/go.mod h1:S5M+++KwbmxA7w68S92B5NdWiCB+cIhITaMUkq9W608=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1 h1:s9Lw4phBWkuQJUd+msaBMxP3utLvrFaBQV9jNgG55r0=
|
||||
github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1/go.mod h1:+rKjP5+h9HMwWRpAfhIkkQ9KE3m3Nz5rwn7YtUpwgqk=
|
||||
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
|
|
@ -523,10 +579,14 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo=
|
||||
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8=
|
||||
github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
|
||||
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
|
|
@ -554,6 +614,10 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e h1:LJUrNHytcMXWKxnULIHPe5SCb1jDpO9o672VB1x2EuQ=
|
||||
github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e h1:FFotfUvew9Eg02LYRl8YybAnm0HCwjjfY5JlOI1oB00=
|
||||
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -563,6 +627,7 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
|
|
@ -580,18 +645,23 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
|
|||
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xenolf/lego v0.0.0-20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
|
||||
github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656 h1:BTvU+npm3/yjuBd53EvgiFLl5+YLikf2WvHsjRQ4KrY=
|
||||
github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVIuJg=
|
||||
github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8=
|
||||
|
|
@ -612,8 +682,12 @@ golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3
|
|||
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
|
|
@ -621,6 +695,8 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -677,6 +753,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE=
|
||||
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -686,8 +763,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -718,6 +793,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
|
|||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
|
|
@ -750,6 +826,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|||
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v1 v1.1.2 h1:/5jmADZB+RiKtZGr4HxsEFOEfbfsjTKsVnqpThUpE30=
|
||||
gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
|
@ -764,7 +841,6 @@ gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
|||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY=
|
||||
helm.sh/helm v2.16.1+incompatible h1:np11uYeEtlYcFIFRya8Xs5ZweV1z6MvaWQqJAW+1SZQ=
|
||||
helm.sh/helm/v3 v3.0.2 h1:BggvLisIMrAc+Is5oAHVrlVxgwOOrMN8nddfQbm5gKo=
|
||||
helm.sh/helm/v3 v3.0.2/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
@ -828,6 +904,7 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
|||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
rsc.io/letsencrypt v0.0.1 h1:DV0d09Ne9E7UUa9ZqWktZ9L2VmybgTgfq7xlfFR/bbU=
|
||||
rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ func makeCacheKey(ns, gvr string, vv []string) string {
|
|||
return ns + ":" + gvr + "::" + strings.Join(vv, ",")
|
||||
}
|
||||
|
||||
func (a *APIClient) clearCache() {
|
||||
for _, k := range a.cache.Keys() {
|
||||
a.cache.Remove(k)
|
||||
}
|
||||
}
|
||||
|
||||
// CanI checks if user has access to a certain resource.
|
||||
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
|
||||
if IsClusterWide(ns) {
|
||||
|
|
@ -131,6 +137,9 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
|||
// BOZO!! No super sure about this approach either??
|
||||
func (a *APIClient) CheckConnectivity() (status bool) {
|
||||
defer func() {
|
||||
if !status {
|
||||
a.clearCache()
|
||||
}
|
||||
if err := recover(); err != nil {
|
||||
status = false
|
||||
}
|
||||
|
|
@ -149,10 +158,10 @@ func (a *APIClient) CheckConnectivity() (status bool) {
|
|||
}
|
||||
}
|
||||
|
||||
if _, err := a.checkClientSet.ServerVersion(); err != nil {
|
||||
log.Error().Err(err).Msgf("K9s can't connect to cluster")
|
||||
} else {
|
||||
if _, err := a.checkClientSet.ServerVersion(); err == nil {
|
||||
status = true
|
||||
} else {
|
||||
log.Error().Err(err).Msgf("K9s can't connect to cluster")
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -258,21 +267,24 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) {
|
|||
return a.mxsClient, err
|
||||
}
|
||||
|
||||
// SwitchContextOrDie handles kubeconfig context switches.
|
||||
func (a *APIClient) SwitchContextOrDie(ctx string) {
|
||||
// SwitchContext handles kubeconfig context switches.
|
||||
func (a *APIClient) SwitchContext(ctx string) error {
|
||||
currentCtx, err := a.config.CurrentContextName()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Fetching current context")
|
||||
return err
|
||||
}
|
||||
if currentCtx == ctx {
|
||||
return nil
|
||||
}
|
||||
|
||||
if currentCtx != ctx {
|
||||
a.cachedClient = nil
|
||||
a.reset()
|
||||
if err := a.config.SwitchContext(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("Switching context")
|
||||
}
|
||||
_ = a.supportsMxServer()
|
||||
if err := a.config.SwitchContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
a.clearCache()
|
||||
a.reset()
|
||||
_ = a.supportsMxServer()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APIClient) reset() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package client
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -48,6 +49,10 @@ func (c *Config) SwitchContext(name string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if _, err := c.GetContext(name); err != nil {
|
||||
return fmt.Errorf("context %s does not exist", name)
|
||||
}
|
||||
|
||||
if currentCtx != name {
|
||||
c.reset()
|
||||
c.flags.Context, c.currentContext = &name, name
|
||||
|
|
@ -173,13 +178,31 @@ func (c *Config) ClusterNames() ([]string, error) {
|
|||
|
||||
// CurrentGroupNames retrieves the active group names.
|
||||
func (c *Config) CurrentGroupNames() ([]string, error) {
|
||||
if c.flags.ImpersonateGroup != nil && len(*c.flags.ImpersonateGroup) != 0 {
|
||||
if areSet(c.flags.ImpersonateGroup) {
|
||||
return *c.flags.ImpersonateGroup, nil
|
||||
}
|
||||
|
||||
return []string{}, errors.New("unable to locate current group")
|
||||
}
|
||||
|
||||
// ImpersonateGroups retrieves the active groupsif set on the CLI.
|
||||
func (c *Config) ImpersonateGroups() (string, error) {
|
||||
if areSet(c.flags.ImpersonateGroup) {
|
||||
return strings.Join(*c.flags.ImpersonateGroup, ","), nil
|
||||
}
|
||||
|
||||
return "", errors.New("no groups set")
|
||||
}
|
||||
|
||||
// ImpersonateUser retrieves the active user name if set on the CLI.
|
||||
func (c *Config) ImpersonateUser() (string, error) {
|
||||
if isSet(c.flags.Impersonate) {
|
||||
return *c.flags.Impersonate, nil
|
||||
}
|
||||
|
||||
return "", errors.New("no user set")
|
||||
}
|
||||
|
||||
// CurrentUserName retrieves the active user name.
|
||||
func (c *Config) CurrentUserName() (string, error) {
|
||||
if isSet(c.flags.Impersonate) {
|
||||
|
|
@ -307,3 +330,7 @@ func (c *Config) ensureConfig() {
|
|||
func isSet(s *string) bool {
|
||||
return s != nil && len(*s) != 0
|
||||
}
|
||||
|
||||
func areSet(s *[]string) bool {
|
||||
return s != nil && len(*s) != 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func init() {
|
|||
}
|
||||
|
||||
func TestConfigCurrentContext(t *testing.T) {
|
||||
name, kubeConfig := "blee", "./assets/config"
|
||||
name, kubeConfig := "blee", "./testdata/config"
|
||||
uu := []struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
context string
|
||||
|
|
@ -36,7 +36,7 @@ func TestConfigCurrentContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigCurrentCluster(t *testing.T) {
|
||||
name, kubeConfig := "blee", "./assets/config"
|
||||
name, kubeConfig := "blee", "./testdata/config"
|
||||
uu := []struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
cluster string
|
||||
|
|
@ -54,7 +54,7 @@ func TestConfigCurrentCluster(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigCurrentUser(t *testing.T) {
|
||||
name, kubeConfig := "blee", "./assets/config"
|
||||
name, kubeConfig := "blee", "./testdata/config"
|
||||
uu := []struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
user string
|
||||
|
|
@ -72,7 +72,7 @@ func TestConfigCurrentUser(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigCurrentNamespace(t *testing.T) {
|
||||
name, kubeConfig := "blee", "./assets/config"
|
||||
name, kubeConfig := "blee", "./testdata/config"
|
||||
uu := []struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
namespace string
|
||||
|
|
@ -91,7 +91,7 @@ func TestConfigCurrentNamespace(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigGetContext(t *testing.T) {
|
||||
kubeConfig := "./assets/config"
|
||||
kubeConfig := "./testdata/config"
|
||||
uu := []struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
cluster string
|
||||
|
|
@ -114,7 +114,7 @@ func TestConfigGetContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigSwitchContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -129,7 +129,7 @@ func TestConfigSwitchContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigClusterNameFromContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -142,7 +142,7 @@ func TestConfigClusterNameFromContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigAccess(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -155,7 +155,7 @@ func TestConfigAccess(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigContexts(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -168,7 +168,7 @@ func TestConfigContexts(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigContextNames(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -181,7 +181,7 @@ func TestConfigContextNames(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigClusterNames(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config"
|
||||
cluster, kubeConfig := "duh", "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -194,7 +194,7 @@ func TestConfigClusterNames(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigDelContext(t *testing.T) {
|
||||
cluster, kubeConfig := "duh", "./assets/config.1"
|
||||
cluster, kubeConfig := "duh", "./testdata/config.1"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
ClusterName: &cluster,
|
||||
|
|
@ -209,7 +209,7 @@ func TestConfigDelContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigRestConfig(t *testing.T) {
|
||||
kubeConfig := "./assets/config"
|
||||
kubeConfig := "./testdata/config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ func TestConfigRestConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigBadConfig(t *testing.T) {
|
||||
kubeConfig := "./assets/bork_config"
|
||||
kubeConfig := "./testdata/bork_config"
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ func TestConfigBadConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNamespaceNames(t *testing.T) {
|
||||
kubeConfig := "./assets/config"
|
||||
kubeConfig := "./testdata/config"
|
||||
|
||||
flags := genericclioptions.ConfigFlags{
|
||||
KubeConfig: &kubeConfig,
|
||||
|
|
|
|||
|
|
@ -3,20 +3,49 @@ package client
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
const (
|
||||
mxCacheSize = 100
|
||||
mxCacheExpiry = 1 * time.Minute
|
||||
)
|
||||
|
||||
// MetricsDial tracks global metric server handle.
|
||||
var MetricsDial *MetricsServer
|
||||
|
||||
// DialMetrics dials the metrics server.
|
||||
func DialMetrics(c Connection) *MetricsServer {
|
||||
if MetricsDial == nil {
|
||||
MetricsDial = NewMetricsServer(c)
|
||||
}
|
||||
|
||||
return MetricsDial
|
||||
}
|
||||
|
||||
// ResetMetrics resets the metric server handle.
|
||||
func ResetMetrics() {
|
||||
MetricsDial = nil
|
||||
}
|
||||
|
||||
// MetricsServer serves cluster metrics for nodes and pods.
|
||||
type MetricsServer struct {
|
||||
Connection
|
||||
|
||||
cache *cache.LRUExpireCache
|
||||
}
|
||||
|
||||
// NewMetricsServer return a metric server instance.
|
||||
func NewMetricsServer(c Connection) *MetricsServer {
|
||||
return &MetricsServer{Connection: c}
|
||||
return &MetricsServer{
|
||||
Connection: c,
|
||||
cache: cache.NewLRUExpireCache(mxCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// NodesMetrics retrieves metrics for a given set of nodes.
|
||||
|
|
@ -28,15 +57,15 @@ func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeM
|
|||
for _, no := range nodes.Items {
|
||||
mmx[no.Name] = NodeMetrics{
|
||||
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AvailMEM: toMB(no.Status.Allocatable.Memory().Value()),
|
||||
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
||||
TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
|
||||
TotalMEM: toMB(no.Status.Capacity.Memory().Value()),
|
||||
TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
|
||||
}
|
||||
}
|
||||
for _, c := range metrics.Items {
|
||||
if mx, ok := mmx[c.Name]; ok {
|
||||
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
|
||||
mx.CurrentMEM = toMB(c.Usage.Memory().Value())
|
||||
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
|
||||
mmx[c.Name] = mx
|
||||
}
|
||||
}
|
||||
|
|
@ -51,13 +80,13 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
|
|||
for _, no := range nos.Items {
|
||||
nodeMetrics[no.Name] = NodeMetrics{
|
||||
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AvailMEM: toMB(no.Status.Allocatable.Memory().Value()),
|
||||
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
||||
}
|
||||
}
|
||||
for _, mx := range nmx.Items {
|
||||
if m, ok := nodeMetrics[mx.Name]; ok {
|
||||
m.CurrentCPU = mx.Usage.Cpu().MilliValue()
|
||||
m.CurrentMEM = toMB(mx.Usage.Memory().Value())
|
||||
m.CurrentMEM = ToMB(mx.Usage.Memory().Value())
|
||||
nodeMetrics[mx.Name] = m
|
||||
}
|
||||
}
|
||||
|
|
@ -74,86 +103,121 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
|
|||
return nil
|
||||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||
var mx mv1beta1.NodeMetricsList
|
||||
func (m *MetricsServer) checkAccess(ns, gvr, msg string) error {
|
||||
if !m.HasMetrics() {
|
||||
return &mx, fmt.Errorf("No metrics-server detected on cluster")
|
||||
return fmt.Errorf("No metrics-server detected on cluster")
|
||||
}
|
||||
|
||||
auth, err := m.CanI("", "metrics.k8s.io/v1beta1/nodes", ListAccess)
|
||||
auth, err := m.CanI(ns, gvr, ListAccess)
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
return err
|
||||
}
|
||||
if !auth {
|
||||
return &mx, fmt.Errorf("user is not authorized to list node metrics")
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for nodes.
|
||||
func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||
const msg = "user is not authorized to list node metrics"
|
||||
|
||||
mx := new(mv1beta1.NodeMetricsList)
|
||||
if err := m.checkAccess("", "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
const key = "nodes"
|
||||
if entry, ok := m.cache.Get(key); ok && entry != nil {
|
||||
mxList, ok := entry.(*mv1beta1.NodeMetricsList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected nodemetricslist but got %T", entry)
|
||||
}
|
||||
return mxList, nil
|
||||
}
|
||||
|
||||
client, err := m.MXDial()
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
return mx, err
|
||||
}
|
||||
return client.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
|
||||
mxList, err := client.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
m.cache.Add(key, mxList, mxCacheExpiry)
|
||||
|
||||
return mxList, nil
|
||||
}
|
||||
|
||||
// FetchPodsMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error) {
|
||||
var mx mv1beta1.PodMetricsList
|
||||
if m.Connection == nil {
|
||||
return &mx, fmt.Errorf("no client connection")
|
||||
}
|
||||
mx := new(mv1beta1.PodMetricsList)
|
||||
const msg = "user is not authorized to list pods metrics"
|
||||
|
||||
if !m.HasMetrics() {
|
||||
return &mx, fmt.Errorf("No metrics-server detected on cluster")
|
||||
}
|
||||
if ns == NamespaceAll {
|
||||
ns = AllNamespaces
|
||||
}
|
||||
|
||||
auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", ListAccess)
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
if !auth {
|
||||
return &mx, fmt.Errorf("user is not authorized to list pods metrics")
|
||||
|
||||
key := FQN(ns, "pods")
|
||||
if entry, ok := m.cache.Get(key); ok {
|
||||
mxList, ok := entry.(*mv1beta1.PodMetricsList)
|
||||
if !ok {
|
||||
return mx, fmt.Errorf("expected podmetricslist but got %T", entry)
|
||||
}
|
||||
return mxList, nil
|
||||
}
|
||||
|
||||
client, err := m.MXDial()
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
return mx, err
|
||||
}
|
||||
mxList, err := client.MetricsV1beta1().PodMetricses(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
m.cache.Add(key, mxList, mxCacheExpiry)
|
||||
|
||||
return client.MetricsV1beta1().PodMetricses(ns).List(metav1.ListOptions{})
|
||||
return mxList, err
|
||||
}
|
||||
|
||||
// FetchPodMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodMetrics(fqn string) (*mv1beta1.PodMetrics, error) {
|
||||
var mx mv1beta1.PodMetrics
|
||||
if m.Connection == nil {
|
||||
return &mx, fmt.Errorf("no client connection")
|
||||
}
|
||||
if !m.HasMetrics() {
|
||||
return &mx, fmt.Errorf("No metrics-server detected on cluster")
|
||||
}
|
||||
var mx *mv1beta1.PodMetrics
|
||||
const msg = "user is not authorized to list pod metrics"
|
||||
|
||||
ns, n := Namespaced(fqn)
|
||||
if ns == NamespaceAll {
|
||||
ns = AllNamespaces
|
||||
}
|
||||
auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", GetAccess)
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
if !auth {
|
||||
return &mx, fmt.Errorf("user is not authorized to list pod metrics")
|
||||
|
||||
var key = FQN(ns, "pods")
|
||||
if entry, ok := m.cache.Get(key); ok {
|
||||
if list, ok := entry.(*mv1beta1.PodMetricsList); ok && list != nil {
|
||||
for _, m := range list.Items {
|
||||
if FQN(m.Namespace, m.Name) == fqn {
|
||||
return &m, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client, err := m.MXDial()
|
||||
if err != nil {
|
||||
return &mx, err
|
||||
return mx, err
|
||||
}
|
||||
mx, err = client.MetricsV1beta1().PodMetricses(ns).Get(n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
m.cache.Add(key, mx, mxCacheExpiry)
|
||||
|
||||
return client.MetricsV1beta1().PodMetricses(ns).Get(n, metav1.GetOptions{})
|
||||
return mx, nil
|
||||
}
|
||||
|
||||
// PodsMetrics retrieves metrics for all pods in a given namespace.
|
||||
|
|
@ -167,7 +231,7 @@ func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetri
|
|||
var mx PodMetrics
|
||||
for _, c := range p.Containers {
|
||||
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
|
||||
mx.CurrentMEM += toMB(c.Usage.Memory().Value())
|
||||
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
|
||||
}
|
||||
mmx[p.Namespace+"/"+p.Name] = mx
|
||||
}
|
||||
|
|
@ -178,8 +242,8 @@ func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetri
|
|||
|
||||
const megaByte = 1024 * 1024
|
||||
|
||||
// toMB converts bytes to megabytes.
|
||||
func toMB(v int64) float64 {
|
||||
// ToMB converts bytes to megabytes.
|
||||
func ToMB(v int64) float64 {
|
||||
return float64(v) / megaByte
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package client
|
||||
|
||||
// PortTunnel represents a host tunnel port mapper.
|
||||
type PortTunnel struct {
|
||||
Address, LocalPort, ContainerPort string
|
||||
}
|
||||
|
||||
// PortMap returns a port mapping.
|
||||
func (t PortTunnel) PortMap() string {
|
||||
return t.LocalPort + ":" + t.ContainerPort
|
||||
}
|
||||
|
|
@ -22,6 +22,9 @@ const (
|
|||
|
||||
// ClusterScope designates a resource is not namespaced.
|
||||
ClusterScope = "-"
|
||||
|
||||
// NotNamespaced designates a non resource namespace.
|
||||
NotNamespaced = "*"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -64,7 +67,7 @@ type Connection interface {
|
|||
|
||||
Config() *Config
|
||||
DialOrDie() kubernetes.Interface
|
||||
SwitchContextOrDie(ctx string)
|
||||
SwitchContext(ctx string) error
|
||||
CachedDiscoveryOrDie() *disk.CachedDiscoveryClient
|
||||
RestConfigOrDie() *restclient.Config
|
||||
MXDial() (*versioned.Clientset, error)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
|
|
@ -87,6 +88,13 @@ func (a *Aliases) loadDefaults() {
|
|||
// Load K9s aliases.
|
||||
func (a *Aliases) Load() error {
|
||||
a.loadDefaults()
|
||||
|
||||
_, err := os.Stat(K9sAlias)
|
||||
if os.IsNotExist(err) {
|
||||
log.Debug().Err(err).Msgf("No custom aliases found")
|
||||
return nil
|
||||
}
|
||||
|
||||
return a.LoadAliases(K9sAlias)
|
||||
}
|
||||
|
||||
|
|
@ -139,11 +147,17 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
// LoadAliases loads alias from a given file.
|
||||
func (a *Aliases) LoadAliases(path string) error {
|
||||
// Load K9s aliases.
|
||||
func (a *Aliases) Load() error {
|
||||
a.loadDefaultAliases()
|
||||
return a.LoadFileAliases(K9sAlias)
|
||||
}
|
||||
|
||||
// LoadFileAliases loads alias from a given file.
|
||||
func (a *Aliases) LoadFileAliases(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No custom aliases found")
|
||||
log.Debug().Err(err).Msgf("No custom aliases found")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +175,63 @@ func (a *Aliases) LoadAliases(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *Aliases) loadDefaultAliases() {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
a.Alias["dp"] = "apps/v1/deployments"
|
||||
a.Alias["sec"] = "v1/secrets"
|
||||
a.Alias["jo"] = "batch/v1/jobs"
|
||||
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
|
||||
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
|
||||
a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles"
|
||||
a.Alias["rb"] = "rbac.authorization.k8s.io/v1/rolebindings"
|
||||
a.Alias["np"] = "networking.k8s.io/v1/networkpolicies"
|
||||
|
||||
const contexts = "contexts"
|
||||
{
|
||||
a.Alias["ctx"] = contexts
|
||||
a.Alias[contexts] = contexts
|
||||
a.Alias["context"] = contexts
|
||||
}
|
||||
const users = "users"
|
||||
{
|
||||
a.Alias["usr"] = users
|
||||
a.Alias[users] = users
|
||||
a.Alias["user"] = users
|
||||
}
|
||||
const groups = "groups"
|
||||
{
|
||||
a.Alias["grp"] = groups
|
||||
a.Alias["group"] = groups
|
||||
a.Alias[groups] = groups
|
||||
}
|
||||
const portFwds = "portforwards"
|
||||
{
|
||||
a.Alias["pf"] = portFwds
|
||||
a.Alias[portFwds] = portFwds
|
||||
a.Alias["portforward"] = portFwds
|
||||
}
|
||||
const benchmarks = "benchmarks"
|
||||
{
|
||||
a.Alias["be"] = benchmarks
|
||||
a.Alias["benchmark"] = benchmarks
|
||||
a.Alias[benchmarks] = benchmarks
|
||||
}
|
||||
const dumps = "screendumps"
|
||||
{
|
||||
a.Alias["sd"] = dumps
|
||||
a.Alias["screendump"] = dumps
|
||||
a.Alias[dumps] = dumps
|
||||
}
|
||||
const pulses = "pulses"
|
||||
{
|
||||
a.Alias["hz"] = pulses
|
||||
a.Alias["pu"] = pulses
|
||||
a.Alias["pulse"] = pulses
|
||||
}
|
||||
}
|
||||
|
||||
// Save alias to disk.
|
||||
func (a *Aliases) Save() error {
|
||||
log.Debug().Msg("[Config] Saving Aliases...")
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func TestAliasDefine(t *testing.T) {
|
|||
func TestAliasesLoad(t *testing.T) {
|
||||
a := config.NewAliases()
|
||||
|
||||
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
|
||||
assert.Nil(t, a.LoadFileAliases("testdata/alias.yml"))
|
||||
assert.Equal(t, 2, len(a.Alias))
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +82,6 @@ func TestAliasesSave(t *testing.T) {
|
|||
a.Alias["blee"] = "duh"
|
||||
|
||||
assert.Nil(t, a.SaveAliases("/tmp/a.yml"))
|
||||
assert.Nil(t, a.LoadAliases("/tmp/a.yml"))
|
||||
assert.Nil(t, a.LoadFileAliases("/tmp/a.yml"))
|
||||
assert.Equal(t, 2, len(a.Alias))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ type (
|
|||
|
||||
// BenchConfig represents a service benchmark.
|
||||
BenchConfig struct {
|
||||
Name string
|
||||
C int `yaml:"concurrency"`
|
||||
N int `yaml:"requests"`
|
||||
Auth Auth `yaml:"auth"`
|
||||
HTTP HTTP `yaml:"http"`
|
||||
Name string
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +73,8 @@ func newBenchmark() Benchmark {
|
|||
}
|
||||
}
|
||||
|
||||
func (b Benchmark) empty() bool {
|
||||
// Empty checks if the benchmark is set
|
||||
func (b Benchmark) Empty() bool {
|
||||
return b.C == 0 && b.N == 0
|
||||
}
|
||||
|
||||
|
|
@ -104,3 +105,15 @@ func (s *Bench) load(path string) error {
|
|||
|
||||
return yaml.Unmarshal(f, &s)
|
||||
}
|
||||
|
||||
// DefaultBenchSpec returns a default bench spec.
|
||||
func DefaultBenchSpec() BenchConfig {
|
||||
return BenchConfig{
|
||||
C: DefaultC,
|
||||
N: DefaultN,
|
||||
HTTP: HTTP{
|
||||
Method: DefaultMethod,
|
||||
Path: "/",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func TestBenchEmpty(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, u.b.empty())
|
||||
assert.Equal(t, u.e, u.b.Empty())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -32,14 +32,14 @@ func TestBenchLoad(t *testing.T) {
|
|||
coCount int
|
||||
}{
|
||||
"goodConfig": {
|
||||
"test_assets/b_good.yml",
|
||||
"testdata/b_good.yml",
|
||||
2,
|
||||
1000,
|
||||
2,
|
||||
0,
|
||||
},
|
||||
"malformed": {
|
||||
"test_assets/b_toast.yml",
|
||||
"testdata/b_toast.yml",
|
||||
1,
|
||||
200,
|
||||
0,
|
||||
|
|
@ -100,7 +100,7 @@ func TestBenchServiceLoad(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
b, err := NewBench("test_assets/b_good.yml")
|
||||
b, err := NewBench("testdata/b_good.yml")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
||||
|
|
@ -119,16 +119,16 @@ func TestBenchServiceLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBenchReLoad(t *testing.T) {
|
||||
b, err := NewBench("test_assets/b_containers.yml")
|
||||
b, err := NewBench("testdata/b_containers.yml")
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, b.Benchmarks.Defaults.C)
|
||||
assert.Nil(t, b.Reload("test_assets/b_containers_1.yml"))
|
||||
assert.Nil(t, b.Reload("testdata/b_containers_1.yml"))
|
||||
assert.Equal(t, 20, b.Benchmarks.Defaults.C)
|
||||
}
|
||||
|
||||
func TestBenchLoadToast(t *testing.T) {
|
||||
_, err := NewBench("test_assets/toast.yml")
|
||||
_, err := NewBench("testdata/toast.yml")
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ func TestBenchContainerLoad(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
b, err := NewBench("test_assets/b_containers.yml")
|
||||
b, err := NewBench("testdata/b_containers.yml")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(b.Benchmarks.Services))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func init() {
|
|||
}
|
||||
|
||||
func TestConfigRefine(t *testing.T) {
|
||||
cfgFile, ctx, cluster, ns := "test_assets/kubeconfig-test.yml", "test", "c1", "ns1"
|
||||
cfgFile, ctx, cluster, ns := "testdata/kubeconfig-test.yml", "test", "c1", "ns1"
|
||||
uu := map[string]struct {
|
||||
flags *genericclioptions.ConfigFlags
|
||||
issue bool
|
||||
|
|
@ -85,7 +85,7 @@ func TestConfigValidate(t *testing.T) {
|
|||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.Validate()
|
||||
// mc.VerifyWasCalledOnce().ValidNamespaces()
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ func TestConfigValidate(t *testing.T) {
|
|||
func TestConfigLoad(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
|
||||
assert.Equal(t, 2, cfg.K9s.RefreshRate)
|
||||
assert.Equal(t, 200, cfg.K9s.LogBufferSize)
|
||||
|
|
@ -119,7 +119,7 @@ func TestConfigCurrentCluster(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.NotNil(t, cfg.CurrentCluster())
|
||||
assert.Equal(t, "kube-system", cfg.CurrentCluster().Namespace.Active)
|
||||
assert.Equal(t, "ctx", cfg.CurrentCluster().View.Active)
|
||||
|
|
@ -129,7 +129,7 @@ func TestConfigActiveNamespace(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Equal(t, "kube-system", cfg.ActiveNamespace())
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ func TestConfigSetActiveNamespace(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Nil(t, cfg.SetActiveNamespace("default"))
|
||||
assert.Equal(t, "default", cfg.ActiveNamespace())
|
||||
}
|
||||
|
|
@ -151,7 +151,7 @@ func TestConfigActiveView(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
assert.Equal(t, "ctx", cfg.ActiveView())
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ func TestConfigSetActiveView(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.SetActiveView("po")
|
||||
assert.Equal(t, "po", cfg.ActiveView())
|
||||
}
|
||||
|
|
@ -173,7 +173,7 @@ func TestConfigFavNamespaces(t *testing.T) {
|
|||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"}
|
||||
assert.Equal(t, expectedNS, cfg.FavNamespaces())
|
||||
}
|
||||
|
|
@ -181,13 +181,13 @@ func TestConfigFavNamespaces(t *testing.T) {
|
|||
func TestConfigLoadOldCfg(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s_old.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s_old.yml"))
|
||||
}
|
||||
|
||||
func TestConfigLoadCrap(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.NotNil(t, cfg.Load("test_assets/k9s_not_there.yml"))
|
||||
assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yml"))
|
||||
}
|
||||
|
||||
func TestConfigSaveFile(t *testing.T) {
|
||||
|
|
@ -203,7 +203,7 @@ func TestConfigSaveFile(t *testing.T) {
|
|||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.RefreshRate = 100
|
||||
cfg.K9s.ReadOnly = true
|
||||
cfg.K9s.LogBufferSize = 500
|
||||
|
|
@ -233,7 +233,7 @@ func TestConfigReset(t *testing.T) {
|
|||
|
||||
cfg := config.NewConfig(mk)
|
||||
cfg.SetConnection(mc)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.Reset()
|
||||
cfg.Validate()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestHotKeyLoad(t *testing.T) {
|
||||
h := config.NewHotKeys()
|
||||
assert.Nil(t, h.LoadHotKeys("test_assets/hot_key.yml"))
|
||||
assert.Nil(t, h.LoadHotKeys("testdata/hot_key.yml"))
|
||||
|
||||
assert.Equal(t, 1, len(h.HotKey))
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func TestK9sActiveClusterBlank(t *testing.T) {
|
|||
func TestK9sActiveCluster(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
|
||||
cl := cfg.K9s.ActiveCluster()
|
||||
assert.NotNil(t, cl)
|
||||
|
|
|
|||
|
|
@ -267,12 +267,14 @@ func (mock *MockConnection) SupportsResource(_param0 string) bool {
|
|||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SwitchContextOrDie(_param0 string) {
|
||||
func (mock *MockConnection) SwitchContext(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{})
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestPluginLoad(t *testing.T) {
|
||||
p := config.NewPlugins()
|
||||
assert.Nil(t, p.LoadPlugins("test_assets/plugin.yml"))
|
||||
assert.Nil(t, p.LoadPlugins("testdata/plugin.yml"))
|
||||
|
||||
assert.Equal(t, 1, len(p.Plugin))
|
||||
k, ok := p.Plugin["blah"]
|
||||
|
|
|
|||
|
|
@ -21,17 +21,31 @@ type StyleListener interface {
|
|||
}
|
||||
|
||||
type (
|
||||
// Color represents a color.
|
||||
Color string
|
||||
|
||||
// Colors tracks multiple colors.
|
||||
Colors []Color
|
||||
|
||||
// Styles tracks K9s styling options.
|
||||
Styles struct {
|
||||
K9s Style `yaml:"k9s"`
|
||||
listeners []StyleListener
|
||||
}
|
||||
|
||||
// Style tracks K9s styles.
|
||||
Style struct {
|
||||
Body Body `yaml:"body"`
|
||||
Frame Frame `yaml:"frame"`
|
||||
Info Info `yaml:"info"`
|
||||
Views Views `yaml:"views"`
|
||||
}
|
||||
|
||||
// Body tracks body styles.
|
||||
Body struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
LogoColor string `yaml:"logoColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
LogoColor Color `yaml:"logoColor"`
|
||||
}
|
||||
|
||||
// Frame tracks frame styles.
|
||||
|
|
@ -45,120 +59,171 @@ type (
|
|||
|
||||
// Views tracks individual view styles.
|
||||
Views struct {
|
||||
Yaml Yaml `yaml:"yaml"`
|
||||
Log Log `yaml:"logs"`
|
||||
Table Table `yaml:"table"`
|
||||
Xray Xray `yaml:"xray"`
|
||||
Charts Charts `yaml:"charts"`
|
||||
Yaml Yaml `yaml:"yaml"`
|
||||
Log Log `yaml:"logs"`
|
||||
}
|
||||
|
||||
// Status tracks resource status styles.
|
||||
Status struct {
|
||||
NewColor string `yaml:"newColor"`
|
||||
ModifyColor string `yaml:"modifyColor"`
|
||||
AddColor string `yaml:"addColor"`
|
||||
ErrorColor string `yaml:"errorColor"`
|
||||
HighlightColor string `yaml:"highlightColor"`
|
||||
KillColor string `yaml:"killColor"`
|
||||
CompletedColor string `yaml:"completedColor"`
|
||||
NewColor Color `yaml:"newColor"`
|
||||
ModifyColor Color `yaml:"modifyColor"`
|
||||
AddColor Color `yaml:"addColor"`
|
||||
ErrorColor Color `yaml:"errorColor"`
|
||||
HighlightColor Color `yaml:"highlightColor"`
|
||||
KillColor Color `yaml:"killColor"`
|
||||
CompletedColor Color `yaml:"completedColor"`
|
||||
}
|
||||
|
||||
// Log tracks Log styles.
|
||||
Log struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
}
|
||||
|
||||
// Yaml tracks yaml styles.
|
||||
Yaml struct {
|
||||
KeyColor string `yaml:"keyColor"`
|
||||
ValueColor string `yaml:"valueColor"`
|
||||
ColonColor string `yaml:"colonColor"`
|
||||
KeyColor Color `yaml:"keyColor"`
|
||||
ValueColor Color `yaml:"valueColor"`
|
||||
ColonColor Color `yaml:"colonColor"`
|
||||
}
|
||||
|
||||
// Title tracks title styles.
|
||||
Title struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
HighlightColor string `yaml:"highlightColor"`
|
||||
CounterColor string `yaml:"counterColor"`
|
||||
FilterColor string `yaml:"filterColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
HighlightColor Color `yaml:"highlightColor"`
|
||||
CounterColor Color `yaml:"counterColor"`
|
||||
FilterColor Color `yaml:"filterColor"`
|
||||
}
|
||||
|
||||
// Info tracks info styles.
|
||||
Info struct {
|
||||
SectionColor string `yaml:"sectionColor"`
|
||||
FgColor string `yaml:"fgColor"`
|
||||
SectionColor Color `yaml:"sectionColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
}
|
||||
|
||||
// Border tracks border styles.
|
||||
Border struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
FocusColor string `yaml:"focusColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
FocusColor Color `yaml:"focusColor"`
|
||||
}
|
||||
|
||||
// Crumb tracks crumbs styles.
|
||||
Crumb struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
ActiveColor string `yaml:"activeColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
ActiveColor Color `yaml:"activeColor"`
|
||||
}
|
||||
|
||||
// Table tracks table styles.
|
||||
Table struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
CursorColor string `yaml:"cursorColor"`
|
||||
MarkColor string `yaml:"markColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
CursorColor Color `yaml:"cursorColor"`
|
||||
MarkColor Color `yaml:"markColor"`
|
||||
Header TableHeader `yaml:"header"`
|
||||
}
|
||||
|
||||
// TableHeader tracks table header styles.
|
||||
TableHeader struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
SorterColor string `yaml:"sorterColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
SorterColor Color `yaml:"sorterColor"`
|
||||
}
|
||||
|
||||
// Xray tracks xray styles.
|
||||
Xray struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
CursorColor string `yaml:"cursorColor"`
|
||||
GraphicColor string `yaml:"graphicColor"`
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
CursorColor Color `yaml:"cursorColor"`
|
||||
GraphicColor Color `yaml:"graphicColor"`
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
}
|
||||
|
||||
// Menu tracks menu styles.
|
||||
Menu struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
KeyColor string `yaml:"keyColor"`
|
||||
NumKeyColor string `yaml:"numKeyColor"`
|
||||
FgColor Color `yaml:"fgColor"`
|
||||
KeyColor Color `yaml:"keyColor"`
|
||||
NumKeyColor Color `yaml:"numKeyColor"`
|
||||
}
|
||||
|
||||
// Style tracks K9s styles.
|
||||
Style struct {
|
||||
Body Body `yaml:"body"`
|
||||
Frame Frame `yaml:"frame"`
|
||||
Info Info `yaml:"info"`
|
||||
Table Table `yaml:"table"`
|
||||
Xray Xray `yaml:"xray"`
|
||||
Views Views `yaml:"views"`
|
||||
// Charts tracks charts styles.
|
||||
Charts struct {
|
||||
BgColor Color `yaml:"bgColor"`
|
||||
DialBgColor Color `yaml:"dialBgColor"`
|
||||
ChartBgColor Color `yaml:"chartBgColor"`
|
||||
DefaultDialColors Colors `yaml:"defaultDialColors"`
|
||||
DefaultChartColors Colors `yaml:"defaultChartColors"`
|
||||
ResourceColors map[string]Colors `yaml:"resourceColors"`
|
||||
}
|
||||
)
|
||||
|
||||
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 {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// Color returns a view color.
|
||||
func (c Color) Color() tcell.Color {
|
||||
if c == DefaultColor {
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
if color, ok := tcell.ColorNames[c.String()]; ok {
|
||||
return color
|
||||
}
|
||||
return tcell.GetColor(c.String())
|
||||
}
|
||||
|
||||
// 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(),
|
||||
Frame: newFrame(),
|
||||
Info: newInfo(),
|
||||
Table: newTable(),
|
||||
Views: newViews(),
|
||||
Xray: newXray(),
|
||||
}
|
||||
}
|
||||
|
||||
func newCharts() Charts {
|
||||
return Charts{
|
||||
BgColor: "default",
|
||||
DialBgColor: "default",
|
||||
ChartBgColor: "default",
|
||||
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
}
|
||||
}
|
||||
func newViews() Views {
|
||||
return Views{
|
||||
Yaml: newYaml(),
|
||||
Log: newLog(),
|
||||
Table: newTable(),
|
||||
Xray: newXray(),
|
||||
Charts: newCharts(),
|
||||
Yaml: newYaml(),
|
||||
Log: newLog(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +253,7 @@ func newStatus() Status {
|
|||
ErrorColor: "orangered",
|
||||
HighlightColor: "aqua",
|
||||
KillColor: "mediumpurple",
|
||||
CompletedColor: "gray",
|
||||
CompletedColor: "lightgray",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,14 +357,24 @@ func NewStyles() *Styles {
|
|||
}
|
||||
}
|
||||
|
||||
// Reset resets styles.
|
||||
func (s *Styles) Reset() {
|
||||
s.K9s = newStyle()
|
||||
}
|
||||
|
||||
// DefaultSkin loads the default skin
|
||||
func (s *Styles) DefaultSkin() {
|
||||
s.K9s = newStyle()
|
||||
}
|
||||
|
||||
// FgColor returns the foreground color.
|
||||
func (s *Styles) FgColor() tcell.Color {
|
||||
return AsColor(s.Body().FgColor)
|
||||
return s.Body().FgColor.Color()
|
||||
}
|
||||
|
||||
// BgColor returns the background color.
|
||||
func (s *Styles) BgColor() tcell.Color {
|
||||
return AsColor(s.Body().BgColor)
|
||||
return s.Body().BgColor.Color()
|
||||
}
|
||||
|
||||
// AddListener registers a new listener.
|
||||
|
|
@ -348,14 +423,19 @@ func (s *Styles) Title() Title {
|
|||
return s.Frame().Title
|
||||
}
|
||||
|
||||
// Charts returns charts styles.
|
||||
func (s *Styles) Charts() Charts {
|
||||
return s.K9s.Views.Charts
|
||||
}
|
||||
|
||||
// Table returns table styles.
|
||||
func (s *Styles) Table() Table {
|
||||
return s.K9s.Table
|
||||
return s.K9s.Views.Table
|
||||
}
|
||||
|
||||
// Xray returns xray styles.
|
||||
func (s *Styles) Xray() Xray {
|
||||
return s.K9s.Xray
|
||||
return s.K9s.Views.Xray
|
||||
}
|
||||
|
||||
// Views returns views styles.
|
||||
|
|
@ -383,15 +463,7 @@ func (s *Styles) Update() {
|
|||
tview.Styles.PrimitiveBackgroundColor = s.BgColor()
|
||||
tview.Styles.ContrastBackgroundColor = s.BgColor()
|
||||
tview.Styles.PrimaryTextColor = s.FgColor()
|
||||
tview.Styles.BorderColor = AsColor(s.K9s.Frame.Border.FgColor)
|
||||
tview.Styles.FocusColor = AsColor(s.K9s.Frame.Border.FocusColor)
|
||||
}
|
||||
|
||||
// AsColor checks color index, if match return color otherwise pink it is.
|
||||
func AsColor(c string) tcell.Color {
|
||||
if color, ok := tcell.ColorNames[c]; ok {
|
||||
return color
|
||||
}
|
||||
|
||||
return tcell.GetColor(c)
|
||||
tview.Styles.BorderColor = s.K9s.Frame.Border.FgColor.Color()
|
||||
tview.Styles.FocusColor = s.K9s.Frame.Border.FocusColor.Color()
|
||||
s.fireStylesChanged()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAsColor(t *testing.T) {
|
||||
func TestColor(t *testing.T) {
|
||||
uu := map[string]tcell.Color{
|
||||
"blah": tcell.ColorDefault,
|
||||
"blue": tcell.ColorBlue,
|
||||
|
|
@ -20,19 +20,19 @@ func TestAsColor(t *testing.T) {
|
|||
for k := range uu {
|
||||
c, u := k, uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u, config.AsColor(c))
|
||||
assert.Equal(t, u, config.NewColor(c).Color())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkinNone(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.Nil(t, s.Load("test_assets/empty_skin.yml"))
|
||||
assert.Nil(t, s.Load("testdata/empty_skin.yml"))
|
||||
s.Update()
|
||||
|
||||
assert.Equal(t, "cadetblue", s.Body().FgColor)
|
||||
assert.Equal(t, "black", s.Body().BgColor)
|
||||
assert.Equal(t, "black", s.Table().BgColor)
|
||||
assert.Equal(t, "cadetblue", s.Body().FgColor.String())
|
||||
assert.Equal(t, "black", s.Body().BgColor.String())
|
||||
assert.Equal(t, "black", s.Table().BgColor.String())
|
||||
assert.Equal(t, tcell.ColorCadetBlue, s.FgColor())
|
||||
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
||||
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
|
||||
|
|
@ -40,12 +40,12 @@ func TestSkinNone(t *testing.T) {
|
|||
|
||||
func TestSkin(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.Nil(t, s.Load("test_assets/black_and_wtf.yml"))
|
||||
assert.Nil(t, s.Load("testdata/black_and_wtf.yml"))
|
||||
s.Update()
|
||||
|
||||
assert.Equal(t, "white", s.Body().FgColor)
|
||||
assert.Equal(t, "black", s.Body().BgColor)
|
||||
assert.Equal(t, "black", s.Table().BgColor)
|
||||
assert.Equal(t, "white", s.Body().FgColor.String())
|
||||
assert.Equal(t, "black", s.Body().BgColor.String())
|
||||
assert.Equal(t, "black", s.Table().BgColor.String())
|
||||
assert.Equal(t, tcell.ColorWhite, s.FgColor())
|
||||
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
||||
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
|
||||
|
|
@ -53,10 +53,10 @@ func TestSkin(t *testing.T) {
|
|||
|
||||
func TestSkinNotExits(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.NotNil(t, s.Load("test_assets/blee.yml"))
|
||||
assert.NotNil(t, s.Load("testdata/blee.yml"))
|
||||
}
|
||||
|
||||
func TestSkinBoarked(t *testing.T) {
|
||||
s := config.NewStyles()
|
||||
assert.NotNil(t, s.Load("test_assets/skin_boarked.yml"))
|
||||
assert.NotNil(t, s.Load("testdata/skin_boarked.yml"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ func TestBenchmarkList(t *testing.T) {
|
|||
a := dao.Benchmark{}
|
||||
a.Init(makeFactory(), client.NewGVR("benchmarks"))
|
||||
|
||||
ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench")
|
||||
ctx := context.WithValue(context.Background(), internal.KeyDir, "testdata/bench")
|
||||
oo, err := a.List(ctx, "-")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(oo))
|
||||
assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
|
||||
assert.Equal(t, "testdata/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ func (c *Chart) ToYAML(path string) (string, error) {
|
|||
|
||||
// Delete uninstall a Chart.
|
||||
func (c *Chart) Delete(path string, cascade, force bool) error {
|
||||
log.Debug().Msgf("CHART DELETE %q", path)
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -33,23 +33,21 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("no context path for %q", c.gvr)
|
||||
}
|
||||
|
||||
var (
|
||||
pmx *mv1beta1.PodMetrics
|
||||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(c.Client()).FetchPodMetrics(fqn); err != nil {
|
||||
log.Warn().Err(err).Msgf("No metrics found for pod %q", fqn)
|
||||
}
|
||||
}
|
||||
|
||||
po, err := c.fetchPod(fqn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pmx *mv1beta1.PodMetrics
|
||||
if c.Client().HasMetrics() {
|
||||
mx := client.NewMetricsServer(c.Client())
|
||||
if c.Client() != nil {
|
||||
var err error
|
||||
pmx, err = mx.FetchPodMetrics(fqn)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No metrics found for pod %q", fqn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers))
|
||||
for _, co := range po.Spec.InitContainers {
|
||||
res = append(res, makeContainerRes(co, po, pmx, true))
|
||||
|
|
@ -62,7 +60,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
}
|
||||
|
||||
// TailLogs tails a given container logs
|
||||
func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error {
|
||||
func (c *Container) TailLogs(ctx context.Context, logChan chan<- []byte, opts LogOptions) error {
|
||||
fac, ok := ctx.Value(internal.KeyFactory).(Factory)
|
||||
if !ok {
|
||||
return errors.New("Expecting an informer")
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func makeConn() *conn {
|
|||
|
||||
func (c *conn) Config() *client.Config { return nil }
|
||||
func (c *conn) DialOrDie() kubernetes.Interface { return nil }
|
||||
func (c *conn) SwitchContextOrDie(ctx string) {}
|
||||
func (c *conn) SwitchContext(ctx string) error { return nil }
|
||||
func (c *conn) CachedDiscoveryOrDie() *disk.CachedDiscoveryClient { return nil }
|
||||
func (c *conn) RestConfigOrDie() *restclient.Config { return nil }
|
||||
func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ func (c *Context) MustCurrentContextName() string {
|
|||
|
||||
// Switch to another context.
|
||||
func (c *Context) Switch(ctx string) error {
|
||||
c.Factory.Client().SwitchContextOrDie(ctx)
|
||||
return nil
|
||||
return c.Factory.Client().SwitchContext(ctx)
|
||||
}
|
||||
|
||||
// KubeUpdate modifies kubeconfig default context.
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ type CustomResourceDefinition struct {
|
|||
// List returns a collection of nodes.
|
||||
func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
strLabel, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
lsel := labels.Everything()
|
||||
labelSel := labels.Everything()
|
||||
if sel, e := labels.ConvertSelectorToLabelsMap(strLabel); ok && e == nil {
|
||||
lsel = sel.AsSelector()
|
||||
labelSel = sel.AsSelector()
|
||||
}
|
||||
|
||||
const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
|
||||
return c.Factory.List(gvr, "-", true, lsel)
|
||||
return c.Factory.List(gvr, "-", true, labelSel)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func TestCruiserSlice(t *testing.T) {
|
|||
// Helpers...
|
||||
|
||||
func loadJSON(t assert.TestingT, n string) *unstructured.Unstructured {
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("test_assets/%s.json", n))
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.json", n))
|
||||
assert.Nil(t, err)
|
||||
|
||||
var o unstructured.Unstructured
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ var (
|
|||
_ Loggable = (*Deployment)(nil)
|
||||
_ Restartable = (*Deployment)(nil)
|
||||
_ Scalable = (*Deployment)(nil)
|
||||
_ Controller = (*Deployment)(nil)
|
||||
)
|
||||
|
||||
// Deployment represents a deployment K8s resource.
|
||||
|
|
@ -28,6 +29,11 @@ type Deployment struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (d *Deployment) IsHappy(dp appsv1.Deployment) bool {
|
||||
return dp.Status.Replicas == dp.Status.AvailableReplicas
|
||||
}
|
||||
|
||||
// Scale a Deployment.
|
||||
func (d *Deployment) Scale(path string, replicas int32) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
|
|
@ -51,12 +57,7 @@ func (d *Deployment) Scale(path string, replicas int32) error {
|
|||
|
||||
// Restart a Deployment rollout.
|
||||
func (d *Deployment) Restart(path string) error {
|
||||
o, err := d.Factory.Get(d.gvr.String(), path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ds appsv1.Deployment
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||
dp, err := d.GetInstance(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -69,30 +70,50 @@ func (d *Deployment) Restart(path string) error {
|
|||
if !auth {
|
||||
return fmt.Errorf("user is not authorized to restart a deployment")
|
||||
}
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(dp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
|
||||
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(dp.Namespace).Patch(dp.Name, types.StrategicMergePatchType, update)
|
||||
return err
|
||||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Deployment.
|
||||
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything())
|
||||
func (d *Deployment) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
dp, err := d.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dp appsv1.Deployment
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
|
||||
if err != nil {
|
||||
return errors.New("expecting Deployment resource")
|
||||
}
|
||||
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("No valid selector found on Deployment %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
func (d *Deployment) Pod(fqn string) (string, error) {
|
||||
dp, err := d.GetInstance(fqn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return podFromSelector(d.Factory, dp.Namespace, dp.Spec.Selector.MatchLabels)
|
||||
}
|
||||
|
||||
// GetInstance returns a deployment instance.
|
||||
func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
|
||||
o, err := d.Factory.Get(d.gvr.String(), fqn, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dp appsv1.Deployment
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
|
||||
if err != nil {
|
||||
return nil, errors.New("expecting Deployment resource")
|
||||
}
|
||||
|
||||
return &dp, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -25,6 +24,7 @@ var (
|
|||
_ Nuker = (*DaemonSet)(nil)
|
||||
_ Loggable = (*DaemonSet)(nil)
|
||||
_ Restartable = (*DaemonSet)(nil)
|
||||
_ Controller = (*DaemonSet)(nil)
|
||||
)
|
||||
|
||||
// DaemonSet represents a K8s daemonset.
|
||||
|
|
@ -32,14 +32,14 @@ type DaemonSet struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool {
|
||||
return ds.Status.DesiredNumberScheduled == ds.Status.CurrentNumberScheduled
|
||||
}
|
||||
|
||||
// Restart a DaemonSet rollout.
|
||||
func (d *DaemonSet) Restart(path string) error {
|
||||
o, err := d.Factory.Get(d.gvr.String(), path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ds appsv1.DaemonSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||
ds, err := d.GetInstance(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -51,26 +51,21 @@ func (d *DaemonSet) Restart(path string) error {
|
|||
if !auth {
|
||||
return fmt.Errorf("user is not authorized to restart a daemonset")
|
||||
}
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
|
||||
|
||||
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
|
||||
return err
|
||||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this DaemonSet.
|
||||
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything())
|
||||
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
ds, err := d.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ds appsv1.DaemonSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||
if err != nil {
|
||||
return errors.New("expecting daemonset resource")
|
||||
}
|
||||
|
||||
if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 {
|
||||
return fmt.Errorf("no valid selector found on daemonset %q", opts.Path)
|
||||
|
|
@ -79,7 +74,7 @@ func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptio
|
|||
return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
|
||||
func podLogs(ctx context.Context, c chan<- []byte, sel map[string]string, opts LogOptions) error {
|
||||
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
||||
if !ok {
|
||||
return errors.New("expecting a context factory")
|
||||
|
|
@ -94,7 +89,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
|
|||
}
|
||||
|
||||
ns, _ := client.Namespaced(opts.Path)
|
||||
oo, err := f.List("v1/pods", ns, true, lsel)
|
||||
oo, err := f.List("v1/pods", ns, false, lsel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -111,7 +106,6 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug().Msgf("TAILING logs on pod %q", pod.Name)
|
||||
opts.Path = client.FQN(pod.Namespace, pod.Name)
|
||||
if err := po.TailLogs(ctx, c, opts); err != nil {
|
||||
return err
|
||||
|
|
@ -120,6 +114,32 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
|
|||
return nil
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
func (d *DaemonSet) Pod(fqn string) (string, error) {
|
||||
ds, err := d.GetInstance(fqn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return podFromSelector(d.Factory, ds.Namespace, ds.Spec.Selector.MatchLabels)
|
||||
}
|
||||
|
||||
// GetInstance returns a daemonset instance.
|
||||
func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
|
||||
o, err := d.Factory.Get(d.gvr.String(), fqn, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ds appsv1.DaemonSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||
if err != nil {
|
||||
return nil, errors.New("expecting DaemonSet resource")
|
||||
}
|
||||
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ type Generic struct {
|
|||
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
labelSel, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
if !ok {
|
||||
log.Warn().Msgf("No label selector found in context. Listing all resources")
|
||||
log.Debug().Msgf("No label selector found in context. Listing all resources")
|
||||
}
|
||||
if client.IsAllNamespace(ns) {
|
||||
ns = client.AllNamespaces
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ func ToYAML(o runtime.Object) (string, error) {
|
|||
if o == nil {
|
||||
return "", errors.New("no object to yamlize")
|
||||
}
|
||||
|
||||
var (
|
||||
buff bytes.Buffer
|
||||
p printers.YAMLPrinter
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ type Job struct {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Job.
|
||||
func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
func (j *Job) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
o, err := j.Factory.Get(j.gvr.String(), opts.Path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -47,19 +47,26 @@ func colorize(c color.Paint, txt string) string {
|
|||
}
|
||||
|
||||
// DecorateLog add a log header to display po/co information along with the log message.
|
||||
func (o LogOptions) DecorateLog(msg string) string {
|
||||
_, n := client.Namespaced(o.Path)
|
||||
if msg == "" {
|
||||
return msg
|
||||
func (o LogOptions) DecorateLog(bytes []byte) []byte {
|
||||
if len(bytes) == 0 {
|
||||
return bytes
|
||||
}
|
||||
|
||||
bytes = bytes[:len(bytes)-1]
|
||||
_, n := client.Namespaced(o.Path)
|
||||
|
||||
var prefix []byte
|
||||
if o.MultiPods {
|
||||
return colorize(o.Color, n+":"+o.Container+" ") + msg
|
||||
prefix = []byte(colorize(o.Color, n+":"+o.Container+" "))
|
||||
}
|
||||
|
||||
if !o.SingleContainer {
|
||||
return colorize(o.Color, o.Container+" ") + msg
|
||||
prefix = []byte(colorize(o.Color, o.Container+" "))
|
||||
}
|
||||
|
||||
return msg
|
||||
if len(prefix) == 0 {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return append(prefix, bytes...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,19 @@ type Node struct {
|
|||
|
||||
// List returns a collection of node resources.
|
||||
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
log.Debug().Msgf("NODE-LIST %q:%q", ns, n.gvr)
|
||||
|
||||
labels, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
if !ok {
|
||||
log.Warn().Msgf("No label selector found in context")
|
||||
}
|
||||
|
||||
mx := client.NewMetricsServer(n.Client())
|
||||
nmx, err := mx.FetchNodesMetrics()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No node metrics")
|
||||
var (
|
||||
nmx *mv1beta1.NodeMetricsList
|
||||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if nmx, err = client.DialMetrics(n.Client()).FetchNodesMetrics(); err != nil {
|
||||
log.Warn().Err(err).Msgf("No node metrics")
|
||||
}
|
||||
}
|
||||
|
||||
nn, err := FetchNodes(n.Factory, labels)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/openfaas/faas-cli/proxy"
|
||||
"github.com/openfaas/faas/gateway/requests"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
oFaasGatewayEnv = "OPENFAAS_GATEWAY"
|
||||
oFaasJWTTokenEnv = "OPENFAAS_JWT_TOKEN"
|
||||
oFaasTLSInsecure = "OPENFAAS_TLS_INSECURE"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Accessor = (*OpenFaas)(nil)
|
||||
_ Nuker = (*OpenFaas)(nil)
|
||||
_ Describer = (*OpenFaas)(nil)
|
||||
)
|
||||
|
||||
// OpenFaas represents a faas gateway connection.
|
||||
type OpenFaas struct {
|
||||
NonResource
|
||||
}
|
||||
|
||||
// IsOpenFaasEnabled returns true if a gateway url is set in the environment.
|
||||
func IsOpenFaasEnabled() bool {
|
||||
return os.Getenv(oFaasGatewayEnv) != ""
|
||||
}
|
||||
|
||||
func getOpenFAASFlags() (string, string, bool) {
|
||||
gw, token := os.Getenv(oFaasGatewayEnv), os.Getenv(oFaasJWTTokenEnv)
|
||||
tlsInsecure := false
|
||||
if os.Getenv(oFaasTLSInsecure) == "true" {
|
||||
tlsInsecure = true
|
||||
}
|
||||
|
||||
return gw, token, tlsInsecure
|
||||
}
|
||||
|
||||
// Get returns a function by name.
|
||||
func (f *OpenFaas) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
|
||||
oo, err := f.List(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var found runtime.Object
|
||||
for _, o := range oo {
|
||||
r, ok := o.(render.OpenFaasRes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if r.Function.Name == n {
|
||||
found = o
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found == nil {
|
||||
return nil, fmt.Errorf("unable to locate function %q", path)
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// List returns a collection of functions.
|
||||
func (f *OpenFaas) List(_ context.Context, ns string) ([]runtime.Object, error) {
|
||||
if !IsOpenFaasEnabled() {
|
||||
return nil, errors.New("OpenFAAS is not enabled on this cluster")
|
||||
}
|
||||
|
||||
gw, token, tls := getOpenFAASFlags()
|
||||
ff, err := proxy.ListFunctionsToken(gw, tls, token, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oo := make([]runtime.Object, 0, len(ff))
|
||||
for _, f := range ff {
|
||||
oo = append(oo, render.OpenFaasRes{Function: f})
|
||||
}
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
|
||||
// Delete removes a function.
|
||||
func (f *OpenFaas) Delete(path string, _, _ bool) error {
|
||||
gw, token, tls := getOpenFAASFlags()
|
||||
ns, n := client.Namespaced(path)
|
||||
|
||||
// BOZO!! openfaas spews to stdout. Not good for us...
|
||||
return deleteFunctionToken(gw, n, tls, token, ns)
|
||||
}
|
||||
|
||||
// ToYAML dumps a function to yaml.
|
||||
func (f *OpenFaas) ToYAML(path string) (string, error) {
|
||||
return f.Describe(path)
|
||||
}
|
||||
|
||||
// Describe describes a function.
|
||||
func (f *OpenFaas) Describe(path string) (string, error) {
|
||||
o, err := f.Get(context.Background(), path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fn, ok := o.(render.OpenFaasRes)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("expecting OpenFaasRes but got %T", o)
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(fn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bytes, err := yaml.JSONToYAML(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// BOZO!! Meow! openfaas fn prints to stdout have to dup ;(
|
||||
func deleteFunctionToken(gateway string, functionName string, tlsInsecure bool, token string, namespace string) error {
|
||||
defaultCommandTimeout := 60 * time.Second
|
||||
|
||||
gateway = strings.TrimRight(gateway, "/")
|
||||
delReq := requests.DeleteFunctionRequest{FunctionName: functionName}
|
||||
reqBytes, _ := json.Marshal(&delReq)
|
||||
reader := bytes.NewReader(reqBytes)
|
||||
|
||||
c := proxy.MakeHTTPClient(&defaultCommandTimeout, tlsInsecure)
|
||||
|
||||
deleteEndpoint, err := createSystemEndpoint(gateway, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", deleteEndpoint, reader)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if len(token) > 0 {
|
||||
proxy.SetToken(req, token)
|
||||
} else {
|
||||
proxy.SetAuth(req, gateway)
|
||||
}
|
||||
|
||||
delRes, delErr := c.Do(req)
|
||||
|
||||
if delErr != nil {
|
||||
fmt.Printf("Error removing existing function: %s, gateway=%s, functionName=%s\n", delErr.Error(), gateway, functionName)
|
||||
return delErr
|
||||
}
|
||||
|
||||
if delRes.Body != nil {
|
||||
defer func() {
|
||||
if err := delRes.Body.Close(); err != nil {
|
||||
log.Error().Err(err).Msgf("closing delete-gtw body")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
switch delRes.StatusCode {
|
||||
case http.StatusOK, http.StatusCreated, http.StatusAccepted:
|
||||
return nil
|
||||
case http.StatusNotFound:
|
||||
return fmt.Errorf("no function named %s found", functionName)
|
||||
case http.StatusUnauthorized:
|
||||
return fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server")
|
||||
default:
|
||||
bytesOut, err := ioutil.ReadAll(delRes.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("server returned unexpected status code %d %s", delRes.StatusCode, string(bytesOut))
|
||||
}
|
||||
}
|
||||
|
||||
func createSystemEndpoint(gateway, namespace string) (string, error) {
|
||||
const systemPath = "/system/functions"
|
||||
|
||||
gatewayURL, err := url.Parse(gateway)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid gateway URL: %s", err.Error())
|
||||
}
|
||||
gatewayURL.Path = path.Join(gatewayURL.Path, systemPath)
|
||||
if len(namespace) > 0 {
|
||||
q := gatewayURL.Query()
|
||||
q.Set("namespace", namespace)
|
||||
gatewayURL.RawQuery = q.Encode()
|
||||
}
|
||||
return gatewayURL.String(), nil
|
||||
}
|
||||
|
|
@ -27,9 +27,10 @@ import (
|
|||
const defaultTimeout = 1 * time.Second
|
||||
|
||||
var (
|
||||
_ Accessor = (*Pod)(nil)
|
||||
_ Nuker = (*Pod)(nil)
|
||||
_ Loggable = (*Pod)(nil)
|
||||
_ Accessor = (*Pod)(nil)
|
||||
_ Nuker = (*Pod)(nil)
|
||||
_ Loggable = (*Pod)(nil)
|
||||
_ Controller = (*Pod)(nil)
|
||||
)
|
||||
|
||||
// Pod represents a pod resource.
|
||||
|
|
@ -37,6 +38,16 @@ type Pod struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (p *Pod) IsHappy(po v1.Pod) bool {
|
||||
for _, c := range po.Status.Conditions {
|
||||
if c.Status == v1.ConditionFalse {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get returns a resource instance if found, else an error.
|
||||
func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
o, err := p.Resource.Get(ctx, path)
|
||||
|
|
@ -49,11 +60,11 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
|
||||
}
|
||||
|
||||
// No Deal!
|
||||
mx := client.NewMetricsServer(p.Client())
|
||||
pmx, err := mx.FetchPodMetrics(path)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No pods metrics")
|
||||
var pmx *mv1beta1.PodMetrics
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodMetrics(path); err != nil {
|
||||
log.Warn().Err(err).Msgf("No pod metrics")
|
||||
}
|
||||
}
|
||||
|
||||
return &render.PodWithMetrics{Raw: u, MX: pmx}, nil
|
||||
|
|
@ -76,10 +87,11 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
return oo, err
|
||||
}
|
||||
|
||||
mx := client.NewMetricsServer(p.Client())
|
||||
pmx, err := mx.FetchPodsMetrics(ns)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No pods metrics")
|
||||
var pmx *mv1beta1.PodMetricsList
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodsMetrics(ns); err != nil {
|
||||
log.Warn().Err(err).Msgf("No pods metrics")
|
||||
}
|
||||
}
|
||||
|
||||
var res []runtime.Object
|
||||
|
|
@ -122,18 +134,12 @@ func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, er
|
|||
|
||||
// Containers returns all container names on pod
|
||||
func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
|
||||
o, err := p.Factory.Get(p.gvr.String(), path, true, labels.Everything())
|
||||
pod, err := p.GetInstance(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pod v1.Pod
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := []string{}
|
||||
cc := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||
for _, c := range pod.Spec.Containers {
|
||||
cc = append(cc, c.Name)
|
||||
}
|
||||
|
|
@ -147,15 +153,36 @@ func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
|
|||
return cc, nil
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
func (p *Pod) Pod(fqn string) (string, error) {
|
||||
return fqn, nil
|
||||
}
|
||||
|
||||
// GetInstance returns a pod instance.
|
||||
func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) {
|
||||
o, err := p.Factory.Get(p.gvr.String(), fqn, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pod v1.Pod
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pod, nil
|
||||
}
|
||||
|
||||
// TailLogs tails a given container logs
|
||||
func (p *Pod) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
func (p *Pod) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
if !opts.HasContainer() {
|
||||
return p.logs(ctx, c, opts)
|
||||
}
|
||||
return tailLogs(ctx, p, c, opts)
|
||||
}
|
||||
|
||||
func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
func (p *Pod) logs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
||||
if !ok {
|
||||
return errors.New("Expecting an informer")
|
||||
|
|
@ -194,7 +221,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptions) error {
|
||||
func tailLogs(ctx context.Context, logger Logger, c chan<- []byte, opts LogOptions) error {
|
||||
log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container)
|
||||
o := v1.PodLogOptions{
|
||||
Container: opts.Container,
|
||||
|
|
@ -206,11 +233,13 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctxt, cancelFunc := context.WithCancel(ctx)
|
||||
req.Context(ctxt)
|
||||
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
req.Context(ctx)
|
||||
|
||||
var blocked int32 = 1
|
||||
go logsTimeout(cancelFunc, &blocked)
|
||||
go logsTimeout(cancel, &blocked)
|
||||
|
||||
// This call will block if nothing is in the stream!!
|
||||
stream, err := req.Stream()
|
||||
|
|
@ -219,7 +248,7 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
|
|||
log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path)
|
||||
return fmt.Errorf("Unable to obtain log stream for %s", opts.Path)
|
||||
}
|
||||
go readLogs(ctx, stream, c, opts)
|
||||
go readLogs(stream, c, opts)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -232,7 +261,7 @@ func logsTimeout(cancel context.CancelFunc, blocked *int32) {
|
|||
}
|
||||
}
|
||||
|
||||
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) {
|
||||
func readLogs(stream io.ReadCloser, c chan<- []byte, opts LogOptions) {
|
||||
defer func() {
|
||||
log.Debug().Msgf(">>> Closing stream `%s", opts.Path)
|
||||
if err := stream.Close(); err != nil {
|
||||
|
|
@ -240,16 +269,18 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L
|
|||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(stream)
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
r := bufio.NewReader(stream)
|
||||
for {
|
||||
bytes, err := r.ReadBytes('\n')
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Read error")
|
||||
if err != io.EOF {
|
||||
log.Error().Err(err).Msgf("stream reader failed")
|
||||
}
|
||||
return
|
||||
default:
|
||||
c <- opts.DecorateLog(scanner.Text())
|
||||
}
|
||||
c <- opts.DecorateLog(bytes)
|
||||
}
|
||||
log.Error().Msgf("SCAN_ERR %#v", scanner.Err())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package dao
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -40,21 +42,26 @@ func (p *PortForward) Delete(path string, cascade, force bool) error {
|
|||
|
||||
// List returns a collection of screen dumps.
|
||||
func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
config, ok := ctx.Value(internal.KeyBenchCfg).(*config.Bench)
|
||||
benchFile, ok := ctx.Value(internal.KeyBenchCfg).(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no benchconfig found in context")
|
||||
return nil, fmt.Errorf("no bench file found in context")
|
||||
}
|
||||
|
||||
config, err := config.NewBench(benchFile)
|
||||
if err != nil {
|
||||
log.Debug().Msgf("No custom benchmark config file found")
|
||||
}
|
||||
|
||||
cc := config.Benchmarks.Containers
|
||||
oo := make([]runtime.Object, 0, len(p.Factory.Forwarders()))
|
||||
for _, f := range p.Factory.Forwarders() {
|
||||
for k, f := range p.Factory.Forwarders() {
|
||||
cfg := render.BenchCfg{
|
||||
C: config.Benchmarks.Defaults.C,
|
||||
N: config.Benchmarks.Defaults.N,
|
||||
}
|
||||
if config, ok := cc[containerID(f.Path(), f.Container())]; ok {
|
||||
cfg.C, cfg.N = config.C, config.N
|
||||
cfg.Host, cfg.Path = config.HTTP.Host, config.HTTP.Path
|
||||
if cust, ok := cc[PodToKey(k)]; ok {
|
||||
cfg.C, cfg.N = cust.C, cust.N
|
||||
cfg.Host, cfg.Path = cust.HTTP.Host, cust.HTTP.Path
|
||||
}
|
||||
oo = append(oo, render.ForwardRes{
|
||||
Forwarder: f,
|
||||
|
|
@ -68,10 +75,31 @@ func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, err
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// ContainerID computes container ID based on ns/po/co.
|
||||
func containerID(path, co string) string {
|
||||
ns, n := client.Namespaced(path)
|
||||
po := strings.Split(n, "-")[0]
|
||||
var podNameRX = regexp.MustCompile(`\A(.+)\-(\w{10})\-(\w{5})\z`)
|
||||
|
||||
return ns + "/" + po + ":" + co
|
||||
// PodToKey converts a pod path to a generic bench config key
|
||||
func PodToKey(path string) string {
|
||||
tokens := strings.Split(path, ":")
|
||||
ns, po := client.Namespaced(tokens[0])
|
||||
sections := podNameRX.FindStringSubmatch(po)
|
||||
if len(sections) >= 1 {
|
||||
po = sections[1]
|
||||
}
|
||||
return client.FQN(ns, po) + ":" + tokens[1]
|
||||
}
|
||||
|
||||
// BenchConfigFor returns a custom bench spec if defined otherwise returns the default one.
|
||||
func BenchConfigFor(benchFile, path string) config.BenchConfig {
|
||||
def := config.DefaultBenchSpec()
|
||||
cust, err := config.NewBench(benchFile)
|
||||
if err != nil {
|
||||
log.Debug().Msgf("No custom benchmark config file found")
|
||||
return def
|
||||
}
|
||||
if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok {
|
||||
return b
|
||||
}
|
||||
|
||||
def.C, def.N = cust.Benchmarks.Defaults.C, cust.Benchmarks.Defaults.N
|
||||
return def
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package dao_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBenchForConfig(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
file, key string
|
||||
spec config.BenchConfig
|
||||
}{
|
||||
"no_file": {file: "", key: "", spec: config.DefaultBenchSpec()},
|
||||
"spec": {file: "testdata/benchspec.yml", key: "default/nginx-123-456:nginx", spec: config.BenchConfig{
|
||||
C: 2,
|
||||
N: 3000,
|
||||
HTTP: config.HTTP{
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.NotNil(t, u.spec, dao.BenchConfigFor(u.file, u.key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ const localhost = "localhost"
|
|||
|
||||
// PortForwarder tracks a port forward stream.
|
||||
type PortForwarder struct {
|
||||
client.Connection
|
||||
Factory
|
||||
genericclioptions.IOStreams
|
||||
|
||||
stopChan, readyChan chan struct{}
|
||||
|
|
@ -37,11 +37,11 @@ type PortForwarder struct {
|
|||
}
|
||||
|
||||
// NewPortForwarder returns a new port forward streamer.
|
||||
func NewPortForwarder(c client.Connection) *PortForwarder {
|
||||
func NewPortForwarder(f Factory) *PortForwarder {
|
||||
return &PortForwarder{
|
||||
Connection: c,
|
||||
stopChan: make(chan struct{}),
|
||||
readyChan: make(chan struct{}),
|
||||
Factory: f,
|
||||
stopChan: make(chan struct{}),
|
||||
readyChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,12 @@ func (p *PortForwarder) Ports() []string {
|
|||
|
||||
// Path returns the pod resource path.
|
||||
func (p *PortForwarder) Path() string {
|
||||
return p.path + ":" + p.container
|
||||
return PortForwardID(p.path, p.container)
|
||||
}
|
||||
|
||||
// PortForwardID computes port-forward identifier.
|
||||
func PortForwardID(path, co string) string {
|
||||
return path + ":" + co
|
||||
}
|
||||
|
||||
// Container returns the targetes container.
|
||||
|
|
@ -87,19 +92,33 @@ func (p *PortForwarder) FQN() string {
|
|||
return p.path + ":" + p.container
|
||||
}
|
||||
|
||||
// HasPortMapping checks if port mapping is defined for this fwd.
|
||||
func (p *PortForwarder) HasPortMapping(m string) bool {
|
||||
for _, mapping := range p.ports {
|
||||
if mapping == m {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Start initiates a port forward session for a given pod and ports.
|
||||
func (p *PortForwarder) Start(path, co, address string, ports []string) (*portforward.PortForwarder, error) {
|
||||
p.path, p.container, p.ports, p.age = path, co, ports, time.Now()
|
||||
func (p *PortForwarder) Start(path, co string, t client.PortTunnel) (*portforward.PortForwarder, error) {
|
||||
fwds := []string{t.PortMap()}
|
||||
p.path, p.container, p.ports, p.age = path, co, fwds, time.Now()
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
auth, err := p.CanI(ns, "v1/pods", []string{client.GetVerb})
|
||||
auth, err := p.Client().CanI(ns, "v1/pods", []string{client.GetVerb})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !auth {
|
||||
return nil, fmt.Errorf("user is not authorized to get pods")
|
||||
}
|
||||
pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
|
||||
|
||||
var res Pod
|
||||
res.Init(p, client.NewGVR("v1/pods"))
|
||||
pod, err := res.GetInstance(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -107,7 +126,7 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo
|
|||
return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
|
||||
}
|
||||
|
||||
auth, err = p.CanI(ns, "v1/pods:portforward", []string{client.UpdateVerb})
|
||||
auth, err = p.Client().CanI(ns, "v1/pods:portforward", []string{client.UpdateVerb})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -115,7 +134,7 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo
|
|||
return nil, fmt.Errorf("user is not authorized to update portforward")
|
||||
}
|
||||
|
||||
rcfg := p.RestConfigOrDie()
|
||||
rcfg := p.Client().RestConfigOrDie()
|
||||
rcfg.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"}
|
||||
rcfg.APIPath = "/api"
|
||||
codec, _ := codec()
|
||||
|
|
@ -131,11 +150,11 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo
|
|||
Name(n).
|
||||
SubResource("portforward")
|
||||
|
||||
return p.forwardPorts("POST", req.URL(), address, ports)
|
||||
return p.forwardPorts("POST", req.URL(), t.Address, fwds)
|
||||
}
|
||||
|
||||
func (p *PortForwarder) forwardPorts(method string, url *url.URL, address string, ports []string) (*portforward.PortForwarder, error) {
|
||||
cfg, err := p.Config().RESTConfig()
|
||||
cfg, err := p.Client().Config().RESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Pulse tracks pulses.
|
||||
type Pulse struct {
|
||||
NonResource
|
||||
}
|
||||
|
||||
// List lists out pulses.
|
||||
func (h *Pulse) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
return nil, fmt.Errorf("NYI")
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
|||
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
|
||||
client.NewGVR("batch/v1/jobs"): &Job{},
|
||||
client.NewGVR("charts"): &Chart{},
|
||||
client.NewGVR("openfaas"): &OpenFaas{},
|
||||
}
|
||||
|
||||
r, ok := m[gvr]
|
||||
|
|
@ -96,7 +97,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" {
|
||||
if c == "k9s" || c == "helm" || c == "faas" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -135,9 +136,19 @@ func loadNonResource(m ResourceMetas) {
|
|||
loadK9s(m)
|
||||
loadRBAC(m)
|
||||
loadHelm(m)
|
||||
if IsOpenFaasEnabled() {
|
||||
loadOpenFaas(m)
|
||||
}
|
||||
}
|
||||
|
||||
func loadK9s(m ResourceMetas) {
|
||||
m[client.NewGVR("pulses")] = metav1.APIResource{
|
||||
Name: "pulses",
|
||||
Kind: "Pulse",
|
||||
SingularName: "pulses",
|
||||
ShortNames: []string{"hz", "pu"},
|
||||
Categories: []string{"k9s"},
|
||||
}
|
||||
m[client.NewGVR("xrays")] = metav1.APIResource{
|
||||
Name: "xray",
|
||||
Kind: "XRays",
|
||||
|
|
@ -203,6 +214,17 @@ func loadHelm(m ResourceMetas) {
|
|||
}
|
||||
}
|
||||
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func TestExtractString(t *testing.T) {
|
|||
// Helpers...
|
||||
|
||||
func load(t *testing.T, n string) *unstructured.Unstructured {
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("assets/%s.json", n))
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.json", n))
|
||||
assert.Nil(t, err)
|
||||
|
||||
var o unstructured.Unstructured
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
// ReplicaSet represents a replicaset K8s resource.
|
||||
type ReplicaSet struct {
|
||||
Resource
|
||||
}
|
||||
|
||||
// IsHappy check for happy deployments.
|
||||
func (d *ReplicaSet) IsHappy(rs appsv1.ReplicaSet) bool {
|
||||
if rs.Status.Replicas == 0 && rs.Status.Replicas != rs.Status.ReadyReplicas {
|
||||
return false
|
||||
}
|
||||
|
||||
if rs.Status.Replicas != 0 && rs.Status.Replicas != rs.Status.ReadyReplicas {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ var (
|
|||
_ Loggable = (*StatefulSet)(nil)
|
||||
_ Restartable = (*StatefulSet)(nil)
|
||||
_ Scalable = (*StatefulSet)(nil)
|
||||
_ Controller = (*StatefulSet)(nil)
|
||||
)
|
||||
|
||||
// StatefulSet represents a K8s sts.
|
||||
|
|
@ -28,6 +29,11 @@ type StatefulSet struct {
|
|||
Resource
|
||||
}
|
||||
|
||||
// IsHappy check for happy sts.
|
||||
func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool {
|
||||
return sts.Status.Replicas == sts.Status.ReadyReplicas
|
||||
}
|
||||
|
||||
// Scale a StatefulSet.
|
||||
func (s *StatefulSet) Scale(path string, replicas int32) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
|
|
@ -51,12 +57,7 @@ func (s *StatefulSet) Scale(path string, replicas int32) error {
|
|||
|
||||
// Restart a StatefulSet rollout.
|
||||
func (s *StatefulSet) Restart(path string) error {
|
||||
o, err := s.Factory.Get(s.gvr.String(), path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ds appsv1.StatefulSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||
sts, err := s.getStatefulSet(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -70,24 +71,18 @@ func (s *StatefulSet) Restart(path string) error {
|
|||
return fmt.Errorf("user is not authorized to update statefulsets")
|
||||
}
|
||||
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
|
||||
update, err := polymorphichelpers.ObjectRestarterFn(sts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
|
||||
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(sts.Namespace).Patch(sts.Name, types.StrategicMergePatchType, update)
|
||||
return err
|
||||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this StatefulSet.
|
||||
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sts appsv1.StatefulSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sts)
|
||||
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
sts, err := s.getStatefulSet(opts.Path)
|
||||
if err != nil {
|
||||
return errors.New("expecting StatefulSet resource")
|
||||
}
|
||||
|
|
@ -97,3 +92,28 @@ func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOpt
|
|||
|
||||
return podLogs(ctx, c, sts.Spec.Selector.MatchLabels, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
func (s *StatefulSet) Pod(fqn string) (string, error) {
|
||||
sts, err := s.getStatefulSet(fqn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return podFromSelector(s.Factory, sts.Namespace, sts.Spec.Selector.MatchLabels)
|
||||
}
|
||||
|
||||
func (s *StatefulSet) getStatefulSet(fqn string) (*appsv1.StatefulSet, error) {
|
||||
o, err := s.Factory.Get(s.gvr.String(), fqn, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sts appsv1.StatefulSet
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sts)
|
||||
if err != nil {
|
||||
return nil, errors.New("expecting Service resource")
|
||||
}
|
||||
|
||||
return &sts, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
|
@ -12,8 +13,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
_ Accessor = (*Service)(nil)
|
||||
_ Loggable = (*Service)(nil)
|
||||
_ Accessor = (*Service)(nil)
|
||||
_ Loggable = (*Service)(nil)
|
||||
_ Controller = (*Service)(nil)
|
||||
)
|
||||
|
||||
// Service represents a k8s service.
|
||||
|
|
@ -22,20 +24,62 @@ type Service struct {
|
|||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this Service.
|
||||
func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything())
|
||||
func (s *Service) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
|
||||
svc, err := s.GetInstance(opts.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var svc v1.Service
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &svc)
|
||||
if err != nil {
|
||||
return errors.New("expecting Service resource")
|
||||
}
|
||||
|
||||
if svc.Spec.Selector == nil || len(svc.Spec.Selector) == 0 {
|
||||
return fmt.Errorf("no valid selector found on Service %s", opts.Path)
|
||||
}
|
||||
|
||||
return podLogs(ctx, c, svc.Spec.Selector, opts)
|
||||
}
|
||||
|
||||
// Pod returns a pod victim by name.
|
||||
func (s *Service) Pod(fqn string) (string, error) {
|
||||
svc, err := s.GetInstance(fqn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return podFromSelector(s.Factory, svc.Namespace, svc.Spec.Selector)
|
||||
}
|
||||
|
||||
// GetInstance returns a service instance.
|
||||
func (s *Service) GetInstance(fqn string) (*v1.Service, error) {
|
||||
o, err := s.Factory.Get(s.gvr.String(), fqn, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var svc v1.Service
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &svc)
|
||||
if err != nil {
|
||||
return nil, errors.New("expecting Service resource")
|
||||
}
|
||||
|
||||
return &svc, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func podFromSelector(f Factory, ns string, sel map[string]string) (string, error) {
|
||||
oo, err := f.List("v1/pods", ns, true, labels.Set(sel).AsSelector())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(oo) == 0 {
|
||||
return "", fmt.Errorf("no matching pods for %v", sel)
|
||||
}
|
||||
|
||||
var pod v1.Pod
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(oo[0].(*unstructured.Unstructured).Object, &pod)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return client.FQN(pod.Namespace, pod.Name), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -22,7 +23,6 @@ type Table struct {
|
|||
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
|
||||
log.Debug().Msgf("TABLE-GET %q:%q", ns, t.gvr)
|
||||
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||
_, codec := t.codec()
|
||||
|
||||
|
|
@ -46,7 +46,11 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
|
||||
// List all Resources in a given namespace.
|
||||
func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
log.Debug().Msgf("TABLE-LIST %q:%q", ns, t.gvr)
|
||||
labelSel, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
if !ok {
|
||||
log.Debug().Msgf("No label selector found in context. Listing all resources")
|
||||
}
|
||||
|
||||
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||
_, codec := t.codec()
|
||||
|
||||
|
|
@ -58,7 +62,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
SetHeader("Accept", a).
|
||||
Namespace(ns).
|
||||
Resource(t.gvr.R()).
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||
VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec).
|
||||
Do().Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
benchmarks:
|
||||
defaults:
|
||||
concurrency: 2
|
||||
requests: 500
|
||||
containers:
|
||||
default/nginx:nginx:
|
||||
concurrency: 2
|
||||
requests: 3000
|
||||
http:
|
||||
method: GET
|
||||
path: /
|
||||
services:
|
||||
default/nginx:
|
||||
concurrency: 1
|
||||
requests: 666
|
||||
http:
|
||||
method: GET
|
||||
host: 192.168.64.1
|
||||
path: /
|
||||
|
|
@ -73,7 +73,7 @@ type Accessor interface {
|
|||
// Loggable represents resources with logs.
|
||||
type Loggable interface {
|
||||
// TaiLogs streams resource logs.
|
||||
TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error
|
||||
TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error
|
||||
}
|
||||
|
||||
// Describer describes a resource.
|
||||
|
|
@ -91,6 +91,12 @@ type Scalable interface {
|
|||
Scale(path string, replicas int32) error
|
||||
}
|
||||
|
||||
// Controller represents a pod controller.
|
||||
type Controller interface {
|
||||
// Pod returns a pod instance matching the selector.
|
||||
Pod(path string) (string, error)
|
||||
}
|
||||
|
||||
// Nuker represents a resource deleter.
|
||||
type Nuker interface {
|
||||
// Delete removes a resource from the api server.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package health
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Check tracks resource health.
|
||||
type Check struct {
|
||||
Counts
|
||||
|
||||
GVR string
|
||||
}
|
||||
|
||||
// Checks represents a collection of health checks.
|
||||
type Checks []*Check
|
||||
|
||||
// NewCheck returns a new health check.
|
||||
func NewCheck(gvr string) *Check {
|
||||
return &Check{
|
||||
GVR: gvr,
|
||||
Counts: make(Counts),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets a health metric.
|
||||
func (c *Check) Set(l Level, v int) {
|
||||
c.Counts[l] = v
|
||||
}
|
||||
|
||||
// Inc increments a health metric.
|
||||
func (c *Check) Inc(l Level) {
|
||||
c.Counts[l]++
|
||||
}
|
||||
|
||||
// Total stores a metric total.
|
||||
func (c *Check) Total(n int) {
|
||||
c.Counts[Corpus] = n
|
||||
}
|
||||
|
||||
// Tally retrieves a given health metric.
|
||||
func (c *Check) Tally(l Level) int {
|
||||
return c.Counts[l]
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
func (Check) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a container copy.
|
||||
func (c Check) DeepCopyObject() runtime.Object {
|
||||
return c
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package health_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/health"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
var cc health.Checks
|
||||
|
||||
c := health.NewCheck("test")
|
||||
n := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Inc(health.OK)
|
||||
cc = append(cc, c)
|
||||
n++
|
||||
}
|
||||
c.Total(n)
|
||||
|
||||
assert.Equal(t, 10, len(cc))
|
||||
assert.Equal(t, 10, c.Tally(health.Corpus))
|
||||
assert.Equal(t, 10, c.Tally(health.OK))
|
||||
assert.Equal(t, 0, c.Tally(health.Toast))
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package health
|
||||
|
||||
// Level tracks health count categories.
|
||||
type Level int
|
||||
|
||||
const (
|
||||
// Unknown represents no health level.
|
||||
Unknown Level = 1 << iota
|
||||
|
||||
// Corpus tracks total health.
|
||||
Corpus
|
||||
|
||||
// OK tracks healhy.
|
||||
OK
|
||||
|
||||
// Warn tracks health warnings.
|
||||
Warn
|
||||
|
||||
// Toast tracks unhealties.
|
||||
Toast
|
||||
)
|
||||
|
||||
// Message represents a health message.
|
||||
type Message struct {
|
||||
Level Level
|
||||
Message string
|
||||
GVR string
|
||||
FQN string
|
||||
}
|
||||
|
||||
// Messages tracks a collection of messages.
|
||||
type Messages []Message
|
||||
|
||||
// Counts tracks health counts by category.
|
||||
type Counts map[Level]int
|
||||
|
||||
// Vital tracks a resource vitals.
|
||||
type Vital struct {
|
||||
Resource string
|
||||
Total, OK, Toast int
|
||||
}
|
||||
|
||||
// Vitals tracks a collection of resource health.
|
||||
type Vitals []Vital
|
||||
|
|
@ -25,4 +25,6 @@ const (
|
|||
KeyApp ContextKey = "app"
|
||||
KeyStyles ContextKey = "styles"
|
||||
KeyMetrics ContextKey = "metrics"
|
||||
KeyToast ContextKey = "toast"
|
||||
KeyWithMetrics ContextKey = "withMetrics"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type (
|
|||
func NewCluster(f dao.Factory) *Cluster {
|
||||
return &Cluster{
|
||||
factory: f,
|
||||
mx: client.NewMetricsServer(f.Client()),
|
||||
mx: client.DialMetrics(f.Client()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package model_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClusterMetaDelta(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
o, n model.ClusterMeta
|
||||
e bool
|
||||
}{
|
||||
"empty": {
|
||||
o: model.NewClusterMeta(),
|
||||
n: model.NewClusterMeta(),
|
||||
},
|
||||
"same": {
|
||||
o: makeClusterMeta("fred"),
|
||||
n: makeClusterMeta("fred"),
|
||||
},
|
||||
"diff": {
|
||||
o: makeClusterMeta("fred"),
|
||||
n: makeClusterMeta("freddie"),
|
||||
e: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, u.o.Deltas(u.n))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func makeClusterMeta(cluster string) model.ClusterMeta {
|
||||
m := model.NewClusterMeta()
|
||||
m.Cluster = cluster
|
||||
m.Cpu, m.Mem = 10, 20
|
||||
|
||||
return m
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultFlashDelay sets the flash clear delay.
|
||||
DefaultFlashDelay = 3 * time.Second
|
||||
|
||||
// FlashInfo represents an info message.
|
||||
FlashInfo FlashLevel = iota
|
||||
// FlashWarn represents an warning message.
|
||||
FlashWarn
|
||||
// FlashErr represents an error message.
|
||||
FlashErr
|
||||
)
|
||||
|
||||
// LevelMessage tracks an message and severity.
|
||||
type LevelMessage struct {
|
||||
Level FlashLevel
|
||||
Text string
|
||||
}
|
||||
|
||||
func newClearMessage() LevelMessage {
|
||||
return LevelMessage{}
|
||||
}
|
||||
|
||||
// IsClear returns true if message is empty.
|
||||
func (l LevelMessage) IsClear() bool {
|
||||
return l.Text == ""
|
||||
}
|
||||
|
||||
// FlashLevel represents flash message severity.
|
||||
type FlashLevel int
|
||||
|
||||
// FlashChan represents a flash event channel.
|
||||
type FlashChan chan LevelMessage
|
||||
|
||||
// FlashListener represents a text model listener.
|
||||
type FlashListener interface {
|
||||
// FlashChanged notifies the model changed.
|
||||
FlashChanged(FlashLevel, string)
|
||||
|
||||
// FlashCleared notifies when the filter changed.
|
||||
FlashCleared()
|
||||
}
|
||||
|
||||
// Flash represents a flash message model.
|
||||
type Flash struct {
|
||||
msg LevelMessage
|
||||
cancel context.CancelFunc
|
||||
delay time.Duration
|
||||
msgChan chan LevelMessage
|
||||
}
|
||||
|
||||
// NewFlash returns a new instance.
|
||||
func NewFlash(dur time.Duration) *Flash {
|
||||
return &Flash{
|
||||
delay: dur,
|
||||
msgChan: make(FlashChan, 3),
|
||||
}
|
||||
}
|
||||
|
||||
// Channel returns the flash channel.
|
||||
func (f *Flash) Channel() FlashChan {
|
||||
return f.msgChan
|
||||
}
|
||||
|
||||
// Info displays an info flash message.
|
||||
func (f *Flash) Info(msg string) {
|
||||
f.SetMessage(FlashInfo, msg)
|
||||
}
|
||||
|
||||
// Infof displays a formatted info flash message.
|
||||
func (f *Flash) Infof(fmat string, args ...interface{}) {
|
||||
f.Info(fmt.Sprintf(fmat, args...))
|
||||
}
|
||||
|
||||
// Warn displays a warning flash message.
|
||||
func (f *Flash) Warn(msg string) {
|
||||
log.Warn().Msg(msg)
|
||||
f.SetMessage(FlashWarn, msg)
|
||||
}
|
||||
|
||||
// Warnf displays a formatted warning flash message.
|
||||
func (f *Flash) Warnf(fmat string, args ...interface{}) {
|
||||
f.Warn(fmt.Sprintf(fmat, args...))
|
||||
}
|
||||
|
||||
// Err displays an error flash message.
|
||||
func (f *Flash) Err(err error) {
|
||||
log.Error().Msg(err.Error())
|
||||
f.SetMessage(FlashErr, err.Error())
|
||||
}
|
||||
|
||||
// Errf displays a formatted error flash message.
|
||||
func (f *Flash) Errf(fmat string, args ...interface{}) {
|
||||
var err error
|
||||
for _, a := range args {
|
||||
switch e := a.(type) {
|
||||
case error:
|
||||
err = e
|
||||
}
|
||||
}
|
||||
log.Error().Err(err).Msgf(fmat, args...)
|
||||
f.SetMessage(FlashErr, fmt.Sprintf(fmat, args...))
|
||||
}
|
||||
|
||||
// Clear clears the flash message.
|
||||
func (f *Flash) Clear() {
|
||||
f.fireCleared()
|
||||
}
|
||||
|
||||
// SetMessage sets the flash level message.
|
||||
func (f *Flash) SetMessage(level FlashLevel, msg string) {
|
||||
if f.cancel != nil {
|
||||
f.cancel()
|
||||
f.cancel = nil
|
||||
}
|
||||
|
||||
f.setLevelMessage(LevelMessage{Level: level, Text: msg})
|
||||
f.fireFlashChanged()
|
||||
|
||||
var ctx context.Context
|
||||
ctx, f.cancel = context.WithCancel(context.Background())
|
||||
go f.refresh(ctx)
|
||||
}
|
||||
|
||||
func (f *Flash) refresh(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(f.delay):
|
||||
f.fireCleared()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flash) setLevelMessage(msg LevelMessage) {
|
||||
f.msg = msg
|
||||
}
|
||||
|
||||
func (f *Flash) fireFlashChanged() {
|
||||
f.msgChan <- f.msg
|
||||
}
|
||||
|
||||
func (f *Flash) fireCleared() {
|
||||
f.msgChan <- newClearMessage()
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package model_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFlash(t *testing.T) {
|
||||
const delay = 1 * time.Millisecond
|
||||
|
||||
uu := map[string]struct {
|
||||
level model.FlashLevel
|
||||
e string
|
||||
}{
|
||||
"info": {level: model.FlashInfo, e: "blee"},
|
||||
"warn": {level: model.FlashWarn, e: "blee"},
|
||||
"err": {level: model.FlashErr, e: "blee"},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
|
||||
t.Run(k, func(t *testing.T) {
|
||||
f := model.NewFlash(delay)
|
||||
v := newFlash()
|
||||
go v.listen(f.Channel())
|
||||
|
||||
switch u.level {
|
||||
case model.FlashInfo:
|
||||
f.Info(u.e)
|
||||
case model.FlashWarn:
|
||||
f.Warn(u.e)
|
||||
case model.FlashErr:
|
||||
f.Err(errors.New(u.e))
|
||||
}
|
||||
|
||||
time.Sleep(5 * delay)
|
||||
s, _, l, m := v.getMetrics()
|
||||
assert.Equal(t, 1, s)
|
||||
assert.Equal(t, u.level, l)
|
||||
assert.Equal(t, u.e, m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlashBurst(t *testing.T) {
|
||||
const delay = 1 * time.Millisecond
|
||||
|
||||
f := model.NewFlash(delay)
|
||||
v := newFlash()
|
||||
go v.listen(f.Channel())
|
||||
|
||||
count := 5
|
||||
for i := 1; i <= count; i++ {
|
||||
f.Info(fmt.Sprintf("test-%d", i))
|
||||
}
|
||||
|
||||
time.Sleep(2 * delay)
|
||||
s, _, l, m := v.getMetrics()
|
||||
assert.Equal(t, count, s)
|
||||
assert.Equal(t, model.FlashInfo, l)
|
||||
assert.Equal(t, fmt.Sprintf("test-%d", count), m)
|
||||
}
|
||||
|
||||
type flash struct {
|
||||
set, clear int
|
||||
level model.FlashLevel
|
||||
msg string
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
func newFlash() *flash {
|
||||
return &flash{}
|
||||
}
|
||||
|
||||
func (f *flash) getMetrics() (int, int, model.FlashLevel, string) {
|
||||
f.mx.RLock()
|
||||
defer f.mx.RUnlock()
|
||||
return f.set, f.clear, f.level, f.msg
|
||||
}
|
||||
|
||||
func (f *flash) listen(c model.FlashChan) {
|
||||
for m := range c {
|
||||
f.mx.Lock()
|
||||
{
|
||||
if m.IsClear() {
|
||||
f.clear++
|
||||
} else {
|
||||
f.set++
|
||||
f.level, f.msg = m.Level, m.Text
|
||||
}
|
||||
}
|
||||
f.mx.Unlock()
|
||||
}
|
||||
}
|
||||
|
|
@ -31,25 +31,23 @@ type LogsListener interface {
|
|||
|
||||
// Log represents a resource logger.
|
||||
type Log struct {
|
||||
factory dao.Factory
|
||||
lines []string
|
||||
listeners []LogsListener
|
||||
gvr client.GVR
|
||||
logOptions dao.LogOptions
|
||||
cancelFn context.CancelFunc
|
||||
initialized bool
|
||||
mx sync.RWMutex
|
||||
filter string
|
||||
lastSent int
|
||||
factory dao.Factory
|
||||
lines []string
|
||||
listeners []LogsListener
|
||||
gvr client.GVR
|
||||
logOptions dao.LogOptions
|
||||
cancelFn context.CancelFunc
|
||||
mx sync.RWMutex
|
||||
filter string
|
||||
lastSent int
|
||||
}
|
||||
|
||||
// NewLog returns a new model.
|
||||
func NewLog(gvr client.GVR, msg string, opts dao.LogOptions, timeOut time.Duration) *Log {
|
||||
func NewLog(gvr client.GVR, opts dao.LogOptions, timeOut time.Duration) *Log {
|
||||
return &Log{
|
||||
gvr: gvr,
|
||||
logOptions: opts,
|
||||
initialized: true,
|
||||
lines: []string{msg},
|
||||
gvr: gvr,
|
||||
logOptions: opts,
|
||||
lines: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +82,11 @@ func (l *Log) Start() {
|
|||
|
||||
// Stop terminates log tailing.
|
||||
func (l *Log) Stop() {
|
||||
if l.cancelFn == nil {
|
||||
return
|
||||
defer log.Debug().Msgf("<<<< Logger STOPPED!")
|
||||
if l.cancelFn != nil {
|
||||
l.cancelFn()
|
||||
l.cancelFn = nil
|
||||
}
|
||||
log.Debug().Msgf("<<<< Logger STOP!")
|
||||
l.cancelFn()
|
||||
l.cancelFn = nil
|
||||
}
|
||||
|
||||
// Set sets the log lines (for testing only!)
|
||||
|
|
@ -131,7 +128,7 @@ func (l *Log) load() error {
|
|||
ctx = context.WithValue(context.Background(), internal.KeyFactory, l.factory)
|
||||
ctx, l.cancelFn = context.WithCancel(ctx)
|
||||
|
||||
c := make(chan string, 10)
|
||||
c := make(chan []byte, 10)
|
||||
go l.updateLogs(ctx, c)
|
||||
|
||||
accessor, err := dao.AccessorFor(l.factory, l.gvr)
|
||||
|
|
@ -163,8 +160,7 @@ func (l *Log) Append(line string) {
|
|||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
if l.initialized {
|
||||
l.lines, l.initialized, l.lastSent = []string{}, false, 0
|
||||
if l.lines == nil {
|
||||
l.fireLogCleared()
|
||||
}
|
||||
|
||||
|
|
@ -190,20 +186,20 @@ func (l *Log) Notify(timedOut bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *Log) updateLogs(ctx context.Context, c <-chan string) {
|
||||
func (l *Log) updateLogs(ctx context.Context, c <-chan []byte) {
|
||||
defer func() {
|
||||
log.Debug().Msgf("updateLogs view bailing out!")
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case line, ok := <-c:
|
||||
case bytes, ok := <-c:
|
||||
if !ok {
|
||||
log.Debug().Msgf("Closed channel detected. Bailing out...")
|
||||
l.Append(line)
|
||||
l.Append(string(bytes))
|
||||
l.Notify(false)
|
||||
return
|
||||
}
|
||||
l.Append(line)
|
||||
l.Append(string(bytes))
|
||||
var overflow bool
|
||||
l.mx.RLock()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
func TestLogFullBuffer(t *testing.T) {
|
||||
size := 4
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(size), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
@ -60,7 +60,7 @@ func TestLogFilter(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(size), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
@ -89,7 +89,7 @@ func TestLogFilter(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLogStartStop(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
@ -110,7 +110,7 @@ func TestLogStartStop(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLogClear(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
assert.Equal(t, "fred", m.GetPath())
|
||||
assert.Equal(t, "blee", m.GetContainer())
|
||||
|
|
@ -132,7 +132,7 @@ func TestLogClear(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLogBasic(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(2), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(2), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
@ -148,7 +148,7 @@ func TestLogBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLogAppend(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "blah blah", makeLogOpts(4), 5*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 5*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
@ -161,17 +161,17 @@ func TestLogAppend(t *testing.T) {
|
|||
m.Append(d)
|
||||
}
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, []string{}, v.data)
|
||||
assert.Equal(t, []string{"blah blah"}, v.data)
|
||||
|
||||
m.Notify(true)
|
||||
assert.Equal(t, 2, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, data, v.data)
|
||||
assert.Equal(t, append([]string{"blah blah"}, data...), v.data)
|
||||
}
|
||||
|
||||
func TestLogTimedout(t *testing.T) {
|
||||
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond)
|
||||
m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
|
||||
m.Init(makeFactory())
|
||||
|
||||
v := newTestView()
|
||||
|
|
|
|||
|
|
@ -332,12 +332,14 @@ func (mock *MockClusterMeta) SupportsResource(_param0 string) bool {
|
|||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockClusterMeta) SwitchContextOrDie(_param0 string) {
|
||||
func (mock *MockClusterMeta) SwitchContext(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{})
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mock *MockClusterMeta) UserName() (string, error) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/health"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const defaultRefreshRate = 5 * time.Second
|
||||
|
||||
// PulseListener represents a health model listener.
|
||||
type PulseListener interface {
|
||||
// PulseChanged notifies the model data changed.
|
||||
PulseChanged(*health.Check)
|
||||
|
||||
// TreeFailed notifies the health check failed.
|
||||
PulseFailed(error)
|
||||
}
|
||||
|
||||
// Pulse tracks multiple resources health.
|
||||
type Pulse struct {
|
||||
gvr string
|
||||
namespace string
|
||||
inUpdate int32
|
||||
listeners []PulseListener
|
||||
refreshRate time.Duration
|
||||
health *PulseHealth
|
||||
data health.Checks
|
||||
}
|
||||
|
||||
// NewPulse returns a new pulse.
|
||||
func NewPulse(gvr string) *Pulse {
|
||||
return &Pulse{
|
||||
gvr: gvr,
|
||||
refreshRate: defaultRefreshRate,
|
||||
}
|
||||
}
|
||||
|
||||
// Watch monitors pulses.
|
||||
func (p *Pulse) Watch(ctx context.Context) {
|
||||
p.Refresh(ctx)
|
||||
go p.updater(ctx)
|
||||
}
|
||||
|
||||
func (p *Pulse) updater(ctx context.Context) {
|
||||
defer log.Debug().Msgf("Pulse canceled -- %q", p.gvr)
|
||||
|
||||
rate := initRefreshRate
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(rate):
|
||||
rate = p.refreshRate
|
||||
p.refresh(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh update the model now.
|
||||
func (p *Pulse) Refresh(ctx context.Context) {
|
||||
for _, d := range p.data {
|
||||
p.firePulseChanged(d)
|
||||
}
|
||||
p.refresh(ctx)
|
||||
}
|
||||
|
||||
func (p *Pulse) refresh(ctx context.Context) {
|
||||
if !atomic.CompareAndSwapInt32(&p.inUpdate, 0, 1) {
|
||||
log.Debug().Msgf("Dropping update...")
|
||||
return
|
||||
}
|
||||
defer atomic.StoreInt32(&p.inUpdate, 0)
|
||||
|
||||
if err := p.reconcile(ctx); err != nil {
|
||||
log.Error().Err(err).Msg("Reconcile failed")
|
||||
p.firePulseFailed(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) list(ctx context.Context) ([]runtime.Object, error) {
|
||||
f, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory))
|
||||
}
|
||||
if p.health == nil {
|
||||
p.health = NewPulseHealth(f)
|
||||
}
|
||||
ctx = context.WithValue(ctx, internal.KeyFields, "")
|
||||
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
||||
return p.health.List(ctx, p.namespace)
|
||||
}
|
||||
|
||||
func (p *Pulse) reconcile(ctx context.Context) error {
|
||||
oo, err := p.list(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.data = health.Checks{}
|
||||
for _, o := range oo {
|
||||
c, ok := o.(*health.Check)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expecting health check but got %T", o)
|
||||
}
|
||||
p.data = append(p.data, c)
|
||||
p.firePulseChanged(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNamespace returns the model namespace.
|
||||
func (p *Pulse) GetNamespace() string {
|
||||
return p.namespace
|
||||
}
|
||||
|
||||
// SetNamespace sets up model namespace.
|
||||
func (p *Pulse) SetNamespace(ns string) {
|
||||
p.namespace = ns
|
||||
}
|
||||
|
||||
// AddListener adds a listener.
|
||||
func (p *Pulse) AddListener(l PulseListener) {
|
||||
p.listeners = append(p.listeners, l)
|
||||
}
|
||||
|
||||
// RemoveListener delete a listener.
|
||||
func (p *Pulse) RemoveListener(l PulseListener) {
|
||||
victim := -1
|
||||
for i, lis := range p.listeners {
|
||||
if lis == l {
|
||||
victim = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if victim >= 0 {
|
||||
p.listeners = append(p.listeners[:victim], p.listeners[victim+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) firePulseChanged(check *health.Check) {
|
||||
for _, l := range p.listeners {
|
||||
l.PulseChanged(check)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) firePulseFailed(err error) {
|
||||
for _, l := range p.listeners {
|
||||
l.PulseFailed(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/health"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// PulseHealth tracks resources health.
|
||||
type PulseHealth struct {
|
||||
factory dao.Factory
|
||||
}
|
||||
|
||||
// NewPulseHealth returns a new instance.
|
||||
func NewPulseHealth(f dao.Factory) *PulseHealth {
|
||||
return &PulseHealth{
|
||||
factory: f,
|
||||
}
|
||||
}
|
||||
|
||||
// List returns a canned collection of resources health.
|
||||
func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
defer func(t time.Time) {
|
||||
log.Debug().Msgf("PulseHealthCheck %v", time.Since(t))
|
||||
}(time.Now())
|
||||
|
||||
gvrs := []string{
|
||||
"v1/pods",
|
||||
"v1/events",
|
||||
"apps/v1/replicasets",
|
||||
"apps/v1/deployments",
|
||||
"apps/v1/statefulsets",
|
||||
"apps/v1/daemonsets",
|
||||
"batch/v1/jobs",
|
||||
"v1/persistentvolumes",
|
||||
}
|
||||
|
||||
hh := make([]runtime.Object, 0, 10)
|
||||
for _, gvr := range gvrs {
|
||||
c, err := h.check(ctx, ns, gvr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hh = append(hh, c)
|
||||
}
|
||||
|
||||
mm, err := h.checkMetrics()
|
||||
if err != nil {
|
||||
return hh, nil
|
||||
}
|
||||
for _, m := range mm {
|
||||
hh = append(hh, m)
|
||||
}
|
||||
|
||||
return hh, nil
|
||||
}
|
||||
|
||||
func (h *PulseHealth) checkMetrics() (health.Checks, error) {
|
||||
dial := client.DialMetrics(h.factory.Client())
|
||||
nmx, err := dial.FetchNodesMetrics()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching metrics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cpu, mem float64
|
||||
for _, mx := range nmx.Items {
|
||||
cpu += float64(mx.Usage.Cpu().MilliValue())
|
||||
mem += client.ToMB(mx.Usage.Memory().Value())
|
||||
}
|
||||
c1 := health.NewCheck("cpu")
|
||||
c1.Set(health.OK, int(math.Round(cpu)))
|
||||
c2 := health.NewCheck("mem")
|
||||
c2.Set(health.OK, int(math.Round(mem)))
|
||||
|
||||
return health.Checks{c1, c2}, nil
|
||||
}
|
||||
|
||||
func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, error) {
|
||||
meta, ok := Registry[gvr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No meta for %q", gvr)
|
||||
}
|
||||
if meta.DAO == nil {
|
||||
meta.DAO = &dao.Resource{}
|
||||
}
|
||||
|
||||
meta.DAO.Init(h.factory, client.NewGVR(gvr))
|
||||
oo, err := meta.DAO.List(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := health.NewCheck(gvr)
|
||||
c.Total(len(oo))
|
||||
rr, re := make(render.Rows, len(oo)), meta.Renderer
|
||||
for i, o := range oo {
|
||||
if err := re.Render(o, ns, &rr[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !render.Happy(ns, rr[i]) {
|
||||
c.Inc(health.Toast)
|
||||
} else {
|
||||
c.Inc(health.OK)
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue