Merge branch 'master' into attach

mine
Mike Hummel 2020-02-17 23:57:27 +01:00 committed by GitHub
commit ad0b9e49d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
211 changed files with 2961 additions and 1105 deletions

4
.gitignore vendored
View File

@ -1,6 +1,5 @@
.vscode
.idea
k9s.log
.envrc
cov.out
execs
@ -10,11 +9,10 @@ dist
notes
vendor
go.mod1
popeye1.go
gen.sh
cluster_info_test.go
*.test
*.log
*~
pod1.go
.project
faas

View File

@ -15,23 +15,23 @@ builds:
goarch:
- 386
- amd64
- arm
- arm64
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:

View File

@ -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}

133
README.md
View File

@ -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
@ -97,6 +101,7 @@ K9s is available on Linux, OSX and Windows platforms.
## Demo Video
* [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)
@ -141,16 +146,16 @@ K9s uses aliases to navigate most K8s resources.
---
## 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 +190,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 +205,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 +253,7 @@ plugin:
description: Pod logs
scopes:
- po
command: /usr/local/bin/kubectl
command: kubectl
background: false
args:
- logs
@ -233,6 +273,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 +282,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 +310,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 +333,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 +350,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 +457,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 +491,7 @@ k9s:
activeColor: skyblue
# Resource status and update styles
status:
newColor: #00ff00
newColor: '#00ff00'
modifyColor: powderblue
addColor: lightskyblue
errorColor: indianred
@ -585,8 +592,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)
---

BIN
assets/k9s_fez.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

12
go.mod
View File

@ -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.5
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/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

89
go.sum
View File

@ -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,12 @@ 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/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 +165,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 +178,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 +207,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 +228,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 +295,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 +303,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 +344,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 +390,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 +404,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 +444,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 +458,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,6 +480,7 @@ 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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7PEeBTyK1rj1gABdCO2mbri6GKO1cMDs=
@ -448,10 +490,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 +513,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 +527,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=
@ -527,6 +579,8 @@ github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvf
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 +608,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 +621,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 +639,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 +676,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 +689,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 +747,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 +757,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 +787,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 +820,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 +835,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 +898,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=

View File

@ -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() {

View File

@ -48,6 +48,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

View File

@ -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,

11
internal/client/tunnel.go Normal file
View File

@ -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
}

View File

@ -64,7 +64,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)

View File

@ -143,7 +143,7 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
func (a *Aliases) LoadAliases(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
}

View File

@ -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.LoadAliases("testdata/alias.yml"))
assert.Equal(t, 2, len(a.Alias))
}

View File

@ -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: "/",
},
}
}

View File

@ -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))

View File

@ -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()

View File

@ -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))

View File

@ -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)

View File

@ -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) {

View File

@ -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"]

View File

@ -292,6 +292,11 @@ func NewStyles() *Styles {
}
}
// 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)
@ -385,10 +390,14 @@ func (s *Styles) Update() {
tview.Styles.PrimaryTextColor = s.FgColor()
tview.Styles.BorderColor = AsColor(s.K9s.Frame.Border.FgColor)
tview.Styles.FocusColor = AsColor(s.K9s.Frame.Border.FocusColor)
s.fireStylesChanged()
}
// AsColor checks color index, if match return color otherwise pink it is.
func AsColor(c string) tcell.Color {
if c == "default" {
return tcell.ColorDefault
}
if color, ok := tcell.ColorNames[c]; ok {
return color
}

View File

@ -27,7 +27,7 @@ func TestAsColor(t *testing.T) {
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)
@ -40,7 +40,7 @@ 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)
@ -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"))
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -62,7 +62,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")

View File

@ -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 }

View File

@ -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.

View File

@ -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)
}

View File

@ -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

View File

@ -21,6 +21,7 @@ var (
_ Loggable = (*Deployment)(nil)
_ Restartable = (*Deployment)(nil)
_ Scalable = (*Deployment)(nil)
_ Controller = (*Deployment)(nil)
)
// Deployment represents a deployment K8s resource.
@ -51,12 +52,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 +65,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
}

View File

@ -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.
@ -34,12 +34,7 @@ type DaemonSet struct {
// 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 +46,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 +69,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 +84,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 +101,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 +109,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...

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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...)
}

View File

@ -29,8 +29,6 @@ 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")

219
internal/dao/ofaas.go Normal file
View File

@ -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
}

View File

@ -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.
@ -122,13 +123,7 @@ 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())
if err != nil {
return nil, err
}
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
pod, err := p.GetInstance(path)
if err != nil {
return nil, err
}
@ -147,15 +142,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 +210,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 +222,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 +237,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 +250,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 +258,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())
}
// ----------------------------------------------------------------------------

View File

@ -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
}

View File

@ -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))
})
}
}

View File

@ -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
}

View File

@ -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,6 +136,9 @@ func loadNonResource(m ResourceMetas) {
loadK9s(m)
loadRBAC(m)
loadHelm(m)
if IsOpenFaasEnabled() {
loadOpenFaas(m)
}
}
func loadK9s(m ResourceMetas) {
@ -203,6 +207,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",

View File

@ -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

View File

@ -21,6 +21,7 @@ var (
_ Loggable = (*StatefulSet)(nil)
_ Restartable = (*StatefulSet)(nil)
_ Scalable = (*StatefulSet)(nil)
_ Controller = (*StatefulSet)(nil)
)
// StatefulSet represents a K8s sts.
@ -51,12 +52,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 +66,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 +87,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
}

View File

@ -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
}

View File

@ -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

19
internal/dao/testdata/benchspec.yml vendored Normal file
View File

@ -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: /

View File

@ -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.

View File

@ -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
}

View File

@ -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()
{

View File

@ -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()

View File

@ -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) {

View File

@ -14,6 +14,10 @@ var Registry = map[string]ResourceMeta{
DAO: &dao.Chart{},
Renderer: &render.Chart{},
},
"openfaas": {
DAO: &dao.OpenFaas{},
Renderer: &render.OpenFaas{},
},
"containers": {
DAO: &dao.Container{},
Renderer: &render.Container{},

View File

@ -75,10 +75,15 @@ func (t *Table) RemoveListener(l TableListener) {
// Watch initiates model updates.
func (t *Table) Watch(ctx context.Context) {
t.Refresh(ctx)
t.refresh(ctx)
go t.updater(ctx)
}
// Refresh updates the table content.
func (t *Table) Refresh(ctx context.Context) {
t.refresh(ctx)
}
// Get returns a resource instance if found, else an error.
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
meta, err := t.getMeta(ctx)
@ -134,11 +139,6 @@ func (t *Table) ToYAML(ctx context.Context, path string) (string, error) {
return desc.ToYAML(path)
}
// Refresh update the model now.
func (t *Table) Refresh(ctx context.Context) {
t.refresh(ctx)
}
// GetNamespace returns the model namespace.
func (t *Table) GetNamespace() string {
return t.namespace
@ -205,7 +205,7 @@ func (t *Table) refresh(ctx context.Context) {
t.fireTableLoadFailed(err)
return
}
t.fireTableChanged()
t.fireTableChanged(t.Peek())
}
func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, error) {
@ -259,7 +259,6 @@ func (t *Table) reconcile(ctx context.Context) error {
t.mx.Lock()
defer t.mx.Unlock()
// if labelSelector in place might as well clear the model data.
sel, ok := ctx.Value(internal.KeyLabels).(string)
if ok && sel != "" {
@ -298,8 +297,7 @@ func (t *Table) resourceMeta() ResourceMeta {
return meta
}
func (t *Table) fireTableChanged() {
data := t.Peek()
func (t *Table) fireTableChanged(data render.TableData) {
for _, l := range t.listeners {
l.TableDataChanged(data)
}

View File

@ -69,12 +69,6 @@ func TestTableMeta(t *testing.T) {
accessor dao.Accessor
renderer Renderer
}{
// BOZO!!
// "full": {
// gvr: "v1/pods",
// accessor: &pd,
// renderer: &render.Pod{},
// },
"generic": {
gvr: "containers",
accessor: &dao.Container{},
@ -144,7 +138,7 @@ func TestTableGenericHydrate(t *testing.T) {
// Helpers...
func mustLoad(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))
if err != nil {
panic(err)
}
@ -156,7 +150,7 @@ func mustLoad(n string) *unstructured.Unstructured {
}
func load(t *testing.T, 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
err = json.Unmarshal(raw, &o)
@ -165,7 +159,7 @@ func load(t *testing.T, n string) *unstructured.Unstructured {
}
func raw(t *testing.T, n string) []byte {
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)
return raw
}

View File

@ -116,7 +116,7 @@ func makeTableFactory() tableFactory {
}
func mustLoad(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))
if err != nil {
panic(err)
}

120
internal/model/text.go Normal file
View File

@ -0,0 +1,120 @@
package model
import (
"regexp"
"strings"
"github.com/sahilm/fuzzy"
)
// TextListener represents a text model listener.
type TextListener interface {
// TextChanged notifies the model changed.
TextChanged([]string)
// TextFiltered notifies when the filter changed.
TextFiltered([]string, fuzzy.Matches)
}
// Text represents a text model.
type Text struct {
lines []string
listeners []TextListener
query string
}
// NewText returns a new model.
func NewText() *Text {
return &Text{}
}
// Peek returns the current model state.
func (t *Text) Peek() []string {
return t.lines
}
// ClearFilter clear out filter.
func (t *Text) ClearFilter() {
t.query = ""
t.filterChanged(t.lines)
}
// Filter filters out the text.
func (t *Text) Filter(q string) {
t.query = q
t.filterChanged(t.lines)
}
// SetText sets the current model content.
func (t *Text) SetText(buff string) {
t.lines = strings.Split(buff, "\n")
t.fireTextChanged(t.lines)
}
// AddListener adds a new model listener.
func (t *Text) AddListener(listener TextListener) {
t.listeners = append(t.listeners, listener)
}
// RemoveListener delete a listener from the list.
func (t *Text) RemoveListener(listener TextListener) {
victim := -1
for i, lis := range t.listeners {
if lis == listener {
victim = i
break
}
}
if victim >= 0 {
t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...)
}
}
func (t *Text) filterChanged(lines []string) {
t.fireTextFiltered(lines, t.filter(t.query, lines))
}
func (t *Text) fireTextChanged(lines []string) {
for _, lis := range t.listeners {
lis.TextChanged(lines)
}
}
func (t *Text) fireTextFiltered(lines []string, matches fuzzy.Matches) {
for _, lis := range t.listeners {
lis.TextFiltered(lines, matches)
}
}
// ----------------------------------------------------------------------------
// Helpers...
func (t *Text) filter(q string, lines []string) fuzzy.Matches {
if q == "" {
return nil
}
if isFuzzySelector(q) {
return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
}
return t.rxFilter(q, lines)
}
func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches {
return fuzzy.Find(q, lines)
}
func (*Text) rxFilter(q string, lines []string) fuzzy.Matches {
rx, err := regexp.Compile(`(?i)` + q)
if err != nil {
return nil
}
matches := make(fuzzy.Matches, 0, len(lines))
for i, l := range lines {
if loc := rx.FindStringIndex(l); len(loc) == 2 {
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
}
}
return matches
}

View File

@ -0,0 +1,90 @@
package model_test
import (
"testing"
"github.com/derailed/k9s/internal/model"
"github.com/sahilm/fuzzy"
"github.com/stretchr/testify/assert"
)
func TestNewText(t *testing.T) {
m := model.NewText()
lis := textLis{}
m.AddListener(&lis)
m.SetText("Hello World\nBumbleBeeTuna")
assert.Equal(t, 1, lis.changed)
assert.Equal(t, 2, lis.lines)
assert.Equal(t, 0, lis.filtered)
assert.Equal(t, 0, lis.matches)
}
func TestTextFilterRXMatch(t *testing.T) {
m := model.NewText()
lis := textLis{}
m.AddListener(&lis)
m.SetText("Hello World\nBumbleBeeTuna")
m.Filter("world")
assert.Equal(t, 1, lis.changed)
assert.Equal(t, 2, lis.lines)
assert.Equal(t, 1, lis.filtered)
assert.Equal(t, 1, lis.matches)
assert.Equal(t, 6, lis.index)
}
func TestTextFilterFuzzyMatch(t *testing.T) {
m := model.NewText()
lis := textLis{}
m.AddListener(&lis)
m.SetText("Hello World\nBumbleBeeTuna")
m.Filter("-f world")
assert.Equal(t, 1, lis.changed)
assert.Equal(t, 2, lis.lines)
assert.Equal(t, 1, lis.filtered)
assert.Equal(t, 1, lis.matches)
assert.Equal(t, 6, lis.index)
}
func TestTextFilterNoMatch(t *testing.T) {
m := model.NewText()
lis := textLis{}
m.AddListener(&lis)
m.SetText("Hello World\nBumbleBeeTuna")
m.Filter("blee")
assert.Equal(t, 1, lis.changed)
assert.Equal(t, 2, lis.lines)
assert.Equal(t, 1, lis.filtered)
assert.Equal(t, 0, lis.matches)
assert.Equal(t, 0, lis.index)
}
// Helpers...
type textLis struct {
changed, filtered, matches, lines, index int
}
func (l *textLis) TextChanged(ll []string) {
l.lines = len(ll)
l.changed++
}
func (l *textLis) TextFiltered(ll []string, mm fuzzy.Matches) {
l.matches = len(mm)
l.filtered++
if len(mm) > 0 && len(mm[0].MatchedIndexes) > 0 {
l.index = mm[0].MatchedIndexes[0]
}
}

View File

@ -64,6 +64,8 @@ func (b *Benchmark) init(base, version string) error {
}
req.Header.Set("User-Agent", ua)
log.Debug().Msgf("Benching %d:%d", b.config.N, b.config.C)
b.worker = &requester.Work{
Request: req,
RequestBody: []byte(b.config.HTTP.Body),

View File

@ -18,19 +18,19 @@ func TestAugmentRow(t *testing.T) {
e Fields
}{
"cool": {
"assets/b1.txt",
"testdata/b1.txt",
Fields{"pass", "3.3544", "29.8116", "100", "0"},
},
"2XX": {
"assets/b4.txt",
"testdata/b4.txt",
Fields{"pass", "3.3544", "29.8116", "160", "0"},
},
"4XX/5XX": {
"assets/b2.txt",
"testdata/b2.txt",
Fields{"pass", "3.3544", "29.8116", "100", "12"},
},
"toast": {
"assets/b3.txt",
"testdata/b3.txt",
Fields{"fail", "2.3688", "35.4606", "0", "0"},
},
}

View File

@ -66,7 +66,7 @@ func (c Chart) Render(o interface{}, ns string, r *Row) error {
// ----------------------------------------------------------------------------
// Helpers...
// ChartRes represents an alias resource.
// ChartRes represents an helm chart resource.
type ChartRes struct {
Release *release.Release
}

View File

@ -151,7 +151,7 @@ func Truncate(str string, width int) string {
func mapToStr(m map[string]string) (s string) {
if len(m) == 0 {
return MissingValue
return ""
}
kk := make([]string, 0, len(m))

View File

@ -305,7 +305,7 @@ func TestMapToStr(t *testing.T) {
e string
}{
{map[string]string{"blee": "duh", "aa": "bb"}, "aa=bb,blee=duh"},
{map[string]string{}, MissingValue},
{map[string]string{}, ""},
}
for _, u := range uu {
assert.Equal(t, u.e, mapToStr(u.i))

View File

@ -7,7 +7,6 @@ import (
"time"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -42,7 +41,6 @@ func (Job) Header(ns string) HeaderRow {
// Render renders a K8s resource to screen.
func (j Job) Render(o interface{}, ns string, r *Row) error {
log.Debug().Msgf("JOB RENDER %q", ns)
raw, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("Expected Job, but got %T", o)

View File

@ -111,7 +111,14 @@ func egress(ee []v1beta1.NetworkPolicyEgressRule) (string, string, string) {
func portsToStr(pp []v1beta1.NetworkPolicyPort) string {
ports := make([]string, 0, len(pp))
for _, p := range pp {
ports = append(ports, string(*p.Protocol)+":"+p.Port.String())
proto, port := NAValue, NAValue
if p.Protocol != nil {
proto = string(*p.Protocol)
}
if p.Port != nil {
port = p.Port.String()
}
ports = append(ports, proto+":"+port)
}
return strings.Join(ports, ",")
}

102
internal/render/ofaas.go Normal file
View File

@ -0,0 +1,102 @@
package render
import (
"fmt"
"strconv"
"time"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
ofaas "github.com/openfaas/faas-provider/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
fnStatusReady = "Ready"
fnStatusNotReady = "Not Ready"
)
// OpenFaas renders an openfaas function to screen.
type OpenFaas struct{}
// ColorerFunc colors a resource row.
func (OpenFaas) ColorerFunc() ColorerFunc {
return func(ns string, re RowEvent) tcell.Color {
return tcell.ColorPaleTurquoise
}
}
// Header returns a header row.
func (OpenFaas) Header(ns string) HeaderRow {
var h HeaderRow
if client.IsAllNamespaces(ns) {
h = append(h, Header{Name: "NAMESPACE"})
}
return append(h,
Header{Name: "NAME"},
Header{Name: "STATUS"},
Header{Name: "IMAGE"},
Header{Name: "LABELS"},
Header{Name: "INVOCATIONS", Align: tview.AlignRight},
Header{Name: "REPLICAS", Align: tview.AlignRight},
Header{Name: "AVAILABLE", Align: tview.AlignRight},
Header{Name: "AGE", Decorator: AgeDecorator},
)
}
// Render renders a chart to screen.
func (f OpenFaas) Render(o interface{}, ns string, r *Row) error {
fn, ok := o.(OpenFaasRes)
if !ok {
return fmt.Errorf("expected OpenFaasRes, but got %T", o)
}
var labels string
if fn.Function.Labels != nil {
labels = mapToStr(*fn.Function.Labels)
}
var status = fnStatusReady
if fn.Function.AvailableReplicas == 0 {
status = fnStatusNotReady
}
r.ID = client.FQN(fn.Function.Namespace, fn.Function.Name)
r.Fields = make(Fields, 0, len(f.Header(ns)))
if client.IsAllNamespaces(ns) {
r.Fields = append(r.Fields, fn.Function.Namespace)
}
r.Fields = append(r.Fields,
fn.Function.Name,
status,
fn.Function.Image,
labels,
strconv.Itoa(int(fn.Function.InvocationCount)),
strconv.Itoa(int(fn.Function.Replicas)),
strconv.Itoa(int(fn.Function.AvailableReplicas)),
toAge(metav1.Time{Time: time.Now()}),
)
return nil
}
// ----------------------------------------------------------------------------
// Helpers...
// OpenFaasRes represents an openfaas function resource.
type OpenFaasRes struct {
Function ofaas.FunctionStatus `json:"function"`
}
// GetObjectKind returns a schema object.
func (OpenFaasRes) GetObjectKind() schema.ObjectKind {
return nil
}
// DeepCopyObject returns a container copy.
func (h OpenFaasRes) DeepCopyObject() runtime.Object {
return h
}

View File

@ -0,0 +1,34 @@
package render_test
import (
"testing"
"github.com/derailed/k9s/internal/render"
ofaas "github.com/openfaas/faas-provider/types"
"github.com/stretchr/testify/assert"
)
func TestOpenFaasRender(t *testing.T) {
c := render.OpenFaas{}
r := render.NewRow(9)
c.Render(makeFn("blee"), "", &r)
assert.Equal(t, "default/blee", r.ID)
assert.Equal(t, render.Fields{"default", "blee", "Ready", "nginx:0", "fred=blee", "10", "1", "1"}, r.Fields[:8])
}
// Helpers...
func makeFn(n string) render.OpenFaasRes {
return render.OpenFaasRes{
Function: ofaas.FunctionStatus{
Name: n,
Namespace: "default",
Image: "nginx:0",
InvocationCount: 10,
Replicas: 1,
AvailableReplicas: 1,
Labels: &map[string]string{"fred": "blee"},
},
}
}

Some files were not shown because too many files have changed in this diff Show More