merging refactor to master

mine
derailed 2019-12-29 23:53:17 -07:00
commit 15667435be
515 changed files with 22932 additions and 25522 deletions

28
.codebeatsettings Normal file
View File

@ -0,0 +1,28 @@
{
"GOLANG": {
"TOO_MANY_IVARS": [
10,
12,
15,
18
],
"LOC": [
30,
50,
80,
100
],
"TOTAL_LOC": [
200,
400,
500,
600
],
"TOO_MANY_FUNCTIONS": [
30,
40,
45,
46
]
}
}

305
.golangci.yml Normal file
View File

@ -0,0 +1,305 @@
# This file contains all available configuration options
# with their default values.
# options for analysis running
run:
# default concurrency is a available CPU number
concurrency: 4
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 1m
# exit code when at least one issue was found, default is 1
issues-exit-code: 1
# include test files or not, default is true
tests: true
# list of build tags, all linters use it. Default is empty list.
# build-tags:
# - mytag
# which dirs to skip: issues from them won't be reported;
# can use regexp here: generated.*, regexp is applied on full path;
# default value is empty list, but default dirs are skipped independently
# from this option's value (see skip-dirs-use-default).
# skip-dirs:
# - src/external_libs
# - autogenerated_by_my_lib
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
# which files to skip: they will be analyzed, but issues from them
# won't be reported. Default value is empty list, but there is
# no need to include all autogenerated files, we confidently recognize
# autogenerated files. If it's not please let us know.
# skip-files:
# - ".*\\.my\\.go$"
# - lib/bad.go
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
# If invoked with -mod=readonly, the go command is disallowed from the implicit
# automatic updating of go.mod described above. Instead, it fails when any changes
# to go.mod are needed. This setting is most useful to check that go.mod does
# not need updates, such as in a continuous integration and testing system.
# If invoked with -mod=vendor, the go command assumes that the vendor
# directory holds the correct copies of dependencies and ignores
# the dependency descriptions in go.mod.
# modules-download-mode: readonly|release|vendor
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number
# print lines of code with issue, default is true
print-issued-lines: true
# print linter name in the end of issue text, default is true
print-linter-name: true
# all available settings of specific linters
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: true
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false
# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
# ignore: fmt:.*,io/ioutil:^Read.*
# path to a file containing a list of functions to exclude from checking
# see https://github.com/kisielk/errcheck#excluding-functions for details
# exclude: /path/to/file.txt
funlen:
lines: 60
statements: 40
govet:
# report about shadowed variables
check-shadowing: true
# settings per analyzer
settings:
printf: # analyzer name, run `go tool vet help` to see all analyzers
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
# enable or disable analyzers by name
enable:
- atomicalign
enable-all: false
disable:
# - shadow
disable-all: false
golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: github.com/org/project
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
gocognit:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 20
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 3
depguard:
list-type: blacklist
include-go-root: false
packages:
- github.com/sirupsen/logrus
packages-with-error-messages:
# specify an error message to output when a blacklisted package is used
github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
misspell:
# Correct spellings using locale preferences for US or UK.
# Default is to use a neutral variety of English.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
locale: US
ignore-words:
- someword
lll:
# max line length, lines longer will be reported. Default is 120.
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
line-length: 120
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
prealloc:
# XXX: we don't recommend using this linter before doing performance profiling.
# For most programs usage of prealloc will be a premature optimization.
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
gocritic:
# Which checks should be enabled; can't be combined with 'disabled-checks';
# See https://go-critic.github.io/overview#checks-overview
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
# By default list of stable checks is used.
# enabled-checks:
# - rangeValCopy
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
disabled-checks:
- regexpMust
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
godox:
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
# might be left in the code accidentally and should be resolved before merging
keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting
- NOTE
- OPTIMIZE # marks code that should be optimized before merging
- HACK # marks hack-arounds that should be removed before merging
dogsled:
# checks assignments with too many blank identifiers; default is 2
max-blank-identifiers: 2
whitespace:
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
wsl:
# If true append is only allowed to be cuddled if appending value is
# matching variables, fields or types on line above. Default is true.
strict-append: true
# Allow calls and assignments to be cuddled as long as the lines have any
# matching variables, fields or types. Default is true.
allow-assign-and-call: true
# Allow multiline assignments to be cuddled. Default is true.
allow-multiline-assign: true
# Allow declarations (var) to be cuddled.
allow-cuddle-declarations: false
# Allow trailing comments in ending of blocks
allow-trailing-comment: false
# Force newlines in end of case at this limit (0 = never).
force-case-trailing-whitespace: 0
linters:
enable:
- megacheck
- govet
- funlen
- gocyclo
disable:
- maligned
- prealloc
- gosec
disable-all: false
presets:
- bugs
- unused
fast: false
issues:
# List of regexps of issue texts to exclude, empty list by default.
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
exclude:
- abcdef
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- funlen
- goconst
- gocognit
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via "nolint" comments.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
# Exclude some staticcheck messages
- linters:
- staticcheck
text: "SA9003:"
# Exclude lll issues for long lines with go:generate
- linters:
- lll
source: "^//go:generate "
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
new: false
# Show only new issues created after git revision `REV`
# new-from-rev: REV
# Show only new issues created in git patch with set file path.
# new-from-patch: path/to/patch/file

View File

@ -75,7 +75,6 @@ K9s is available on Linux, OSX and Windows platforms.
1. Deployments
<img src="assets/screen_dp.png"/>
---
## Demo Video
@ -112,7 +111,7 @@ K9s uses aliases to navigate most K8s resources.
| `Ctrl-a` | Show all available resource alias | select+`<ENTER>` to view |
| `/`filter`ENTER` | Filter out a resource view given a filter | `/bumblebeetuna` |
| `/`-l label-selector`ENTER` | Filter resource view by labels | `/-l app=fred` |
| `<Esc>` | Bails out of command/filter mode | |
| `<Esc>` | Bails out of view/command/filter mode | |
| `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) |
| `:`ctx`<ENTER>` | To view and switch to another Kubernetes context | `:`+`ctx`+`<ENTER>` |
| `:`ns`<ENTER>` | To view and switch to another Kubernetes namespace | `:`+`ns`+`<ENTER>` |
@ -124,11 +123,12 @@ K9s uses aliases to navigate most K8s resources.
## K9s config file ($HOME/.k9s/config.yml)
K9s keeps its configurations in a dot file in your home directory.
K9s keeps its configurations in a .k9s directory in your home directory.
> NOTE: This is still in flux and will change while in pre-release stage!
```yaml
# config.yml
k9s:
# Indicates api-server poll intervals.
refreshRate: 2
@ -162,9 +162,10 @@ K9s uses aliases to navigate most K8s resources.
```
---
## 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 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:
```yaml
# $HOME/.k9s/alias.yml
@ -173,9 +174,10 @@ alias:
crb: rbac.authorization.k8s.io/v1/clusterrolebindings
```
Using this alias file, you can now type pp/crb to list pods, clusterrolebindings respectively.
Using this alias file, you can now type pp/crb to list pods or clusterrolebindings respectively.
---
## 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:
@ -183,9 +185,10 @@ K9s allows you to define your own cluster commands via plugins. K9s will look at
```yaml
# $HOME/.k9s/plugin.yml
plugin:
# Defines a plugin to provide a `Ctrl-L` shorcut to tail the logs while in pod view.
fred:
shortCut: Ctrl-L
description: "Pod logs"
description: Pod logs
scopes:
- po
command: /usr/local/bin/kubectl
@ -202,7 +205,7 @@ plugin:
This defines a plugin for viewing logs on a selected pod using `CtrlL` mnemonic.
The shortcut option represents the command a user would type to activate the plugin. The command represents adhoc commands the plugin runs upon activation. The scopes defines a collection of views shortnames for which the plugin shortcut will be made available to the user.
The shortcut option represents the command a user would type to activate the plugin. The command represents adhoc commands the plugin runs upon activation. The scopes defines a collection of resources names/shortnames for which the plugin shortcut will be made available to the user. You can specify all to provide this shortcut for all views.
K9s does provide additional environment variables for you to customize your plugins. Currently, the available environment variables are as follows:
@ -283,6 +286,41 @@ 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.
@ -290,7 +328,6 @@ On RBAC enabled clusters, you would need to give your users/groups capabilities
These rules below are just suggestions. You will need to customize them based on your environment policies. If you need to edit/delete resources extra Fu will be necessary.
> NOTE! Cluster/Namespace access may change in the future as K9s evolves.
> NOTE! We expect K9s to keep running even in atrophied clusters/namespaces. Please file issues if this is not the case!
### Cluster RBAC scope
@ -315,7 +352,7 @@ rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
# Grants RO access to metric server
# Grants RO access to metric server (if present)
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list", "watch"]
@ -355,7 +392,7 @@ rules:
verbs: ["get", "list", "watch"]
# Grants RO access to metric server
- apiGroups: ["metrics.k8s.io"]
resources: ["pods"]
resources: ["pods", "nodes"]
verbs:
- get
- list
@ -382,7 +419,7 @@ roleRef:
## Skins
You can style K9s based on your own sense of style and look. This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly!
You can style K9s based on your own sense of look and style. This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly!
By default a K9s view displays resource information using the following coloring scheme:
@ -392,7 +429,8 @@ By default a K9s view displays resource information using the following coloring
Skins are YAML files, that enable a user to change K9s presentation layer. K9s skins are loaded from `$HOME/.k9s/skin.yml`. If a skin file is detected then the skin would be loaded if not the current stock skin remains in effect.
Below is a sample skin file, more skins would be available in the skins directory, just simply copy any of these in your user's home dir as `skin.yml`.
You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify 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`.
```yaml
# InTheNavy Skin...
@ -424,7 +462,8 @@ k9s:
activeColor: skyblue
# Resource status and update styles
status:
newColor: blue
# You can also use hex colors!
newColor: #0000ff
modifyColor: powderblue
addColor: lightskyblue
errorColor: indianred
@ -501,7 +540,7 @@ Available color names are defined below:
This initial drop is brittle. K9s will most likely blow up...
1. You're running older versions of Kubernetes. K9s works best Kubernetes 1.12+.
1. You're running older versions of Kubernetes. K9s works best Kubernetes 1.15+.
2. You don't have enough RBAC fu to manage your cluster.
---
@ -516,7 +555,7 @@ dig this effort, please let us know that too!
## ATTA Girls/Boys!
K9s sits on top of many of opensource projects and libraries. Our *sincere*
K9s sits on top of many open source projects and libraries. Our *sincere*
appreciations to all the OSS contributors that work nights and weekends
to make this project a reality!

View File

@ -0,0 +1,88 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.10.0
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
First off, Happy 2020 to you and yours!! Best wishes for good health and good fortune!
This release represents a major overall of K9s core. It's been a long time coming and indeed a long day in the saddle ;( There has been many code changes and hopefully improvements from previous releases. I think some of it is better but I've probably borked a bunch of functionality in the process. I look to you to help me flesh out issues and bugs, so we can move on to bigger and exciting features in 2020! Please do thread lightly on this one and make sure to keep a previous release handy just in case... This was a boatload of work to make this happen, my hope is you'll enjoy some of the improvements... In any case, and as always, if you feel they're better ways or imperfections by all means please pipe in!
I would also like to take this opportunity to thank all of you for your kind PRs and issues and for your support and patience with K9s. I understand this release might be a bit torked, but I will work hard to make sure we reach stability quickly in the next few drops. Thank you for your understanding!!
## VatDoesDisDo?
Most of the refactors are around K8s resource fetching and viewing as well as navigation changes. Based on our observations this release might load resources a bit slower than usual but should make navigation much faster once the cache is primed. We've made some improvements to be more consistent with navigation and shortcuts management. We've got ride off the breadcrumbs navigation ie no more `p` to nav back. Crumbs are now just tracking a natural resoure navigation ie pod -> containers -> logs and no longer commands history. Each new command will now load a brand new breadcrumb. You can press `<esc>` to nav back to the previous page. We're also introducing a new hotkeys feature, that afforts creating shortcuts to navigate to your favorite resources ie shift-0 -> view pods, shift-1 -> view deployments (See HotKey section below). I know there were many outstanding PRS (Thank you to all that I've submitted!) and given the extent of the changes, I've resolved to incorporate them in manually vs having to deal with merge conflicts.
## Custom Skins Per Cluster
In this release, we've added support for skins at the cluster level. Do you want K9s to look differently based on which cluster you're connecting to? All you'll need is to name the skin file in the K9s home directory as follows `mycluster_skin.yml`. If no cluster specific skin file is found, the standard `skin.yml` file will be loaded if present. Please checkout the `skins` directory in this repo or PR me if you have cool skins you'd like to share with your fellow K9ers as they will be featured in these release notes and the project README.
## Hot(Ness)?
Feeling like you want to be able to quickly switch around your favorite resources with your very own shortcut? Wouldn't it be dandy to navigate to your deployments using shift-0 vs entering a command `:dp`? Here is what you'll need to do to add HotKeys to your K9s sessions:
1. In your .k9s home directory create a file named `hotkey.yml`
2. For example 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: statefulsets
```
Not feeling so hot? Your custom hotkeys list will be listed in the help view.`<?>`.
You can choose any keyboard shotcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
## PullRequests
* [PR #447](https://github.com/derailed/k9s/pull/447) K9s MacPorts support. Thank you! [Nils Breunese](https://github.com/breun)
* [PR #446](https://github.com/derailed/k9s/pull/446) Same key invert sort. Big thanks!! [James Hiew](https://github.com/jameshiew)
* [PR #445](https://github.com/derailed/k9s/pull/445) Use `?` to toggle help. Major thanks!! [Ramz](https://github.com/ageekymonk)
* [PR #443](https://github.com/derailed/k9s/pull/443) Hex color skin support. Great work! [Gavin Ray](https://github.com/gavinray97)
* [PR #442](https://github.com/derailed/k9s/pull/442) Full screen/Wrap support on log view. ATTA BOY! [Shiv3](https://github.com/shiv3)
* [PR #412](https://github.com/derailed/k9s/pull/412) Simplify cruder interface. ATTA BOY!! (as always)[Gustavo Silva Paiva](https://github.com/paivagustavo)
* [PR #350](https://github.com/derailed/k9s/pull/350) Sanitize file name before saving. All credits to [Tuomo Syvänperä](https://github.com/syvanpera)
---
## Resolved Bugs/Features
* [Issue #437](https://github.com/derailed/k9s/issues/437) Error when viewing cluster role on a role binding.
* [Issue #434](https://github.com/derailed/k9s/issues/434) Same key `?` toggle help.
* [Issue #432](https://github.com/derailed/k9s/issues/432) Add address field to port forwards.
* [Issue #431](https://github.com/derailed/k9s/issues/431) Add LimitRange resource support.
* [Issue #430](https://github.com/derailed/k9s/issues/430) Add HotKey support.
* [Issue #426](https://github.com/derailed/k9s/issues/426) Address slow scroll while in table view.
* [Issue #417](https://github.com/derailed/k9s/issues/417) Ensure code lints correctly. Thank you Gustavo!!
* [Issue #415](https://github.com/derailed/k9s/issues/415) Add provisions to support longer clusterinfo/namespace header.
* [Issue #408](https://github.com/derailed/k9s/issues/408) Same key toggle inverse sort.
* [Issue #402](https://github.com/derailed/k9s/issues/402) Add `all` support to plugin scope.
* [Issue #401](https://github.com/derailed/k9s/issues/401) Add support for custom plugins on all views.
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -26,7 +26,7 @@ Also if you dig this tool, please make some noise on social! [@kitesurfer](https
### o YAML Highlighter
Describe and YAML commands will now yield syntax highlighted views.
Describe and YAML commands will now yield syntax highlighted view.
[Feature #142](https://github.com/derailed/k9s/issues/142)
---

View File

@ -20,8 +20,7 @@ This is a maintenance release to mainly resolve outstanding issues and bugs.
### Tracking Percentages
Added two new columns to track cpu/memory utilization on metrics-server enabled clusters. These apply to pod,container and node views.
Added two new columns to track cpu/memory utilization on metrics-server enabled clusters. These apply to pod,container and node view.
---

View File

@ -36,7 +36,7 @@ This feature is still work in progress. It does require a new config file to hel
This is one feature that I think is, pardon my french.., totally `Bitch'n`! K9s now bundles [Hey](https://github.com/rakyll/hey) CLI tool from the extremely talented Jaana Dogan of Google fame. Hey allows you to benchmark HTTP service endpoints similar to apache bench.
Benchmarking is enabled via keyboard shortcuts `Ctrl-B` and `Alt-B` to activate/cancel a benchmark while in PortForward and Service views. Benchmarking a service assumes the HTTP service is exposed either as NodePort or LoadBalancer. To view your benchmarks, navigate to the new Benchmark view aka `:be<ENTER>` to list your benchmarks and runs statistics.
Benchmarking is enabled via keyboard shortcuts `Ctrl-B` and `Alt-B` to activate/cancel a benchmark while in PortForward and Service view. Benchmarking a service assumes the HTTP service is exposed either as NodePort or LoadBalancer. To view your benchmarks, navigate to the new Benchmark view aka `:be<ENTER>` to list your benchmarks and runs statistics.
So you now have the ability to stretch out your cluster legs by benchmarking your pods and services and gather all kind of interesting statistics directly in K9s. Generating load on your resources will help you tune your cluster resources, exercise your auto scalers, compare Canary builds perf, etc...

View File

@ -43,7 +43,7 @@ dialogs. This was totally a reasonable thing to do! However in case of managed p
This one is cool! I think this thought came about from (Markus)[https://github.com/Makusi75]. Thank you Markus!! This feature allows K9s users to now customize K9s with their own plugin commands. You will be able to add a new menu shortcut to the K9s menu and fire off a custom command on a selected resource. Some of you might be leveraging kubectl plugins and now you will be able to fire these off directly from K9s along with many other shell commands.
In order to specify a custom plugin command, you will need to modify your .k9s/config.yml file. For example here is a sample extension to list out all the pods in the `fred` namespace while in a pod or deployment views. When this plugin is available a new command `<alt-p>` will show only while in pod and deploy views.
In order to specify a custom plugin command, you will need to modify your .k9s/config.yml file. For example here is a sample extension to list out all the pods in the `fred` namespace while in a pod or deployment view. When this plugin is available a new command `<alt-p>` will show only while in pod and deploy view.
```yaml
plugins:

View File

@ -14,7 +14,7 @@ Also if you dig this tool, please make some noise on social! [@kitesurfer](https
### NetworkPolicy
NetworkPolicy resource is now available under the command `np` while in command mode. It behaves like other K9s resource views. You will get a bit more information in K9s vs `kubectl` as it includes information about ingress and egress rules.
NetworkPolicy resource is now available under the command `np` while in command mode. It behaves like other K9s resource view. You will get a bit more information in K9s vs `kubectl` as it includes information about ingress and egress rules.
### Arrrggg! New CLI Argument

View File

@ -5,11 +5,11 @@ import (
"fmt"
"runtime/debug"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/views"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/view"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
@ -34,20 +34,29 @@ var (
Long: longAppDesc,
Run: run,
}
_ config.KubeSettings = &k8s.Config{}
_ config.KubeSettings = &client.Config{}
)
func init() {
const falseFlag = "false"
rootCmd.AddCommand(versionCmd(), infoCmd())
initK9sFlags()
initK8sFlags()
// Klogs (of course) want to print stuff to the screen ;(
klog.InitFlags(nil)
flag.Set("log_file", config.K9sLogs)
flag.Set("stderrthreshold", "fatal")
flag.Set("alsologtostderr", "false")
flag.Set("logtostderr", "false")
if err := flag.Set("log_file", config.K9sLogs); err != nil {
log.Error().Err(err)
}
if err := flag.Set("stderrthreshold", "fatal"); err != nil {
log.Error().Err(err)
}
if err := flag.Set("alsologtostderr", falseFlag); err != nil {
log.Error().Err(err)
}
if err := flag.Set("logtostderr", falseFlag); err != nil {
log.Error().Err(err)
}
}
// Execute root command
@ -59,22 +68,24 @@ func Execute() {
func run(cmd *cobra.Command, args []string) {
defer func() {
// views.ClearScreen()
// view.ClearScreen()
if err := recover(); err != nil {
log.Error().Msgf("Boom! %v", err)
log.Error().Msg(string(debug.Stack()))
printLogo(color.Red)
fmt.Printf(color.Colorize("Boom!! ", color.Red))
fmt.Printf("%s", color.Colorize("Boom!! ", color.Red))
fmt.Println(color.Colorize(fmt.Sprintf("%v.", err), color.White))
}
}()
zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel))
cfg := loadConfiguration()
app := views.NewApp(cfg)
app := view.NewApp(cfg)
{
defer app.BailOut()
app.Init(version, *k9sFlags.RefreshRate)
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil {
panic(err)
}
app.Run()
}
}
@ -83,8 +94,9 @@ func loadConfiguration() *config.Config {
log.Info().Msg("🐶 K9s starting up...")
// Load K9s config file...
k8sCfg := k8s.NewConfig(k8sFlags)
k8sCfg := client.NewConfig(k8sFlags)
k9sCfg := config.NewConfig(k8sCfg)
if err := k9sCfg.Load(config.K9sConfigFile); err != nil {
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
}
@ -101,25 +113,31 @@ func loadConfiguration() *config.Config {
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
}
if k9sFlags.AllNamespaces != nil && *k9sFlags.AllNamespaces {
k9sCfg.SetActiveNamespace(resource.AllNamespaces)
if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(render.AllNamespaces) != nil {
log.Error().Msg("Setting active namespace")
}
if err := k9sCfg.Refine(k8sFlags); err != nil {
log.Panic().Err(err).Msg("Unable to locate kubeconfig file")
}
k9sCfg.SetConnection(k8s.InitConnectionOrDie(k8sCfg, log.Logger))
k9sCfg.SetConnection(client.InitConnectionOrDie(k8sCfg))
// Try to access server version if that fail. Connectivity issue?
if _, err := k9sCfg.GetConnection().ServerVersion(); err != nil {
log.Panic().Err(err).Msg("K9s can't connect to cluster")
}
log.Info().Msg("✅ Kubernetes connectivity")
k9sCfg.Save()
if err := k9sCfg.Save(); err != nil {
log.Error().Err(err).Msg("Config save")
}
return k9sCfg
}
func isBoolSet(b *bool) bool {
return b != nil && *b
}
func parseLevel(level string) zerolog.Level {
switch level {
case "debug":

12
go.mod
View File

@ -28,14 +28,15 @@ replace (
require (
github.com/atotto/clipboard v0.1.2
github.com/derailed/tview v0.3.2
github.com/derailed/tview v0.3.3
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell v1.3.0
github.com/ghodss/yaml v1.0.0 // indirect
github.com/ghodss/yaml v1.0.0
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/mock v1.2.0
github.com/google/btree v1.0.0 // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
@ -44,22 +45,23 @@ require (
github.com/mattn/go-runewidth v0.0.5
github.com/petergtz/pegomock v2.6.0+incompatible
github.com/rakyll/hey v0.1.2
github.com/rs/zerolog v1.14.3
github.com/rs/zerolog v1.17.2
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.3.0
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.4
gotest.tools v2.2.0+incompatible
k8s.io/api v0.0.0
k8s.io/apiextensions-apiserver v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/cli-runtime v0.0.0
k8s.io/client-go v0.0.0
k8s.io/klog v0.4.0
k8s.io/kubectl v0.0.0
k8s.io/kubernetes v1.16.3
k8s.io/metrics v0.0.0
sigs.k8s.io/yaml v1.1.0
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787

273
go.sum
View File

@ -1,7 +1,9 @@
bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
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/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
@ -11,33 +13,81 @@ github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZt
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
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/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/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=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
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/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=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us=
github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
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/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
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=
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo=
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -45,24 +95,34 @@ 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/derailed/tview v0.3.2 h1:By43yu6kbGvA+iL09VAhTKxKEd02BBOtUPIlrkeHxT4=
github.com/derailed/tview v0.3.2/go.mod h1:yApPszFU62FoaGkf7swy2nIdV/h7Nid3dhMSVy6+OFI=
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/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=
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/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
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/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/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
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/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/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
@ -74,25 +134,66 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2 h1:ophLETFestFZHk3ji7niPEL4d466QjW+0Tdg5VyDq7E=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2 h1:rf5ArTHmIJxyV5Oiks+Su0mUens1+AjpkPoWr5xFRcI=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0 h1:sU6pp4dSV2sGlNKKyHxZzi1m1kG4WnYtWcJ+HYbygjE=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0 h1:0Dn9qy1G9+UJfRU7TR8bmdGxb4uifB7HNrJjOnV0yPk=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2 h1:ky5l57HjyVRrsJfd2+Ro5Z9PjGuKbsmftwyMtk8H7js=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -104,6 +205,8 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -112,6 +215,9 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
@ -119,46 +225,88 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
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 v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
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=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q=
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/heketi/heketi v9.0.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o=
github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI=
github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4=
github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/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/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/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=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
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/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
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=
github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4=
github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@ -167,15 +315,30 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/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=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
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/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/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=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
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/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=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@ -187,10 +350,17 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI=
github.com/rakyll/hey v0.1.2 h1:XlGaKcBdmXJaPImiTnE+TGLDUWQ2toYuHCwdrylLjmg=
github.com/rakyll/hey v0.1.2/go.mod h1:S5M+++KwbmxA7w68S92B5NdWiCB+cIhITaMUkq9W608=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
@ -198,39 +368,75 @@ github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1/go.mod h1:+rKjP5+h9HMwW
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg=
github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo=
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
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=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
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/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/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=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -240,18 +446,28 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
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=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -266,12 +482,17 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
@ -283,64 +504,113 @@ 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/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
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.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad h1:IMoNR9pilTBaCS5WpwWnAdmoVYVeXowOD3bLrwxIAtQ=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
k8s.io/cli-runtime v0.0.0-20190918162238-f783a3654da8 h1:W3zT6wRwUKkEGnUu1OAAJFwcgETlCu1BLdNP/VCTFuM=
k8s.io/cli-runtime v0.0.0-20190918162238-f783a3654da8/go.mod h1:WRliO+M6Osz7/zdOF0RI42IsJgSYHUwbLgqAWJPneSs=
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
k8s.io/cloud-provider v0.0.0-20190918163234-a9c1f33e9fb9/go.mod h1:YfUBehfPUDgnhqAFcuXj8haXt/v86nhy8r4ZOuSvXhg=
k8s.io/cluster-bootstrap v0.0.0-20190918163108-da9fdfce26bb/go.mod h1:mQVbtFRxlw/BzBqBaQwIMzjDTST1KrGtzWaR4CGlsTU=
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090 h1:0UWOjjag5IcVoAko0g+3qGhegdwWkRf4v4AHCIMVwnc=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
k8s.io/cri-api v0.0.0-20190828162817-608eb1dad4ac/go.mod h1:BvtUaNBr0fEpzb11OfrQiJLsLPtqbmulpo1fPwcpP6Q=
k8s.io/csi-translation-lib v0.0.0-20190918163402-db86a8c7bb21/go.mod h1:Ja9f0K9MkTuUSyBgpjFt2am69TOjrmkQUN25WTF3CCM=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-aggregator v0.0.0-20190918161219-8c8f079fddc3/go.mod h1:NJisPUqwlg1A99RhO1BTnNtwC4pKUyXJ2f3Xc4PxKQg=
k8s.io/kube-controller-manager v0.0.0-20190918162944-7a93a0ddadd8/go.mod h1:+HrHoqJm0UqnlrBEKXGzs2701YN4+ozi76oG7iYvJ8s=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-proxy v0.0.0-20190918162534-de037b596c1e/go.mod h1:/48p8Y6dkWJrll4tsceAoGKudGpRmtQu/u1zlG14NnI=
k8s.io/kube-scheduler v0.0.0-20190918162820-3b5c1246eb18/go.mod h1:k2dnGirIGylr51dpqxn2Zv6Yt47A+6NiynBIYfAU67I=
k8s.io/kubectl v0.0.0-20190918164019-21692a0861df h1:EwjdCG4HveZxJkI650+g4UoIuSvH7vODn55VmBjxIAo=
k8s.io/kubectl v0.0.0-20190918164019-21692a0861df/go.mod h1:AjffgL1ZYSrbpRJHER9vC+/INYwTSdmoZD0DXhMKzxQ=
k8s.io/kubelet v0.0.0-20190918162654-250a1838aa2c/go.mod h1:LGhpyzd/3AkWcFcQJ3yO1UxMnJ6urMkCYfCp4iVxhjs=
k8s.io/kubernetes v1.16.3 h1:Bk2cKOdTtuGeod3+ytBeXxqIVHbh7Pu+aq0c+YJLX7g=
k8s.io/kubernetes v1.16.3/go.mod h1:hJd0X6w7E/MiE7PcDp11XHhdgQBYc33vP+WtTJqG/AU=
k8s.io/legacy-cloud-providers v0.0.0-20190918163543-cfa506e53441/go.mod h1:Phw/j+7dcoTPXRkv9Nyi3RJuA6SVSoHlc7M5K1pHizM=
k8s.io/metrics v0.0.0-20190918162108-227c654b2546 h1:GmR5FKUvbcVV2TLAVFusUFWENjlIg7KLldAST5DqalY=
k8s.io/metrics v0.0.0-20190918162108-227c654b2546/go.mod h1:XUFuIsGbIqaUga6Ivs02cCzxNjY4RPRvYnW0KhmnpQY=
k8s.io/repo-infra v0.0.0-20181204233714-00fe14e3d1a3/go.mod h1:+G1xBfZDfVFsm1Tj/HNCvg4QqWx8rJ2Fxpqr1rqp/gQ=
k8s.io/sample-apiserver v0.0.0-20190918161442-d4c9c65c82af/go.mod h1:HP/BmiRyZTMIZ5RI2p4tCz/b2kre7URuKLQ7/KHqWAs=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
@ -351,7 +621,10 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ=
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

View File

@ -1,22 +1,17 @@
package k8s
package client
import (
"fmt"
"path/filepath"
"strings"
"sync"
"time"
"k8s.io/client-go/discovery/cached/disk"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
authorizationv1 "k8s.io/api/authorization/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
@ -29,116 +24,78 @@ const NA = "n/a"
var supportedMetricsAPIVersions = []string{"v1beta1"}
type (
// Collection of empty interfaces.
Collection []interface{}
// Authorizer checks what a user can or cannot do to a resource.
type Authorizer interface {
// CanI returns true if the user can use these actions for a given resource.
CanI(ns, gvr string, verbs []string) (bool, error)
}
// Cruder represent a crudable Kubernetes resource.
Cruder interface {
Get(ns string, name string) (interface{}, error)
List(ns string) (Collection, error)
Delete(ns string, name string) error
SetFieldSelector(string)
SetLabelSelector(string)
}
// BOZO!! Refactor!
// Connection represents a Kubenetes apiserver connection.
type Connection interface {
Authorizer
// Connection represents a Kubenetes apiserver connection.
Connection interface {
Config() *Config
DialOrDie() kubernetes.Interface
SwitchContextOrDie(ctx string)
NSDialOrDie() dynamic.NamespaceableResourceInterface
CachedDiscovery() (*disk.CachedDiscoveryClient, error)
RestConfigOrDie() *restclient.Config
MXDial() (*versioned.Clientset, error)
DynDialOrDie() dynamic.Interface
HasMetrics() bool
IsNamespaced(n string) bool
SupportsResource(group string) bool
ValidNamespaces() ([]v1.Namespace, error)
NodePods(node string) (*v1.PodList, error)
SupportsRes(grp string, versions []string) (string, bool, error)
ServerVersion() (*version.Info, error)
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
CheckNSAccess(ns string) error
CheckListNSAccess() error
CanIAccess(ns, rvg string, verbs []string) (bool, error)
}
Config() *Config
DialOrDie() kubernetes.Interface
SwitchContextOrDie(ctx string)
NSDialOrDie() dynamic.NamespaceableResourceInterface
CachedDiscovery() (*disk.CachedDiscoveryClient, error)
RestConfigOrDie() *restclient.Config
MXDial() (*versioned.Clientset, error)
DynDialOrDie() dynamic.Interface
HasMetrics() bool
IsNamespaced(n string) bool
SupportsResource(group string) bool
ValidNamespaces() ([]v1.Namespace, error)
SupportsRes(grp string, versions []string) (string, bool, error)
ServerVersion() (*version.Info, error)
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
}
k8sClient struct {
client kubernetes.Interface
dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
}
// APIClient represents a Kubernetes api client.
APIClient struct {
k8sClient
cachedDiscovery *disk.CachedDiscoveryClient
config *Config
useMetricServer bool
log zerolog.Logger
mx sync.Mutex
}
)
// APIClient represents a Kubernetes api client.
type APIClient struct {
client kubernetes.Interface
dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
cachedDiscovery *disk.CachedDiscoveryClient
config *Config
useMetricServer bool
mx sync.Mutex
}
// InitConnectionOrDie initialize connection from command line args.
// Checks for connectivity with the api server.
func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient {
conn := APIClient{config: config, log: logger}
func InitConnectionOrDie(config *Config) *APIClient {
conn := APIClient{config: config}
conn.useMetricServer = conn.supportsMxServer()
return &conn
}
// CheckListNSAccess check if current user can list namespaces.
func (a *APIClient) CheckListNSAccess() error {
ns := NewNamespace(a)
_, err := ns.List("", metav1.ListOptions{})
return err
}
// CheckNSAccess asserts if user can access a namespace.
func (a *APIClient) CheckNSAccess(n string) error {
ns := NewNamespace(a)
if n == "" {
_, err := ns.List(n, metav1.ListOptions{})
return err
}
_, err := ns.Get("", n)
return err
}
func makeSAR(ns, rvg string) *authorizationv1.SelfSubjectAccessReview {
gvr, _ := schema.ParseResourceArg(strings.ToLower(rvg))
if gvr == nil {
panic(fmt.Errorf("Unable to get GVR from url %s", rvg))
}
log.Debug().Msgf("GVR for %s -- %#v", rvg, *gvr)
func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
res := GVR(gvr).AsGVR()
return &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: ns,
Group: gvr.Group,
Resource: gvr.Resource,
Group: res.Group,
Resource: res.Resource,
},
},
}
}
// CanIAccess checks if user has access to a certain resource.
func (a *APIClient) CanIAccess(ns, rvg string, verbs []string) (bool, error) {
sar := makeSAR(ns, rvg)
// CanI checks if user has access to a certain resource.
func (a *APIClient) CanI(ns, gvr string, verbs []string) (bool, error) {
sar := makeSAR(ns, gvr)
dial := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews()
for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v
resp, err := dial.Create(sar)
if err != nil {
log.Error().Err(err).Msgf("CanIAccess")
log.Error().Err(err).Msgf("CanI")
return false, err
}
if !resp.Status.Allowed {
@ -177,19 +134,6 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
return nn.Items, nil
}
// NodePods returns a collection of all available pods on a given node.
func (a *APIClient) NodePods(node string) (*v1.PodList, error) {
const selFmt = "spec.nodeName=%s,status.phase!=%s,status.phase!=%s"
fieldSelector, err := fields.ParseSelector(fmt.Sprintf(selFmt, node, v1.PodSucceeded, v1.PodFailed))
if err != nil {
return nil, err
}
return a.DialOrDie().CoreV1().Pods("").List(metav1.ListOptions{
FieldSelector: fieldSelector.String(),
})
}
// IsNamespaced check on server if given resource is namespaced
func (a *APIClient) IsNamespaced(res string) bool {
discovery, err := a.CachedDiscovery()
@ -247,7 +191,7 @@ func (a *APIClient) DialOrDie() kubernetes.Interface {
var err error
if a.client, err = kubernetes.NewForConfig(a.RestConfigOrDie()); err != nil {
a.log.Fatal().Msgf("Unable to connect to api server %v", err)
log.Fatal().Msgf("Unable to connect to api server %v", err)
}
return a.client
}
@ -256,7 +200,7 @@ func (a *APIClient) DialOrDie() kubernetes.Interface {
func (a *APIClient) RestConfigOrDie() *restclient.Config {
cfg, err := a.config.RESTConfig()
if err != nil {
a.log.Panic().Msgf("Unable to connect to api server %v", err)
log.Panic().Msgf("Unable to connect to api server %v", err)
}
return cfg
}
@ -286,7 +230,7 @@ func (a *APIClient) DynDialOrDie() dynamic.Interface {
var err error
if a.dClient, err = dynamic.NewForConfig(a.RestConfigOrDie()); err != nil {
a.log.Panic().Err(err)
log.Panic().Err(err)
}
return a.dClient
}
@ -318,7 +262,7 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) {
}
var err error
if a.mxsClient, err = versioned.NewForConfig(a.RestConfigOrDie()); err != nil {
a.log.Error().Err(err)
log.Error().Err(err)
}
return a.mxsClient, err
@ -328,14 +272,14 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) {
func (a *APIClient) SwitchContextOrDie(ctx string) {
currentCtx, err := a.config.CurrentContextName()
if err != nil {
panic(err)
log.Fatal().Err(err).Msg("Fetching current context")
}
if currentCtx != ctx {
a.cachedDiscovery = nil
a.reset()
if err := a.config.SwitchContext(ctx); err != nil {
panic(err)
log.Fatal().Err(err).Msg("Switching context")
}
a.useMetricServer = a.supportsMxServer()
}

View File

@ -1,8 +1,9 @@
package k8s
package client
import (
"errors"
"fmt"
"sync"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
@ -12,8 +13,6 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
const defaultNamespace = "default"
// Config tracks a kubernetes configuration.
type Config struct {
flags *genericclioptions.ConfigFlags
@ -21,11 +20,15 @@ type Config struct {
currentContext string
rawConfig *clientcmdapi.Config
restConfig *restclient.Config
mutex *sync.RWMutex
}
// NewConfig returns a new k8s config or an error if the flags are invalid.
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
return &Config{flags: f}
return &Config{
flags: f,
mutex: &sync.RWMutex{},
}
}
// Flags returns configuration flags.
@ -233,12 +236,18 @@ func (c *Config) NamespaceNames(nns []v1.Namespace) []string {
// ConfigAccess return the current kubeconfig api server access configuration.
func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
c.mutex.RLock()
defer c.mutex.RUnlock()
c.ensureConfig()
return c.clientConfig.ConfigAccess(), nil
}
// RawConfig fetch the current kubeconfig with no overrides.
func (c *Config) RawConfig() (clientcmdapi.Config, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.rawConfig != nil {
if c.rawConfig.CurrentContext == c.currentContext {
return *c.rawConfig, nil
@ -283,7 +292,6 @@ func (c *Config) ensureConfig() {
log.Debug().Msg("Loading raw config from flags...")
c.clientConfig = c.flags.ToRawKubeConfigLoader()
return
}
// ----------------------------------------------------------------------------

View File

@ -1,11 +1,11 @@
package k8s_test
package client_test
import (
"errors"
"fmt"
"testing"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
@ -28,7 +28,7 @@ func TestConfigCurrentContext(t *testing.T) {
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
cfg := client.NewConfig(u.flags)
ctx, err := cfg.CurrentContextName()
assert.Nil(t, err)
assert.Equal(t, u.context, ctx)
@ -46,7 +46,7 @@ func TestConfigCurrentCluster(t *testing.T) {
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
cfg := client.NewConfig(u.flags)
ctx, err := cfg.CurrentClusterName()
assert.Nil(t, err)
assert.Equal(t, u.cluster, ctx)
@ -64,7 +64,7 @@ func TestConfigCurrentUser(t *testing.T) {
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
cfg := client.NewConfig(u.flags)
ctx, err := cfg.CurrentUserName()
assert.Nil(t, err)
assert.Equal(t, u.user, ctx)
@ -83,7 +83,7 @@ func TestConfigCurrentNamespace(t *testing.T) {
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
cfg := client.NewConfig(u.flags)
ns, err := cfg.CurrentNamespaceName()
assert.Equal(t, u.err, err)
assert.Equal(t, u.namespace, ns)
@ -102,7 +102,7 @@ func TestConfigGetContext(t *testing.T) {
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
cfg := client.NewConfig(u.flags)
ctx, err := cfg.GetContext(u.cluster)
if err != nil {
assert.Equal(t, u.err, err)
@ -120,7 +120,7 @@ func TestConfigSwitchContext(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
err := cfg.SwitchContext("blee")
assert.Nil(t, err)
ctx, err := cfg.CurrentContextName()
@ -135,7 +135,7 @@ func TestConfigClusterNameFromContext(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
cl, err := cfg.ClusterNameFromContext("blee")
assert.Nil(t, err)
assert.Equal(t, "blee", cl)
@ -148,7 +148,7 @@ func TestConfigAccess(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
acc, err := cfg.ConfigAccess()
assert.Nil(t, err)
assert.True(t, len(acc.GetDefaultFilename()) > 0)
@ -161,7 +161,7 @@ func TestConfigContexts(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
cc, err := cfg.Contexts()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
@ -174,7 +174,7 @@ func TestConfigContextNames(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
cc, err := cfg.ContextNames()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
@ -187,7 +187,7 @@ func TestConfigClusterNames(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
cc, err := cfg.ClusterNames()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
@ -200,7 +200,7 @@ func TestConfigDelContext(t *testing.T) {
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
err := cfg.DelContext("fred")
assert.Nil(t, err)
cc, err := cfg.ContextNames()
@ -214,7 +214,7 @@ func TestConfigRestConfig(t *testing.T) {
KubeConfig: &kubeConfig,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
rc, err := cfg.RESTConfig()
assert.Nil(t, err)
assert.Equal(t, "https://localhost:3000", rc.Host)
@ -226,7 +226,7 @@ func TestConfigBadConfig(t *testing.T) {
KubeConfig: &kubeConfig,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
_, err := cfg.RESTConfig()
assert.NotNil(t, err)
}
@ -238,7 +238,7 @@ func TestNamespaceNames(t *testing.T) {
KubeConfig: &kubeConfig,
}
cfg := k8s.NewConfig(&flags)
cfg := client.NewConfig(&flags)
nn := []v1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "ns1"}},

142
internal/client/gvr.go Normal file
View File

@ -0,0 +1,142 @@
package client
import (
"fmt"
"path"
"strings"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/runtime/schema"
"vbom.ml/util/sortorder"
)
// GVR represents a kubernetes resource schema as a string.
// Format is group/version/resources
type GVR string
// NewGVR builds a new gvr from a group, version, resource.
func NewGVR(g, v, r string) GVR {
return GVR(path.Join(g, v, r))
}
// FromGVAndR builds a gvr from a group/version and resource.
func FromGVAndR(gv, r string) GVR {
return GVR(path.Join(gv, r))
}
// ResName returns a resource . separated descriptor in the shape of kind.version.group.
func (g GVR) ResName() string {
return g.ToR() + "." + g.ToV() + "." + g.ToG()
}
// String returns gvr as string.
func (g GVR) String() string {
return string(g)
}
// AsGV returns the group version scheme representation.
func (g GVR) AsGV() schema.GroupVersion {
return schema.GroupVersion{
Group: g.ToG(),
Version: g.ToV(),
}
}
// AsGVR returns a a full schema representation.
func (g GVR) AsGVR() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: g.ToG(),
Version: g.ToV(),
Resource: g.ToR(),
}
}
// ToV returns the resource version.
func (g GVR) ToV() string {
tokens := strings.Split(string(g), "/")
if len(tokens) < 2 {
return ""
}
return tokens[len(tokens)-2]
}
func (g GVR) ToRAndG() (string, string) {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[2], tokens[0]
case 2:
return tokens[1], "core"
default:
return tokens[0], "core"
}
}
// ToR returns the resource name.
func (g GVR) ToR() string {
tokens := strings.Split(string(g), "/")
return tokens[len(tokens)-1]
}
// ToG returns the resource group name.
func (g GVR) ToG() string {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[0]
default:
return ""
}
}
//
type GVRs []GVR
func (g GVRs) Len() int {
return len(g)
}
func (g GVRs) Swap(i, j int) {
g[i], g[j] = g[j], g[i]
}
func (g GVRs) Less(i, j int) bool {
g1, g2 := g[i].ToG(), g[j].ToG()
return sortorder.NaturalLess(g1, g2)
}
// Helper...
// Can determines the available actions for a given resource.
func Can(verbs []string, v string) bool {
for _, verb := range verbs {
candidates, err := mapVerb(v)
if err != nil {
log.Error().Err(err).Msgf("verb mapping failed")
return false
}
for _, c := range candidates {
if verb == c {
return true
}
}
}
return false
}
func mapVerb(v string) ([]string, error) {
switch v {
case "describe":
return []string{"get"}, nil
case "view":
return []string{"get", "list"}, nil
case "delete":
return []string{"delete"}, nil
case "edit":
return []string{"patch", "update"}, nil
default:
return []string{}, fmt.Errorf("no standard verb for %q", v)
}
}

185
internal/client/gvr_test.go Normal file
View File

@ -0,0 +1,185 @@
package client_test
import (
"sort"
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestGVRSort(t *testing.T) {
gg := client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"}
sort.Sort(gg)
assert.Equal(t, client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"}, gg)
}
func TestGVRCan(t *testing.T) {
uu := map[string]struct {
vv []string
v string
e bool
}{
"describe": {[]string{"get"}, "describe", true},
"view": {[]string{"get", "list", "watch"}, "view", true},
"delete": {[]string{"delete", "list", "watch"}, "delete", true},
"no_delete": {[]string{"get", "list", "watch"}, "delete", false},
"edit": {[]string{"path", "update", "watch"}, "edit", true},
"no_edit": {[]string{"get", "list", "watch"}, "edit", false},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.Can(u.vv, u.v))
})
}
}
func TestAsGVR(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersionResource
}{
"full": {"apps/v1/deployments", schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}},
"core": {"v1/pods", schema.GroupVersionResource{Version: "v1", Resource: "pods"}},
"bork": {"users", schema.GroupVersionResource{Resource: "users"}},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).AsGVR())
})
}
}
func TestAsGV(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersion
}{
"full": {"apps/v1/deployments", schema.GroupVersion{Group: "apps", Version: "v1"}},
"core": {"v1/pods", schema.GroupVersion{Version: "v1"}},
"bork": {"users", schema.GroupVersion{}},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).AsGV())
})
}
}
func TestNewGVR(t *testing.T) {
uu := map[string]struct {
g, v, r string
e string
}{
"full": {"apps", "v1", "deployments", "apps/v1/deployments"},
"core": {"", "v1", "pods", "v1/pods"},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.g, u.v, u.r).String())
})
}
}
func TestResName(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments.v1.apps"},
"core": {"v1/pods", "pods.v1."},
"k9s": {"users", "users.."},
"empty": {"", ".."},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ResName())
})
}
}
func TestToR(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments"},
"core": {"v1/pods", "pods"},
"k9s": {"users", "users"},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToR())
})
}
}
func TestToG(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "apps"},
"core": {"v1/pods", ""},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToG())
})
}
}
func TestToV(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "v1"},
"core": {"v1beta1/pods", "v1beta1"},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToV())
})
}
}
func TestToString(t *testing.T) {
uu := map[string]struct {
gvr string
}{
"full": {"apps/v1/deployments"},
"core": {"v1beta1/pods"},
"k9s": {"users"},
"empty": {""},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, client.GVR(u.gvr).String())
})
}
}

View File

@ -0,0 +1,37 @@
package client_test
import (
"testing"
"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
)
func TestNamespaced(t *testing.T) {
uu := []struct {
p, ns, n string
}{
{"fred/blee", "fred", "blee"},
{"blee", "", "blee"},
}
for _, u := range uu {
ns, n := client.Namespaced(u.p)
assert.Equal(t, u.ns, ns)
assert.Equal(t, u.n, n)
}
}
func TestFQN(t *testing.T) {
uu := []struct {
ns, n string
e string
}{
{"fred", "blee", "fred/blee"},
{"", "blee", "blee"},
}
for _, u := range uu {
assert.Equal(t, u.e, client.FQN(u.ns, u.n))
}
}

View File

@ -0,0 +1,40 @@
package client
import (
"os/user"
"path"
"regexp"
"strings"
"github.com/rs/zerolog/log"
)
var toFileName = regexp.MustCompile(`[^(\w/\.)]`)
// Namespaced converts a resource path to namespace and resource name.
func Namespaced(p string) (string, string) {
ns, n := path.Split(p)
return strings.Trim(ns, "/"), n
}
// FQN returns a fully qualified resource name.
func FQN(ns, n string) string {
if ns == "" {
return n
}
return ns + "/" + n
}
func mustHomeDir() string {
usr, err := user.Current()
if err != nil {
log.Fatal().Err(err).Msg("Die getting user home directory")
}
return usr.HomeDir
}
func toHostDir(host string) string {
h := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
return toFileName.ReplaceAllString(h, "_")
}

View File

@ -1,6 +1,8 @@
package k8s
package client
import (
"math"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
@ -9,7 +11,6 @@ import (
type (
// MetricsServer serves cluster metrics for nodes and pods.
MetricsServer struct {
*base
Connection
}
@ -45,47 +46,44 @@ type (
// NewMetricsServer return a metric server instance.
func NewMetricsServer(c Connection) *MetricsServer {
return &MetricsServer{&base{}, c}
return &MetricsServer{Connection: c}
}
// NodesMetrics retrieves metrics for a given set of nodes.
func (m *MetricsServer) NodesMetrics(nodes Collection, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
for _, n := range nodes {
no := n.(*v1.Node)
func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
for _, no := range nodes.Items {
mmx[no.Name] = NodeMetrics{
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
AvailMEM: toMB(no.Status.Allocatable.Memory().Value()),
TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
TotalMEM: toMB(no.Status.Capacity.Memory().Value()),
}
}
for _, c := range metrics.Items {
if mx, ok := mmx[c.Name]; ok {
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
mx.CurrentMEM = toMB(c.Usage.Memory().Value())
mmx[c.Name] = mx
}
}
}
// ClusterLoad retrieves all cluster nodes metrics.
func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterMetrics) {
nodeMetrics := make(NodesMetrics, len(nos))
for _, n := range nos {
no := n.(*v1.Node)
func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsList, mx *ClusterMetrics) error {
nodeMetrics := make(NodesMetrics, len(nos.Items))
for _, no := range nos.Items {
nodeMetrics[no.Name] = NodeMetrics{
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
AvailMEM: toMB(no.Status.Allocatable.Memory().Value()),
}
}
for _, mx := range nmx {
mxx := mx.(*mv1beta1.NodeMetrics)
if m, ok := nodeMetrics[mxx.Name]; ok {
m.CurrentCPU = mxx.Usage.Cpu().MilliValue()
m.CurrentMEM = ToMB(mxx.Usage.Memory().Value())
nodeMetrics[mxx.Name] = m
for _, mx := range nmx.Items {
if m, ok := nodeMetrics[mx.Name]; ok {
m.CurrentCPU = mx.Usage.Cpu().MilliValue()
m.CurrentMEM = toMB(mx.Usage.Memory().Value())
nodeMetrics[mx.Name] = m
}
}
@ -96,8 +94,9 @@ func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterM
mem += mx.CurrentMEM
tmem += mx.AvailMEM
}
mx.PercCPU, mx.PercMEM = toPerc(cpu, tcpu), toPerc(mem, tmem)
return nil
}
// FetchNodesMetrics return all metrics for pods in a given namespace.
@ -120,6 +119,16 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e
return client.MetricsV1beta1().PodMetricses(ns).List(metav1.ListOptions{})
}
// FetchPodsMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchPodMetrics(ns, sel string) (*mv1beta1.PodMetrics, error) {
client, err := m.MXDial()
if err != nil {
return nil, err
}
return client.MetricsV1beta1().PodMetricses(ns).Get(sel, metav1.GetOptions{})
}
// PodsMetrics retrieves metrics for all pods in a given namespace.
func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetrics) {
// Compute all pod's containers metrics.
@ -127,8 +136,25 @@ func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetri
var mx PodMetrics
for _, c := range p.Containers {
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
mx.CurrentMEM += toMB(c.Usage.Memory().Value())
}
mmx[p.Namespace+"/"+p.Name] = mx
}
}
// 0---------------------------------------------------------------------------
// Helpers...
const megaByte = 1024 * 1024
// toMB converts bytes to megabytes.
func toMB(v int64) float64 {
return float64(v) / megaByte
}
func toPerc(v1, v2 float64) float64 {
if v2 == 0 {
return 0
}
return math.Round((v1 / v2) * 100)
}

View File

@ -1,4 +1,4 @@
package k8s
package client
import (
"testing"
@ -7,6 +7,7 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
@ -52,9 +53,11 @@ func BenchmarkPodsMetrics(b *testing.B) {
func TestNodesMetrics(t *testing.T) {
m := NewMetricsServer(nil)
nodes := Collection{
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "2Mi"),
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "10Mi"),
},
}
metrics := v1beta1.NodeMetricsList{
@ -65,7 +68,7 @@ func TestNodesMetrics(t *testing.T) {
}
mmx := make(NodesMetrics)
m.NodesMetrics(nodes, &metrics, mmx)
m.NodesMetrics(&nodes, &metrics, mmx)
assert.Equal(t, 2, len(mmx))
mx, ok := mmx["n1"]
assert.True(t, ok)
@ -78,9 +81,11 @@ func TestNodesMetrics(t *testing.T) {
}
func BenchmarkNodesMetrics(b *testing.B) {
nodes := Collection{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "100m", "2Mi"),
makeNode("n2", "100m", "4Mi", "100m", "2Mi"),
},
}
metrics := v1beta1.NodeMetricsList{
@ -96,38 +101,46 @@ func BenchmarkNodesMetrics(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
m.NodesMetrics(nodes, &metrics, mmx)
m.NodesMetrics(&nodes, &metrics, mmx)
}
}
func TestClusterLoad(t *testing.T) {
m := NewMetricsServer(nil)
nodes := Collection{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
},
}
metrics := Collection{
makeMxNode("n1", "50m", "1Mi"),
makeMxNode("n2", "50m", "1Mi"),
metrics := mv1beta1.NodeMetricsList{
Items: []mv1beta1.NodeMetrics{
*makeMxNode("n1", "50m", "1Mi"),
*makeMxNode("n2", "50m", "1Mi"),
},
}
var mx ClusterMetrics
m.ClusterLoad(nodes, metrics, &mx)
m.ClusterLoad(&nodes, &metrics, &mx)
assert.Equal(t, 100.0, mx.PercCPU)
assert.Equal(t, 50.0, mx.PercMEM)
}
func BenchmarkClusterLoad(b *testing.B) {
nodes := Collection{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
nodes := v1.NodeList{
Items: []v1.Node{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
},
}
metrics := Collection{
makeMxNode("n1", "50m", "1Mi"),
makeMxNode("n2", "50m", "1Mi"),
metrics := mv1beta1.NodeMetricsList{
Items: []mv1beta1.NodeMetrics{
*makeMxNode("n1", "50m", "1Mi"),
*makeMxNode("n2", "50m", "1Mi"),
},
}
m := NewMetricsServer(nil)
@ -135,7 +148,7 @@ func BenchmarkClusterLoad(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
m.ClusterLoad(nodes, metrics, &mx)
m.ClusterLoad(&nodes, &metrics, &mx)
}
}
@ -156,8 +169,8 @@ func makeMxPod(name, cpu, mem string) *v1beta1.PodMetrics {
}
}
func makeNode(name, tcpu, tmem, acpu, amem string) *v1.Node {
return &v1.Node{
func makeNode(name, tcpu, tmem, acpu, amem string) v1.Node {
return v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},

View File

@ -17,7 +17,8 @@ func TestColorize(t *testing.T) {
"default": {"blee", 0, "\x1b[37mblee\x1b[0m"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, Colorize(u.s, u.c))
})

View File

@ -14,6 +14,9 @@ var K9sAlias = filepath.Join(K9sHome, "alias.yml")
// Alias tracks shortname to GVR mappings.
type Alias map[string]string
// ShortNames represents a collection of shortnames for aliases.
type ShortNames map[string][]string
// Aliases represents a collection of aliases.
type Aliases struct {
Alias Alias `yaml:"alias"`
@ -21,54 +24,64 @@ type Aliases struct {
// NewAliases return a new alias.
func NewAliases() Aliases {
aa := Aliases{Alias: make(Alias, 50)}
aa.loadDefaults()
return aa
return Aliases{
Alias: make(Alias, 50),
}
}
func (a Aliases) loadDefaults() {
const (
contexts = "contexts"
portFwds = "portforwards"
benchmarks = "benchmarks"
dumps = "screendumps"
groups = "groups"
users = "users"
)
a.Alias["dp"] = "apps/v1/deployments"
a.Alias["sec"] = "v1/secrets"
a.Alias["jo"] = "batch/v1/jobs"
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles"
a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings"
a.Alias["rb"] = "rbac.authorization.k8s.io/v1/rolebindings"
a.Alias["np"] = "networking.k8s.io/v1/networkpolicies"
{
a.Alias["ctx"] = "contexts"
a.Alias["contexts"] = "contexts"
a.Alias["context"] = "contexts"
a.Alias["ctx"] = contexts
a.Alias[contexts] = contexts
a.Alias["context"] = contexts
}
{
a.Alias["usr"] = "users"
a.Alias["users"] = "users"
a.Alias["user"] = "user"
a.Alias["usr"] = users
a.Alias[users] = users
a.Alias["user"] = users
}
{
a.Alias["grp"] = "groups"
a.Alias["group"] = "groups"
a.Alias["groups"] = "groups"
a.Alias["grp"] = groups
a.Alias["group"] = groups
a.Alias[groups] = groups
}
{
a.Alias["pf"] = "portforwards"
a.Alias["portforwards"] = "portforwards"
a.Alias["portforward"] = "portforwards"
a.Alias["pf"] = portFwds
a.Alias[portFwds] = portFwds
a.Alias["portforward"] = portFwds
}
{
a.Alias["be"] = "benchmarks"
a.Alias["benchmark"] = "benchmarks"
a.Alias["benchmarks"] = "benchmarks"
a.Alias["be"] = benchmarks
a.Alias["benchmark"] = benchmarks
a.Alias[benchmarks] = benchmarks
}
{
a.Alias["sd"] = "screendumps"
a.Alias["screendump"] = "screendumps"
a.Alias["screendumps"] = "screendumps"
a.Alias["sd"] = dumps
a.Alias["screendump"] = dumps
a.Alias[dumps] = dumps
}
}
// Load K9s aliases.
func (a Aliases) Load() error {
a.loadDefaults()
return a.LoadAliases(K9sAlias)
}
@ -79,20 +92,21 @@ func (a Aliases) Get(k string) (string, bool) {
}
// Define declares a new alias.
func (a Aliases) Define(command, alias string) {
if _, ok := a.Alias[alias]; ok {
// Don't override aliases. Take order of alias registration as precedence.
return
func (a Aliases) Define(gvr string, aliases ...string) {
for _, alias := range aliases {
if _, ok := a.Alias[alias]; ok {
continue
}
a.Alias[alias] = gvr
}
a.Alias[alias] = command
}
// LoadAliases loads alias from a given file.
func (a Aliases) LoadAliases(path string) error {
f, err := ioutil.ReadFile(path)
if err != nil {
return err
log.Warn().Err(err).Msgf("No custom aliases found")
return nil
}
var aa Aliases

View File

@ -13,7 +13,7 @@ func TestAliasDefine(t *testing.T) {
aliases []string
}
tts := []struct {
uu := []struct {
name string
aliases []aliasDef
registeredCommands map[string]string
@ -51,15 +51,16 @@ func TestAliasDefine(t *testing.T) {
},
}
for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) {
for i := range uu {
u := uu[i]
t.Run(u.name, func(t *testing.T) {
configAlias := config.NewAliases()
for _, aliases := range tt.aliases {
for _, aliases := range u.aliases {
for _, a := range aliases.aliases {
configAlias.Define(aliases.cmd, a)
}
}
for alias, cmd := range tt.registeredCommands {
for alias, cmd := range u.registeredCommands {
v, ok := configAlias.Get(alias)
assert.True(t, ok)
assert.Equal(t, cmd, v, "Wrong command for alias "+alias)
@ -70,18 +71,17 @@ func TestAliasDefine(t *testing.T) {
func TestAliasesLoad(t *testing.T) {
a := config.NewAliases()
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
assert.Equal(t, 27, len(a.Alias))
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
assert.Equal(t, 2, len(a.Alias))
}
func TestAliasesSave(t *testing.T) {
a := config.NewAliases()
a.Alias["test"] = "fred"
a.Alias["blee"] = "duh"
a.SaveAliases("/tmp/a.yml")
assert.Nil(t, a.SaveAliases("/tmp/a.yml"))
assert.Nil(t, a.LoadAliases("/tmp/a.yml"))
assert.Equal(t, 28, len(a.Alias))
assert.Equal(t, 2, len(a.Alias))
}

View File

@ -16,7 +16,8 @@ func TestBenchEmpty(t *testing.T) {
"notEmpty": {newBenchmark(), false},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.b.empty())
})
@ -46,7 +47,8 @@ func TestBenchLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench(u.file)
@ -95,7 +97,8 @@ func TestBenchServiceLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("test_assets/b_good.yml")
@ -165,7 +168,8 @@ func TestBenchContainerLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("test_assets/b_containers.yml")

View File

@ -1,5 +1,7 @@
package config
import "github.com/derailed/k9s/internal/client"
// Cluster tracks K9s cluster configuration.
type Cluster struct {
Namespace *Namespace `yaml:"namespace"`
@ -12,7 +14,7 @@ func NewCluster() *Cluster {
}
// Validate a cluster config.
func (c *Cluster) Validate(conn Connection, ks KubeSettings) {
func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) {
if c.Namespace == nil {
c.Namespace = NewNamespace()
}

View File

@ -1,8 +1,5 @@
package config
// BOZO!! Once yaml is stable implement validation
// go get gopkg.in/validator.v2
import (
"errors"
"fmt"
@ -10,8 +7,7 @@ import (
"os"
"path/filepath"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
@ -30,9 +26,6 @@ var (
)
type (
// Connection represents a kubernetes api server connection.
Connection k8s.Connection
// KubeSettings exposes kubeconfig context information.
KubeSettings interface {
// CurrentContextName returns the name of the current context.
@ -54,7 +47,7 @@ type (
// Config tracks K9s configuration options.
Config struct {
K9s *K9s `yaml:"k9s"`
client Connection
client client.Connection
settings KubeSettings
}
)
@ -84,7 +77,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
}
c.K9s.CurrentCluster = ctx.Cluster
if len(ctx.Namespace) != 0 {
c.SetActiveNamespace(ctx.Namespace)
if err := c.SetActiveNamespace(ctx.Namespace); err != nil {
return err
}
}
if isSet(flags.ClusterName) {
@ -92,7 +87,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
}
if isSet(flags.Namespace) {
c.SetActiveNamespace(*flags.Namespace)
if err := c.SetActiveNamespace(*flags.Namespace); err != nil {
return err
}
}
return nil
@ -119,7 +116,7 @@ func (c *Config) ActiveNamespace() string {
return cl.Namespace.Active
}
}
return resource.DefaultNamespace
return "default"
}
// FavNamespaces returns fav namespaces in the current cluster.
@ -165,12 +162,12 @@ func (c *Config) SetActiveView(view string) {
}
// GetConnection return an api server connection.
func (c *Config) GetConnection() Connection {
func (c *Config) GetConnection() client.Connection {
return c.client
}
// SetConnection set an api server connection.
func (c *Config) SetConnection(conn Connection) {
func (c *Config) SetConnection(conn client.Connection) {
c.client = conn
}

View File

@ -54,7 +54,8 @@ func TestConfigRefine(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
@ -142,7 +143,7 @@ func TestConfigSetActiveNamespace(t *testing.T) {
cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveNamespace("default")
assert.Nil(t, cfg.SetActiveNamespace("default"))
assert.Equal(t, "default", cfg.ActiveNamespace())
}
@ -202,7 +203,7 @@ func TestConfigSaveFile(t *testing.T) {
cfg := config.NewConfig(mk)
cfg.SetConnection(mc)
cfg.Load("test_assets/k9s.yml")
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.K9s.RefreshRate = 100
cfg.K9s.LogBufferSize = 500
cfg.K9s.LogRequestSize = 100
@ -231,7 +232,7 @@ func TestConfigReset(t *testing.T) {
cfg := config.NewConfig(mk)
cfg.SetConnection(mc)
cfg.Load("test_assets/k9s.yml")
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.Reset()
cfg.Validate()

View File

@ -40,7 +40,7 @@ func InNSList(nn []interface{}, ns string) bool {
func mustK9sHome() string {
usr, err := user.Current()
if err != nil {
panic(err)
log.Fatal().Err(err).Msg("Die on retriving user home")
}
return usr.HomeDir
}
@ -49,7 +49,7 @@ func mustK9sHome() string {
func MustK9sUser() string {
usr, err := user.Current()
if err != nil {
panic(err)
log.Fatal().Err(err).Msg("Die on retriving user info")
}
return usr.Username
}
@ -59,8 +59,7 @@ func EnsurePath(path string, mod os.FileMode) {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, mod); err != nil {
log.Error().Msgf("Unable to create K9s home config dir: %v", err)
panic(err)
log.Fatal().Msgf("Unable to create K9s home config dir: %v", err)
}
}
}

53
internal/config/hotkey.go Normal file
View File

@ -0,0 +1,53 @@
package config
import (
"io/ioutil"
"path/filepath"
"gopkg.in/yaml.v2"
)
// K9sHotKeys manages K9s hotKeys.
var K9sHotKeys = filepath.Join(K9sHome, "hotkey.yml")
// HotKeys represents a collection of plugins.
type HotKeys struct {
HotKey map[string]HotKey `yaml:"hotKey"`
}
// HotKey describes a K9s hotkey.
type HotKey struct {
ShortCut string `yaml:"shortCut"`
Description string `yaml:"description"`
Command string `yaml:"command"`
}
// NewHotKeys returns a new plugin.
func NewHotKeys() HotKeys {
return HotKeys{
HotKey: make(map[string]HotKey),
}
}
// Load K9s plugins.
func (h HotKeys) Load() error {
return h.LoadHotKeys(K9sHotKeys)
}
// LoadHotKeys loads plugins from a given file.
func (h HotKeys) LoadHotKeys(path string) error {
f, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var hh HotKeys
if err := yaml.Unmarshal(f, &hh); err != nil {
return err
}
for k, v := range hh.HotKey {
h.HotKey[k] = v
}
return nil
}

View File

@ -0,0 +1,21 @@
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestHotKeyLoad(t *testing.T) {
h := config.NewHotKeys()
assert.Nil(t, h.LoadHotKeys("test_assets/hot_key.yml"))
assert.Equal(t, 1, len(h.HotKey))
k, ok := h.HotKey["pods"]
assert.True(t, ok)
assert.Equal(t, "shift-0", k.ShortCut)
assert.Equal(t, "Launch pod view", k.Description)
assert.Equal(t, "pods", k.Command)
}

View File

@ -1,8 +1,6 @@
package config
import (
"github.com/derailed/k9s/internal/k8s"
)
import "github.com/derailed/k9s/internal/client"
const (
defaultRefreshRate = 2
@ -114,7 +112,7 @@ func (k *K9s) checkClusters(ks KubeSettings) {
}
// Validate the current configuration.
func (k *K9s) Validate(c k8s.Connection, ks KubeSettings) {
func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
k.validateDefaults()
if k.Clusters == nil {

View File

@ -1,10 +1,10 @@
// Code generated by pegomock. DO NOT EDIT.
// Source: github.com/derailed/k9s/internal/config (interfaces: Connection)
// Source: github.com/derailed/k9s/internal/client (interfaces: Connection)
package config_test
import (
k8s "github.com/derailed/k9s/internal/k8s"
client "github.com/derailed/k9s/internal/client"
pegomock "github.com/petergtz/pegomock"
v1 "k8s.io/api/core/v1"
version "k8s.io/apimachinery/pkg/version"
@ -51,12 +51,12 @@ func (mock *MockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, erro
return ret0, ret1
}
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 []string) (bool, error) {
func (mock *MockConnection) CanI(_param0 string, _param1 string, _param2 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{_param0, _param1, _param2}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanI", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
@ -70,46 +70,16 @@ func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 [
return ret0, ret1
}
func (mock *MockConnection) CheckListNSAccess() error {
func (mock *MockConnection) Config() *client.Config {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("CheckListNSAccess", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**client.Config)(nil)).Elem()})
var ret0 *client.Config
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(error)
}
}
return ret0
}
func (mock *MockConnection) CheckNSAccess(_param0 string) error {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{_param0}
result := pegomock.GetGenericMockFrom(mock).Invoke("CheckNSAccess", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(error)
}
}
return ret0
}
func (mock *MockConnection) Config() *k8s.Config {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**k8s.Config)(nil)).Elem()})
var ret0 *k8s.Config
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(*k8s.Config)
ret0 = result[0].(*client.Config)
}
}
return ret0
@ -419,23 +389,23 @@ func (c *MockConnection_CachedDiscovery_OngoingVerification) GetCapturedArgument
func (c *MockConnection_CachedDiscovery_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) CanIAccess(_param0 string, _param1 string, _param2 []string) *MockConnection_CanIAccess_OngoingVerification {
func (verifier *VerifierMockConnection) CanI(_param0 string, _param1 string, _param2 []string) *MockConnection_CanI_OngoingVerification {
params := []pegomock.Param{_param0, _param1, _param2}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanIAccess", params, verifier.timeout)
return &MockConnection_CanIAccess_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanI", params, verifier.timeout)
return &MockConnection_CanI_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockConnection_CanIAccess_OngoingVerification struct {
type MockConnection_CanI_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *MockConnection_CanIAccess_OngoingVerification) GetCapturedArguments() (string, string, []string) {
func (c *MockConnection_CanI_OngoingVerification) GetCapturedArguments() (string, string, []string) {
_param0, _param1, _param2 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1]
}
func (c *MockConnection_CanIAccess_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) {
func (c *MockConnection_CanI_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
@ -454,50 +424,6 @@ func (c *MockConnection_CanIAccess_OngoingVerification) GetAllCapturedArguments(
return
}
func (verifier *VerifierMockConnection) CheckListNSAccess() *MockConnection_CheckListNSAccess_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CheckListNSAccess", params, verifier.timeout)
return &MockConnection_CheckListNSAccess_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockConnection_CheckListNSAccess_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *MockConnection_CheckListNSAccess_OngoingVerification) GetCapturedArguments() {
}
func (c *MockConnection_CheckListNSAccess_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) CheckNSAccess(_param0 string) *MockConnection_CheckNSAccess_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CheckNSAccess", params, verifier.timeout)
return &MockConnection_CheckNSAccess_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockConnection_CheckNSAccess_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *MockConnection_CheckNSAccess_OngoingVerification) GetCapturedArguments() string {
_param0 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1]
}
func (c *MockConnection_CheckNSAccess_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}
func (verifier *VerifierMockConnection) Config() *MockConnection_Config_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout)

View File

@ -1,12 +1,13 @@
package config
import (
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
)
const (
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
MaxFavoritesNS = 10
MaxFavoritesNS = 9
defaultNS = "default"
allNS = "all"
)
@ -26,7 +27,7 @@ func NewNamespace() *Namespace {
}
// Validate a namespace is setup correctly
func (n *Namespace) Validate(c Connection, ks KubeSettings) {
func (n *Namespace) Validate(c client.Connection, ks KubeSettings) {
nns, err := c.ValidNamespaces()
if err != nil {
return

View File

@ -12,4 +12,11 @@ func TestPluginLoad(t *testing.T) {
assert.Nil(t, p.LoadPlugins("test_assets/plugin.yml"))
assert.Equal(t, 1, len(p.Plugin))
k, ok := p.Plugin["blah"]
assert.True(t, ok)
assert.Equal(t, "shift-s", k.ShortCut)
assert.Equal(t, "blee", k.Description)
assert.Equal(t, []string{"po", "dp"}, k.Scopes)
assert.Equal(t, "duh", k.Command)
assert.Equal(t, []string{"-n", "$NAMESPACE", "-boolean"}, k.Args)
}

View File

@ -14,10 +14,15 @@ var (
K9sStylesFile = filepath.Join(K9sHome, "skin.yml")
)
type StyleListener interface {
StylesChanged(*Styles)
}
type (
// Styles tracks K9s styling options.
Styles struct {
K9s Style `yaml:"k9s"`
K9s Style `yaml:"k9s"`
listeners []StyleListener
}
// Body tracks body styles.
@ -132,7 +137,7 @@ func newStyle() Style {
Body: newBody(),
Frame: newFrame(),
Info: newInfo(),
Table: newTable(),
Table: newGetTable(),
Views: newViews(),
}
}
@ -211,12 +216,12 @@ func newInfo() Info {
}
// NewTable returns a new table style.
func newTable() Table {
func newGetTable() Table {
return Table{
FgColor: "aqua",
BgColor: "black",
CursorColor: "aqua",
MarkColor: "darkgoldenrod",
MarkColor: "violet",
Header: newTableHeader(),
}
}
@ -257,9 +262,10 @@ func newMenu() Menu {
}
// NewStyles creates a new default config.
func NewStyles(path string) (*Styles, error) {
s := &Styles{K9s: newStyle()}
return s, s.load(path)
func NewStyles() *Styles {
return &Styles{
K9s: newStyle(),
}
}
// FgColor returns the foreground color.
@ -272,6 +278,30 @@ func (s *Styles) BgColor() tcell.Color {
return AsColor(s.Body().BgColor)
}
func (s *Styles) AddListener(l StyleListener) {
s.listeners = append(s.listeners, l)
}
func (s *Styles) RemoveListener(l StyleListener) {
victim := -1
for i, lis := range s.listeners {
if lis == l {
victim = i
break
}
}
if victim == -1 {
return
}
s.listeners = append(s.listeners[:victim], s.listeners[victim+1:]...)
}
func (s *Styles) fireStylesChanged() {
for _, list := range s.listeners {
list.StylesChanged(s)
}
}
// Body returns body styles.
func (s *Styles) Body() Body {
return s.K9s.Body
@ -293,7 +323,7 @@ func (s *Styles) Title() Title {
}
// Table returns table styles.
func (s *Styles) Table() Table {
func (s *Styles) GetTable() Table {
return s.K9s.Table
}
@ -303,7 +333,7 @@ func (s *Styles) Views() Views {
}
// Load K9s configuration from file
func (s *Styles) load(path string) error {
func (s *Styles) Load(path string) error {
f, err := ioutil.ReadFile(path)
if err != nil {
return err
@ -312,6 +342,7 @@ func (s *Styles) load(path string) error {
if err := yaml.Unmarshal(f, s); err != nil {
return err
}
s.fireStylesChanged()
return nil
}
@ -327,10 +358,9 @@ func (s *Styles) Update() {
// AsColor checks color index, if match return color otherwise pink it is.
func AsColor(c string) tcell.Color {
// Use tcell.GetColor to support hex codes.
// "Creates a Color from a color name (W3C name). A hex value may be supplied as a string in the format "#ffffff"."
if color := tcell.GetColor(c); color != -1 {
if color, ok := tcell.ColorNames[c]; ok {
return color
}
return tcell.ColorPink
return tcell.GetColor(c)
}

View File

@ -1,51 +1,62 @@
package config
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/stretchr/testify/assert"
)
func TestSkinNone(t *testing.T) {
s, err := NewStyles("test_assets/empty_skin.yml")
assert.Nil(t, err)
func TestAsColor(t *testing.T) {
uu := map[string]tcell.Color{
"blah": tcell.ColorDefault,
"blue": tcell.ColorBlue,
"#ffffff": tcell.NewHexColor(33554431),
"#ff0000": tcell.NewHexColor(33488896),
}
for k := range uu {
c, u := k, uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u, config.AsColor(c))
})
}
}
func TestSkinNone(t *testing.T) {
s := config.NewStyles()
assert.Nil(t, s.Load("test_assets/empty_skin.yml"))
s.Update()
assert.Equal(t, "cadetblue", s.Body().FgColor)
assert.Equal(t, "black", s.Body().BgColor)
assert.Equal(t, "black", s.Table().BgColor)
assert.Equal(t, "black", s.GetTable().BgColor)
assert.Equal(t, tcell.ColorCadetBlue, s.FgColor())
assert.Equal(t, tcell.ColorBlack, s.BgColor())
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
assert.Equal(t, tcell.ColorPink, AsColor("blah"))
assert.Equal(t, tcell.ColorWhite, AsColor("white"))
}
func TestSkin(t *testing.T) {
s, err := NewStyles("test_assets/black_and_wtf.yml")
assert.Nil(t, err)
s := config.NewStyles()
assert.Nil(t, s.Load("test_assets/black_and_wtf.yml"))
s.Update()
assert.Equal(t, "white", s.Body().FgColor)
assert.Equal(t, "black", s.Body().BgColor)
assert.Equal(t, "black", s.Table().BgColor)
assert.Equal(t, "black", s.GetTable().BgColor)
assert.Equal(t, tcell.ColorWhite, s.FgColor())
assert.Equal(t, tcell.ColorBlack, s.BgColor())
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
assert.Equal(t, tcell.ColorPink, AsColor("blah"))
assert.Equal(t, tcell.ColorWhite, AsColor("white"))
}
func TestSkinNotExits(t *testing.T) {
_, err := NewStyles("test_assets/blee.yml")
assert.NotNil(t, err)
s := config.NewStyles()
assert.NotNil(t, s.Load("test_assets/blee.yml"))
}
func TestSkinBoarked(t *testing.T) {
_, err := NewStyles("test_assets/skin_boarked.yml")
assert.NotNil(t, err)
s := config.NewStyles()
assert.NotNil(t, s.Load("test_assets/skin_boarked.yml"))
}

View File

@ -0,0 +1,41 @@
benchmarks:
defaults:
concurrency: 2
requests: 1000
services:
default/nginx:
concurrency: 2
requests: 1000
http:
method: GET
http2: true
host: 10.10.10.10
path: /
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"
blee/fred:
concurrency: 10
requests: 1500
http:
method: POST
http2: false
host: 20.20.20.20
path: /zorg
body: |-
{"fred": "blee"}
headers:
Accept:
- text/html
Content-Type:
- application/json
auth:
user: "fred"
password: "blee"

View File

@ -0,0 +1,5 @@
hotKey:
pods:
shortCut: shift-0
description: Launch pod view
command: pods

64
internal/dao/alias.go Normal file
View File

@ -0,0 +1,64 @@
package dao
import (
"strings"
"github.com/derailed/k9s/internal/config"
)
// Alias tracks standard and custom command aliases.
type Alias struct {
config.Aliases
factory Factory
}
// NewAlias returns a new set of aliases.
func NewAlias(f Factory) *Alias {
return &Alias{
Aliases: config.NewAliases(),
factory: f,
}
}
// ClearAliases remove all aliases.
func (a *Alias) Clear() {
for k := range a.Alias {
delete(a.Alias, k)
}
}
// Ensure makes sure alias are loaded.
func (a *Alias) Ensure() (config.Alias, error) {
if len(a.Alias) == 0 {
if err := LoadResources(a.factory); err != nil {
return config.Alias{}, err
}
return a.Alias, a.load()
}
return a.Alias, nil
}
func (a *Alias) load() error {
if err := a.Load(); err != nil {
return err
}
for _, gvr := range AllGVRs() {
meta, err := MetaFor(gvr)
if err != nil {
return err
}
if _, ok := a.Alias[meta.Kind]; ok {
continue
}
a.Define(string(gvr), strings.ToLower(meta.Kind), meta.Name)
if meta.SingularName != "" {
a.Define(string(gvr), meta.SingularName)
}
if meta.ShortNames != nil {
a.Define(string(gvr), meta.ShortNames...)
}
}
return nil
}

View File

@ -0,0 +1,43 @@
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3000
name: fred
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3001
name: blee
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3002
name: duh
contexts:
- context:
cluster: fred
user: fred
name: fred
- context:
cluster: blee
user: blee
name: blee
- context:
cluster: duh
user: duh
name: duh
current-context: fred
users:
- name: fred
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: blee
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: duh
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==

View File

@ -0,0 +1,39 @@
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3001
name: blee
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3002
name: duh
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3000
name: fred
contexts:
- context:
cluster: blee
user: blee
name: blee
- context:
cluster: duh
user: duh
name: duh
current-context: fred
kind: Config
preferences: {}
users:
- name: blee
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: duh
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: fred
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==

18
internal/dao/benchmark.go Normal file
View File

@ -0,0 +1,18 @@
package dao
import (
"os"
)
// Benchmark represents a benchmark resource.
type Benchmark struct {
Generic
}
var _ Accessor = &Benchmark{}
var _ Nuker = &Benchmark{}
// Delete a Benchmark.
func (d *Benchmark) Delete(path string, cascade, force bool) error {
return os.Remove(path)
}

47
internal/dao/container.go Normal file
View File

@ -0,0 +1,47 @@
package dao
import (
"context"
"errors"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/watch"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
)
type Container struct {
Generic
}
var _ Accessor = &Container{}
var _ Loggable = &Container{}
// Logs tails a given container logs
func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error {
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok {
return errors.New("Expecting an informer")
}
o, err := fac.Get("v1/pods", opts.Path, labels.Everything())
if err != nil {
return err
}
var po v1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
return err
}
return tailLogs(ctx, c, logChan, opts)
}
// Logs fetch container logs for a given pod and container.
func (c *Container) Logs(path string, opts *v1.PodLogOptions) *restclient.Request {
ns, n := client.Namespaced(path)
return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
}

121
internal/dao/context.go Normal file
View File

@ -0,0 +1,121 @@
package dao
import (
"fmt"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
)
type Context struct {
Generic
}
var _ Accessor = &Context{}
var _ Switchable = &Context{}
func (c *Context) config() *client.Config {
return c.Factory.Client().Config()
}
// Get a Context.
func (c *Context) Get(_, n string) (runtime.Object, error) {
ctx, err := c.config().GetContext(n)
if err != nil {
return nil, err
}
return &render.NamedContext{Name: n, Context: ctx}, nil
}
// List all Contexts on the current cluster.
func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) {
ctxs, err := c.config().Contexts()
if err != nil {
return nil, err
}
cc := make([]runtime.Object, 0, len(ctxs))
for k, v := range ctxs {
cc = append(cc, render.NewNamedContext(c.config(), k, v))
}
return cc, nil
}
// Delete a Context.
func (c *Context) Delete(path string, cascade, force bool) error {
ctx, err := c.config().CurrentContextName()
if err != nil {
return err
}
if ctx == path {
return fmt.Errorf("trying to delete your current context %s", path)
}
return c.config().DelContext(path)
}
// MustCurrentContextName return the active context name.
func (c *Context) MustCurrentContextName() string {
cl, err := c.config().CurrentContextName()
if err != nil {
log.Fatal().Err(err).Msg("Fetching current context")
}
return cl
}
// Switch to another context.
func (c *Context) Switch(ctx string) error {
c.Factory.Client().SwitchContextOrDie(ctx)
return nil
}
// KubeUpdate modifies kubeconfig default context.
func (c *Context) KubeUpdate(n string) error {
config, err := c.config().RawConfig()
if err != nil {
return err
}
if err := c.Switch(n); err != nil {
return err
}
return clientcmd.ModifyConfig(
clientcmd.NewDefaultPathOptions(), config, true,
)
}
// ----------------------------------------------------------------------------
// // NamedContext represents a named cluster context.
// type NamedContext struct {
// Name string
// Context *api.Context
// config *client.Config
// }
// // NewNamedContext returns a new named context.
// func NewNamedContext(c *client.Config, n string, ctx *api.Context) *NamedContext {
// return &NamedContext{Name: n, Context: ctx, config: c}
// }
// // MustCurrentContextName return the active context name.
// func (c *NamedContext) MustCurrentContextName() string {
// cl, err := c.config.CurrentContextName()
// if err != nil {
// log.Fatal().Err(err).Msg("Fetching current context")
// }
// return cl
// }
// // GetObjectKind returns a schema object.
// func (c *NamedContext) GetObjectKind() schema.ObjectKind {
// return nil
// }
// // DeepCopyObject returns a container copy.
// func (c *NamedContext) DeepCopyObject() runtime.Object {
// return c
// }

43
internal/dao/cronjob.go Normal file
View File

@ -0,0 +1,43 @@
package dao
import (
"github.com/derailed/k9s/internal/client"
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
)
const maxJobNameSize = 42
type CronJob struct {
Generic
}
var _ Accessor = &CronJob{}
var _ Runnable = &CronJob{}
// Run a CronJob.
func (c *CronJob) Run(path string) error {
ns, n := client.Namespaced(path)
cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{})
if err != nil {
return err
}
var jobName = cj.Name
if len(cj.Name) >= maxJobNameSize {
jobName = cj.Name[0:maxJobNameSize]
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName + "-manual-" + rand.String(3),
Namespace: ns,
Labels: cj.Spec.JobTemplate.Labels,
},
Spec: cj.Spec.JobTemplate.Spec,
}
_, err = c.Client().DialOrDie().BatchV1().Jobs(ns).Create(job)
return err
}

38
internal/dao/describe.go Normal file
View File

@ -0,0 +1,38 @@
package dao
import (
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
"k8s.io/kubectl/pkg/describe"
"k8s.io/kubectl/pkg/describe/versioned"
)
func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error) {
mapper := RestMapper{Connection: c}
m, err := mapper.ToRESTMapper()
if err != nil {
log.Error().Err(err).Msgf("No REST mapper for resource %s", gvr)
return "", err
}
GVR := client.GVR(gvr)
gvk, err := m.KindFor(GVR.AsGVR())
if err != nil {
log.Error().Err(err).Msgf("No GVK for resource %s", gvr)
return "", err
}
mapping, err := mapper.ResourceFor(GVR.ResName(), gvk.Kind)
if err != nil {
log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n)
return "", err
}
d, err := versioned.Describer(c.Config().Flags(), mapping)
if err != nil {
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
return "", err
}
log.Debug().Msgf("DESCRIBE FOR %q -- %q:%q", gvr, ns, n)
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
}

80
internal/dao/dp.go Normal file
View File

@ -0,0 +1,80 @@
package dao
import (
"context"
"errors"
"fmt"
"github.com/derailed/k9s/internal/client"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
type Deployment struct {
Generic
}
var _ Accessor = &Deployment{}
var _ Loggable = &Deployment{}
var _ Restartable = &Deployment{}
var _ Scalable = &Deployment{}
// Scale a Deployment.
func (d *Deployment) Scale(path string, replicas int32) error {
ns, n := client.Namespaced(path)
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = d.Client().DialOrDie().AppsV1().Deployments(ns).UpdateScale(n, scale)
return err
}
// Restart a Deployment rollout.
func (d *Deployment) Restart(path string) error {
o, err := d.Get(string(d.gvr), path, labels.Everything())
if err != nil {
return err
}
var ds appsv1.Deployment
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = d.Client().DialOrDie().AppsV1().Deployments(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// Logs 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.Get(string(d.gvr), opts.Path, labels.Everything())
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)
}

124
internal/dao/ds.go Normal file
View File

@ -0,0 +1,124 @@
package dao
import (
"context"
"errors"
"fmt"
"strings"
"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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
type DaemonSet struct {
Generic
}
var _ Accessor = &DaemonSet{}
var _ Loggable = &DaemonSet{}
var _ Restartable = &DaemonSet{}
// Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(path string) error {
o, err := d.Get(string(d.gvr), path, labels.Everything())
if err != nil {
return err
}
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// Logs 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.Get("apps/v1/daemonsets", opts.Path, labels.Everything())
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)
}
return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts)
}
func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok {
return errors.New("expecting a context factory")
}
ls, err := metav1.ParseToLabelSelector(toSelector(sel))
if err != nil {
return err
}
lsel, err := metav1.LabelSelectorAsSelector(ls)
if err != nil {
return err
}
ns, _ := client.Namespaced(opts.Path)
oo, err := f.List("v1/pods", ns, lsel)
if err != nil {
return err
}
if len(oo) > 1 {
opts.MultiPods = true
}
po := Pod{}
po.Init(f, "v1/pods")
for _, o := range oo {
var pod v1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
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
}
}
return nil
}
// ----------------------------------------------------------------------------
// Helpers...
func toSelector(m map[string]string) string {
s := make([]string, 0, len(m))
for k, v := range m {
s = append(s, k+"="+v)
}
return strings.Join(s, ",")
}

36
internal/dao/generic.go Normal file
View File

@ -0,0 +1,36 @@
package dao
import (
"github.com/derailed/k9s/internal/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
)
type Generic struct {
Factory
gvr client.GVR
}
func (r *Generic) Init(f Factory, gvr client.GVR) {
r.Factory, r.gvr = f, gvr
}
// Delete a Generic.
func (g *Generic) Delete(path string, cascade, force bool) error {
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
ns, n := client.Namespaced(path)
opts := metav1.DeleteOptions{PropagationPolicy: &p}
if ns != "-" {
return g.dynClient().Namespace(ns).Delete(n, &opts)
}
return g.dynClient().Delete(n, &opts)
}
func (g *Generic) dynClient() dynamic.NamespaceableResourceInterface {
return g.Client().DynDialOrDie().Resource(g.gvr.AsGVR())
}

20
internal/dao/helpers.go Normal file
View File

@ -0,0 +1,20 @@
package dao
import (
"math"
"github.com/derailed/tview"
runewidth "github.com/mattn/go-runewidth"
)
func toPerc(v1, v2 float64) float64 {
if v2 == 0 {
return 0
}
return math.Round((v1 / v2) * 100)
}
// Truncate a string to the given l and suffix ellipsis if needed.
func Truncate(str string, width int) string {
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
}

View File

@ -1,4 +1,4 @@
package k8s
package dao
import (
"testing"
@ -19,18 +19,3 @@ func TestToPerc(t *testing.T) {
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
}
}
func TestToMB(t *testing.T) {
uu := []struct {
v int64
e float64
}{
{0, 0},
{2 * megaByte, 2},
{10 * megaByte, 10},
}
for _, u := range uu {
assert.Equal(t, u.e, ToMB(u.v))
}
}

39
internal/dao/job.go Normal file
View File

@ -0,0 +1,39 @@
package dao
import (
"context"
"errors"
"fmt"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
type Job struct {
Generic
}
var _ Accessor = &Job{}
var _ Loggable = &Job{}
// Logs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := j.Get(string(j.gvr), opts.Path, labels.Everything())
if err != nil {
return err
}
var job batchv1.Job
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
if err != nil {
return errors.New("expecting a job resource")
}
if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on Job %s", opts.Path)
}
return podLogs(ctx, c, job.Spec.Selector.MatchLabels, opts)
}

View File

@ -0,0 +1,65 @@
package dao
import (
"strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color"
)
// LogOptions represent logger options.
type LogOptions struct {
Path string
Container string
Lines int64
Color color.Paint
Previous bool
SingleContainer bool
MultiPods bool
}
// HasContainer checks if a container is present.
func (o LogOptions) HasContainer() bool {
return o.Container != ""
}
// FixedSizeName returns a normalize fixed size pod name if possible.
func (o LogOptions) FixedSizeName() string {
_, n := client.Namespaced(o.Path)
tokens := strings.Split(n, "-")
if len(tokens) < 3 {
return n
}
var s []string
for i := 0; i < len(tokens)-1; i++ {
s = append(s, tokens[i])
}
return Truncate(strings.Join(s, "-"), 15) + "-" + tokens[len(tokens)-1]
}
func colorize(c color.Paint, txt string) string {
if c == 0 {
return ""
}
return color.Colorize(txt, c)
}
// 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
}
if o.MultiPods {
return colorize(o.Color, n+":"+o.Container+" ") + msg
}
if !o.SingleContainer {
return colorize(o.Color, o.Container+" ") + msg
}
return msg
}

196
internal/dao/pod.go Normal file
View File

@ -0,0 +1,196 @@
package dao
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"sync/atomic"
"time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
)
const defaultTimeout = 1 * time.Second
// Pod represents a pod resource.
type Pod struct {
Generic
}
var _ Accessor = &Pod{}
var _ Loggable = &Pod{}
// Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request {
ns, n := client.Namespaced(path)
return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
}
// Containers returns all container names on pod
func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
o, err := p.Get("v1/pod", path, 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
}
cc := []string{}
for _, c := range pod.Spec.Containers {
cc = append(cc, c.Name)
}
if includeInit {
for _, c := range pod.Spec.InitContainers {
cc = append(cc, c.Name)
}
}
return cc, nil
}
// Logs tails a given container logs
func (p *Pod) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
if !opts.HasContainer() {
return p.logs(ctx, c, opts)
}
return tailLogs(ctx, p, c, opts)
}
// PodLogs tail logs for all containers in a running Pod.
func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error {
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok {
return errors.New("Expecting an informer")
}
o, err := fac.Get("v1/pods", opts.Path, labels.Everything())
if err != nil {
return err
}
var po v1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
return err
}
opts.Color = asColor(po.Name)
if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 {
opts.SingleContainer = true
}
for _, co := range po.Spec.InitContainers {
opts.Container = co.Name
if err := p.TailLogs(ctx, c, opts); err != nil {
return err
}
}
rcos := loggableContainers(po.Status)
for _, co := range po.Spec.Containers {
if in(rcos, co.Name) {
opts.Container = co.Name
if err := p.TailLogs(ctx, c, opts); err != nil {
log.Error().Err(err).Msgf("Getting logs for %s failed", co.Name)
return err
}
}
}
return nil
}
func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container)
o := v1.PodLogOptions{
Container: opts.Container,
Follow: true,
TailLines: &opts.Lines,
Previous: opts.Previous,
}
req := logger.Logs(opts.Path, &o)
ctxt, cancelFunc := context.WithCancel(ctx)
req.Context(ctxt)
var blocked int32 = 1
go logsTimeout(cancelFunc, &blocked)
// This call will block if nothing is in the stream!!
stream, err := req.Stream()
atomic.StoreInt32(&blocked, 0)
if err != nil {
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)
return nil
}
func logsTimeout(cancel context.CancelFunc, blocked *int32) {
<-time.After(defaultTimeout)
if atomic.LoadInt32(blocked) == 1 {
log.Debug().Msg("Timed out reading the log stream")
cancel()
}
}
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) {
defer func() {
log.Debug().Msgf(">>> Closing stream `%s", opts.Path)
if err := stream.Close(); err != nil {
log.Error().Err(err).Msg("Cloing stream")
}
}()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
select {
case <-ctx.Done():
return
default:
c <- opts.DecorateLog(scanner.Text())
}
}
}
// ----------------------------------------------------------------------------
// Helpers...
func loggableContainers(s v1.PodStatus) []string {
var rcos []string
for _, c := range s.ContainerStatuses {
rcos = append(rcos, c.Name)
}
return rcos
}
func asColor(n string) color.Paint {
var sum int
for _, r := range n {
sum += int(r)
}
return color.Paint(30 + 2 + sum%6)
}
// Check if string is in a string list.
func in(ll []string, s string) bool {
for _, l := range ll {
if l == s {
return true
}
}
return false
}

View File

@ -1,14 +1,14 @@
package k8s
package dao
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
@ -19,18 +19,16 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubectl/pkg/util"
)
const localhost = "localhost"
// PortForward tracks a port forward stream.
type PortForward struct {
Connection
// PortForwarder tracks a port forward stream.
type PortForwarder struct {
client.Connection
genericclioptions.IOStreams
stopChan, readyChan chan struct{}
logger *zerolog.Logger
active bool
path string
container string
@ -38,63 +36,62 @@ type PortForward struct {
age time.Time
}
// NewPortForward returns a new port forward streamer.
func NewPortForward(c Connection, l *zerolog.Logger) *PortForward {
return &PortForward{
// NewPortForwarder returns a new port forward streamer.
func NewPortForwarder(c client.Connection) *PortForwarder {
return &PortForwarder{
Connection: c,
logger: l,
stopChan: make(chan struct{}),
readyChan: make(chan struct{}),
}
}
// Age returns the port forward age.
func (p *PortForward) Age() string {
func (p *PortForwarder) Age() string {
return time.Since(p.age).String()
}
// Active returns the forward status.
func (p *PortForward) Active() bool {
func (p *PortForwarder) Active() bool {
return p.active
}
// SetActive mark a portforward as active.
func (p *PortForward) SetActive(b bool) {
func (p *PortForwarder) SetActive(b bool) {
p.active = b
}
// Ports returns the forwarded ports mappings.
func (p *PortForward) Ports() []string {
func (p *PortForwarder) Ports() []string {
return p.ports
}
// Path returns the pod resource path.
func (p *PortForward) Path() string {
return p.path
func (p *PortForwarder) Path() string {
return p.path + ":" + p.container
}
// Container returns the targetes container.
func (p *PortForward) Container() string {
func (p *PortForwarder) Container() string {
return p.container
}
// Stop terminates a port forard
func (p *PortForward) Stop() {
p.logger.Debug().Msgf("<<< Stopping port forward %q %v", p.path, p.ports)
func (p *PortForwarder) Stop() {
log.Debug().Msgf("<<< Stopping PortForward %q %v", p.path, p.ports)
p.active = false
close(p.stopChan)
}
// FQN returns the portforward unique id.
func (p *PortForward) FQN() string {
func (p *PortForwarder) FQN() string {
return p.path + ":" + p.container
}
// Start initiates a port forward session for a given pod and ports.
func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortForwarder, error) {
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()
ns, n := namespaced(path)
ns, n := client.Namespaced(path)
pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
if err != nil {
return nil, err
@ -110,7 +107,7 @@ func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortF
rcfg.NegotiatedSerializer = codec.WithoutConversion()
clt, err := rest.RESTClientFor(rcfg)
if err != nil {
p.logger.Debug().Msgf("Boom! %#v", err)
log.Debug().Msgf("Boom! %#v", err)
return nil, err
}
req := clt.Post().
@ -119,10 +116,10 @@ func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortF
Name(n).
SubResource("portforward")
return p.forwardPorts("POST", req.URL(), ports)
return p.forwardPorts("POST", req.URL(), address, ports)
}
func (p *PortForward) forwardPorts(method string, url *url.URL, ports []string) (*portforward.PortForwarder, error) {
func (p *PortForwarder) forwardPorts(method string, url *url.URL, address string, ports []string) (*portforward.PortForwarder, error) {
cfg, err := p.Config().RESTConfig()
if err != nil {
return nil, err
@ -133,7 +130,10 @@ func (p *PortForward) forwardPorts(method string, url *url.URL, ports []string)
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
addrs := []string{localhost}
if address == "" {
address = localhost
}
addrs := strings.Split(address, ",")
return portforward.NewOnAddresses(dialer, addrs, ports, p.stopChan, p.readyChan, p.Out, p.ErrOut)
}
@ -149,40 +149,3 @@ func codec() (serializer.CodecFactory, runtime.ParameterCodec) {
return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme)
}
func svcPortToTargetPort(ports []string, svc v1.Service, pod v1.Pod) ([]string, error) {
var translated []string
for _, port := range ports {
localPort, remotePort := splitPort(port)
portnum, err := strconv.Atoi(remotePort)
if err != nil {
svcPort, err := util.LookupServicePortNumberByName(svc, remotePort)
if err != nil {
return nil, err
}
portnum = int(svcPort)
if localPort == remotePort {
localPort = strconv.Itoa(portnum)
}
}
containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum))
if err != nil {
return nil, err
}
if int32(portnum) != containerPort {
port = fmt.Sprintf("%s:%d", localPort, containerPort)
}
translated = append(translated, port)
}
return translated, nil
}
func splitPort(port string) (local, remote string) {
parts := strings.Split(port, ":")
if len(parts) == 2 {
return parts[0], parts[1]
}
return parts[0], parts[0]
}

View File

@ -0,0 +1,14 @@
package dao
type PortForward struct {
Generic
}
var _ Accessor = &PortForward{}
var _ Nuker = &PortForward{}
// Delete a portforward.
func (p *PortForward) Delete(path string, cascade, force bool) error {
p.Factory.DeleteForwarder(path)
return nil
}

287
internal/dao/registry.go Normal file
View File

@ -0,0 +1,287 @@
package dao
import (
"fmt"
"sort"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
// MetaViewers represents a collection of meta viewers.
type ResourceMetas map[client.GVR]metav1.APIResource
// Accessors represents a collection of dao accessors.
type Accessors map[client.GVR]Accessor
var resMetas = ResourceMetas{}
// AccessorFor returns a client accessor for a resource if registered.
// Otherwise it returns a generic accessor.
// Customize here for non resource types or types with metrics or logs.
func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
m := Accessors{
"contexts": &Context{},
"containers": &Container{},
"screendumps": &ScreenDump{},
"benchmarks": &Benchmark{},
"portforwards": &PortForward{},
"v1/services": &Service{},
"v1/pods": &Pod{},
"apps/v1/deployments": &Deployment{},
"apps/v1/daemonsets": &DaemonSet{},
"extensions/v1beta1/daemonsets": &DaemonSet{},
"apps/v1/statefulsets": &StatefulSet{},
"batch/v1beta1/cronjobs": &CronJob{},
"batch/v1/jobs": &Job{},
}
r, ok := m[gvr]
if !ok {
r = &Generic{}
log.Warn().Msgf("No DAO registry entry for %q. Using factory!", gvr)
}
r.Init(f, gvr)
return r, nil
}
// RegisterMeta registers a new resource meta object.
func RegisterMeta(gvr string, res metav1.APIResource) {
resMetas[client.GVR(gvr)] = res
}
func AllGVRs() client.GVRs {
kk := make(client.GVRs, 0, len(resMetas))
for k := range resMetas {
kk = append(kk, k)
}
sort.Sort(kk)
return kk
}
// MetaFor returns a resource metadata for a given gvr.
func MetaFor(gvr client.GVR) (metav1.APIResource, error) {
m, ok := resMetas[gvr]
if !ok {
return metav1.APIResource{}, fmt.Errorf("no resource meta defined for %q", gvr)
}
return m, nil
}
// IsK9sMeta checks for non resource meta.
func IsK9sMeta(m metav1.APIResource) bool {
for _, c := range m.Categories {
if c == "k9s" {
return true
}
}
return false
}
// Load hydrates server preferred+CRDs resource metadata.
func LoadResources(f Factory) error {
resMetas = make(ResourceMetas, 100)
if err := loadPreferred(f, resMetas); err != nil {
return err
}
loadNonResource(resMetas)
return loadCRDs(f, resMetas)
}
// BOZO!! Need contermeasure for direct commands!
func loadNonResource(m ResourceMetas) {
m["aliases"] = metav1.APIResource{
Name: "aliases",
Kind: "Aliases",
Categories: []string{"k9s"},
}
m["contexts"] = metav1.APIResource{
Name: "contexts",
Kind: "Contexts",
ShortNames: []string{"ctx"},
Categories: []string{"k9s"},
}
m["screendumps"] = metav1.APIResource{
Name: "screendumps",
Kind: "ScreenDumps",
ShortNames: []string{"sd"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["benchmarks"] = metav1.APIResource{
Name: "benchmarks",
Kind: "Benchmarks",
ShortNames: []string{"be"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["portforwards"] = metav1.APIResource{
Name: "portforwards",
Namespaced: true,
Kind: "PortForwards",
ShortNames: []string{"pf"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["containers"] = metav1.APIResource{
Name: "containers",
Kind: "Containers",
Categories: []string{"k9s"},
}
loadRBAC(m)
}
func loadRBAC(m ResourceMetas) {
m["rbac"] = metav1.APIResource{
Name: "rbacs",
Kind: "Rules",
Categories: []string{"k9s"},
}
m["policy"] = metav1.APIResource{
Name: "policies",
Kind: "Rules",
Namespaced: true,
Categories: []string{"k9s"},
}
m["users"] = metav1.APIResource{
Name: "users",
Kind: "User",
Categories: []string{"k9s"},
}
m["groups"] = metav1.APIResource{
Name: "groups",
Kind: "Group",
Categories: []string{"k9s"},
}
}
func loadPreferred(f Factory, m ResourceMetas) error {
discovery, err := f.Client().CachedDiscovery()
if err != nil {
return err
}
rr, err := discovery.ServerPreferredResources()
if err != nil {
return err
}
for _, r := range rr {
for _, res := range r.APIResources {
gvr := client.FromGVAndR(r.GroupVersion, res.Name)
log.Debug().Msgf("GVR %s", gvr)
res.Group, res.Version = gvr.ToG(), gvr.ToV()
m[gvr] = res
}
}
return nil
}
func loadCRDs(f Factory, m ResourceMetas) error {
oo, err := f.List("apiextensions.k8s.io/v1beta1/customresourcedefinitions", "", labels.Everything())
if err != nil {
log.Error().Err(err).Msgf("Fail CRDs load")
return nil
}
f.WaitForCacheSync()
for _, o := range oo {
meta, errs := extractMeta(o)
if len(errs) > 0 {
log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs))
continue
}
gvr := client.NewGVR(meta.Group, meta.Version, meta.Name)
m[gvr] = meta
}
return nil
}
func extractMeta(o runtime.Object) (metav1.APIResource, []error) {
var (
m metav1.APIResource
errs []error
)
crd, ok := o.(*unstructured.Unstructured)
if !ok {
return m, append(errs, fmt.Errorf("Expected CustomResourceDefinition, but got %T", o))
}
var spec map[string]interface{}
spec, errs = extractMap(crd.Object, "spec", errs)
var meta map[string]interface{}
meta, errs = extractMap(crd.Object, "metadata", errs)
m.Name, errs = extractStr(meta, "name", errs)
m.Group, errs = extractStr(spec, "group", errs)
m.Version, errs = extractStr(spec, "version", errs)
var scope string
scope, errs = extractStr(spec, "scope", errs)
m.Namespaced = isNamespaced(scope)
var names map[string]interface{}
names, errs = extractMap(spec, "names", errs)
m.Kind, errs = extractStr(names, "kind", errs)
m.SingularName, errs = extractStr(names, "singular", errs)
m.Name, errs = extractStr(names, "plural", errs)
m.ShortNames, errs = extractSlice(names, "shortNames", errs)
return m, errs
}
func isNamespaced(scope string) bool {
return scope == "Namespaced"
}
func extractSlice(m map[string]interface{}, n string, errs []error) ([]string, []error) {
if m[n] == nil {
return nil, errs
}
s, ok := m[n].([]string)
if ok {
return s, errs
}
ii, ok := m[n].([]interface{})
if !ok {
return s, append(errs, fmt.Errorf("failed to extract slice %s -- %#v", n, m))
}
ss := make([]string, len(ii))
for i, name := range ii {
ss[i], ok = name.(string)
if !ok {
return s, append(errs, fmt.Errorf("expecting string shortnames"))
}
}
return s, errs
}
func extractStr(m map[string]interface{}, n string, errs []error) (string, []error) {
s, ok := m[n].(string)
if !ok {
return s, append(errs, fmt.Errorf("failed to extract string %s", n))
}
return s, errs
}
func extractMap(m map[string]interface{}, n string, errs []error) (map[string]interface{}, []error) {
v, ok := m[n].(map[string]interface{})
if !ok {
return v, append(errs, fmt.Errorf("failed to extract field %s", n))
}
return v, errs
}

View File

@ -1,26 +1,21 @@
package k8s
package dao
import (
"fmt"
"os/user"
"regexp"
"strings"
"github.com/rs/zerolog/log"
"github.com/derailed/k9s/internal/client"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/restmapper"
)
var (
// RestMapping holds k8s resource mapping
RestMapping = &RestMapper{}
toFileName = regexp.MustCompile(`[^(\w/\.)]`)
)
// RestMapping holds k8s resource mapping
var RestMapping = &RestMapper{}
// RestMapper map resource to REST mapping ie kind, group, version.
type RestMapper struct {
Connection
client.Connection
}
// ToRESTMapper map resources to kind, and map kind and version to interfaces for manipulating K8s objects.
@ -34,19 +29,6 @@ func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) {
return expander, nil
}
func toHostDir(host string) string {
h := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
return toFileName.ReplaceAllString(h, "_")
}
func mustHomeDir() string {
usr, err := user.Current()
if err != nil {
panic(err)
}
return usr.HomeDir
}
// ResourceFor produces a rest mapping from a given resource.
// Support full res name ie deployment.v1.apps.
func (r *RestMapper) ResourceFor(resourceArg, kind string) (*meta.RESTMapping, error) {
@ -73,7 +55,6 @@ func (r *RestMapper) resourceFor(resourceArg string) (schema.GroupVersionResourc
}
fullGVR, gr := schema.ParseResourceArg(strings.ToLower(resourceArg))
log.Debug().Msgf("GVR %#v -- %#v", fullGVR, gr)
if fullGVR != nil {
return mapper.ResourceFor(*fullGVR)
}

View File

@ -0,0 +1,17 @@
package dao
import (
"os"
)
type ScreenDump struct {
Generic
}
var _ Accessor = &ScreenDump{}
var _ Nuker = &ScreenDump{}
// Delete a ScreenDump.
func (d *ScreenDump) Delete(path string, cascade, force bool) error {
return os.Remove(path)
}

80
internal/dao/sts.go Normal file
View File

@ -0,0 +1,80 @@
package dao
import (
"context"
"errors"
"fmt"
"github.com/derailed/k9s/internal/client"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
type StatefulSet struct {
Generic
}
var _ Accessor = &StatefulSet{}
var _ Loggable = &StatefulSet{}
var _ Restartable = &StatefulSet{}
var _ Scalable = &StatefulSet{}
// Scale a StatefulSet.
func (s *StatefulSet) Scale(path string, replicas int32) error {
ns, n := client.Namespaced(path)
scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(n, scale)
return err
}
// Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(path string) error {
o, err := s.Get(string(s.gvr), path, labels.Everything())
if err != nil {
return err
}
var ds appsv1.StatefulSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// Logs 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.Get(string(s.gvr), opts.Path, labels.Everything())
if err != nil {
return err
}
var dp appsv1.StatefulSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
if err != nil {
return errors.New("expecting StatefulSet resource")
}
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path)
}
return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts)
}

38
internal/dao/svc.go Normal file
View File

@ -0,0 +1,38 @@
package dao
import (
"context"
"errors"
"fmt"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
type Service struct {
Generic
}
var _ Accessor = &Service{}
var _ Loggable = &Service{}
// Logs 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.Get(string(s.gvr), opts.Path, labels.Everything())
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)
}

83
internal/dao/types.go Normal file
View File

@ -0,0 +1,83 @@
package dao
import (
"context"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/watch"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
restclient "k8s.io/client-go/rest"
)
type Factory interface {
// Client retrieves an api client.
Client() client.Connection
// Get fetch a given resource.
Get(gvr, path string, sel labels.Selector) (runtime.Object, error)
// List fetch a collection of resources.
List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error)
// ForResource fetch an informer for a given resource.
ForResource(ns, gvr string) informers.GenericInformer
// WaitForCacheSync synchronize the cache.
WaitForCacheSync()
// DeleteForwarder deletes a pod forwarder.
DeleteForwarder(path string)
// Forwards returns all portforwards.
Forwarders() watch.Forwarders
}
// Accessor represents an accessible k8s resource.
type Accessor interface {
Nuker
// Init the resource with a factory object.
Init(Factory, client.GVR)
}
// Loggable represents resources with logs.
type Loggable interface {
// TaiLogs streams resource logs.
TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error
}
type Scalable interface {
Scale(path string, replicas int32) error
}
// Nuker represents a resource deleter.
type Nuker interface {
// Delete removes a resource from the api server.
Delete(path string, cascade, force bool) error
}
// Switchable represents a switchable resource.
type Switchable interface {
// Switch changes the active context.
Switch(ctx string) error
}
// Restartable represents a restartable resource.
type Restartable interface {
// Restart performs a rollout restart.
Restart(path string) error
}
// Runnable represents a runnable resource.
type Runnable interface {
// Run triggers a run.
Run(path string) error
}
// Loggers represents a resource that exposes logs.
type Logger interface {
Logs(path string, opts *v1.PodLogOptions) *restclient.Request
}

View File

@ -1,8 +0,0 @@
package k8s
type base struct {
}
func (b *base) Kill(ns, n string) error {
return nil
}

View File

@ -1,63 +0,0 @@
package k8s
import (
"github.com/rs/zerolog"
v1 "k8s.io/api/core/v1"
)
// Cluster represents a Kubernetes cluster.
type Cluster struct {
Connection
logger *zerolog.Logger
}
// NewCluster instantiates a new cluster.
func NewCluster(c Connection, l *zerolog.Logger) *Cluster {
return &Cluster{c, l}
}
// Version returns the current cluster git version.
func (c *Cluster) Version() (string, error) {
rev, err := c.ServerVersion()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "", err
}
return rev.GitVersion, nil
}
// ContextName returns the currently active context.
func (c *Cluster) ContextName() string {
ctx, err := c.Config().CurrentContextName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
}
return ctx
}
// ClusterName return the currently active cluster name.
func (c *Cluster) ClusterName() string {
ctx, err := c.Config().CurrentClusterName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
}
return ctx
}
// UserName returns the currently active user.
func (c *Cluster) UserName() string {
usr, err := c.Config().CurrentUserName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
}
return usr
}
// GetNodes get all available nodes in the cluster.
func (c *Cluster) GetNodes() (*v1.NodeList, error) {
return c.FetchNodes()
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClusterRole represents a Kubernetes ClusterRole
type ClusterRole struct {
*base
Connection
}
// NewClusterRole returns a new ClusterRole.
func NewClusterRole(c Connection) *ClusterRole {
return &ClusterRole{&base{}, c}
}
// Get a cluster role.
func (c *ClusterRole) Get(_, n string) (interface{}, error) {
return c.DialOrDie().RbacV1().ClusterRoles().Get(n, metav1.GetOptions{})
}
// List all ClusterRoles on a cluster.
func (c *ClusterRole) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := c.DialOrDie().RbacV1().ClusterRoles().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a ClusterRole.
func (c *ClusterRole) Delete(_, n string, cascade, force bool) error {
return c.DialOrDie().RbacV1().ClusterRoles().Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClusterRoleBinding represents a Kubernetes ClusterRoleBinding
type ClusterRoleBinding struct {
*base
Connection
}
// NewClusterRoleBinding returns a new ClusterRoleBinding.
func NewClusterRoleBinding(c Connection) *ClusterRoleBinding {
return &ClusterRoleBinding{&base{}, c}
}
// Get a service.
func (c *ClusterRoleBinding) Get(_, n string) (interface{}, error) {
return c.DialOrDie().RbacV1().ClusterRoleBindings().Get(n, metav1.GetOptions{})
}
// List all ClusterRoleBindings on a cluster.
func (c *ClusterRoleBinding) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := c.DialOrDie().RbacV1().ClusterRoleBindings().List(opts)
if err != nil {
return Collection{}, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a ClusterRoleBinding.
func (c *ClusterRoleBinding) Delete(_, n string, cascade, force bool) error {
return c.DialOrDie().RbacV1().ClusterRoleBindings().Delete(n, nil)
}

View File

@ -1,106 +0,0 @@
package k8s
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
// NamedContext represents a named cluster context.
type NamedContext struct {
Name string
Context *api.Context
config *Config
}
// NewNamedContext returns a new named context.
func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext {
return &NamedContext{Name: n, Context: ctx, config: c}
}
// MustCurrentContextName return the active context name.
func (c *NamedContext) MustCurrentContextName() string {
cl, err := c.config.CurrentContextName()
if err != nil {
panic(err)
}
return cl
}
// ----------------------------------------------------------------------------
// Context represents a Kubernetes Context.
type Context struct {
*base
Connection
}
// NewContext returns a new Context.
func NewContext(c Connection) *Context {
return &Context{&base{}, c}
}
// Get a Context.
func (c *Context) Get(_, n string) (interface{}, error) {
ctx, err := c.Config().GetContext(n)
if err != nil {
return nil, err
}
return &NamedContext{Name: n, Context: ctx}, nil
}
// List all Contexts on the current cluster.
func (c *Context) List(string, metav1.ListOptions) (Collection, error) {
ctxs, err := c.Config().Contexts()
if err != nil {
return nil, err
}
cc := make([]interface{}, 0, len(ctxs))
for k, v := range ctxs {
cc = append(cc, NewNamedContext(c.Config(), k, v))
}
return cc, nil
}
// Delete a Context.
func (c *Context) Delete(_, n string, cascade, force bool) error {
ctx, err := c.Config().CurrentContextName()
if err != nil {
return err
}
if ctx == n {
return fmt.Errorf("trying to delete your current context %s", n)
}
return c.Config().DelContext(n)
}
// MustCurrentContextName return the active context name.
func (c *Context) MustCurrentContextName() string {
cl, err := c.Config().CurrentContextName()
if err != nil {
panic(err)
}
return cl
}
// Switch to another context.
func (c *Context) Switch(ctx string) error {
c.SwitchContextOrDie(ctx)
return nil
}
// KubeUpdate modifies kubeconfig default context.
func (c *Context) KubeUpdate(n string) error {
config, err := c.Config().RawConfig()
if err != nil {
return err
}
c.Switch(n)
return clientcmd.ModifyConfig(
clientcmd.NewDefaultPathOptions(), config, true,
)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CustomResourceDefinition represents a Kubernetes CustomResourceDefinition
type CustomResourceDefinition struct {
*base
Connection
}
// NewCustomResourceDefinition returns a new CustomResourceDefinition.
func NewCustomResourceDefinition(c Connection) *CustomResourceDefinition {
return &CustomResourceDefinition{&base{}, c}
}
// Get a CustomResourceDefinition.
func (c *CustomResourceDefinition) Get(_, n string) (interface{}, error) {
return c.NSDialOrDie().Get(n, metav1.GetOptions{})
}
// List all CustomResourceDefinitions in a given namespace.
func (c *CustomResourceDefinition) List(_ string, opts metav1.ListOptions) (Collection, error) {
rr, err := c.NSDialOrDie().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a CustomResourceDefinition.
func (c *CustomResourceDefinition) Delete(_, n string, cascade, force bool) error {
return c.NSDialOrDie().Delete(n, nil)
}

View File

@ -1,71 +0,0 @@
package k8s
import (
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
)
const maxJobNameSize = 42
// CronJob represents a Kubernetes CronJob.
type CronJob struct {
*base
Connection
}
// NewCronJob returns a new CronJob.
func NewCronJob(c Connection) *CronJob {
return &CronJob{&base{}, c}
}
// Get a CronJob.
func (c *CronJob) Get(ns, n string) (interface{}, error) {
return c.DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{})
}
// List all CronJobs in a given namespace.
func (c *CronJob) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := c.DialOrDie().BatchV1beta1().CronJobs(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a CronJob.
func (c *CronJob) Delete(ns, n string, cascade, force bool) error {
return c.DialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, nil)
}
// Run the job associated with this cronjob.
func (c *CronJob) Run(ns, n string) error {
cj, err := c.Get(ns, n)
if err != nil {
return err
}
cronJob := cj.(*batchv1beta1.CronJob)
var jobName = cronJob.Name
if len(cronJob.Name) >= maxJobNameSize {
jobName = cronJob.Name[0:maxJobNameSize]
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName + "-manual-" + rand.String(3),
Namespace: ns,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
_, err = c.DialOrDie().BatchV1().Jobs(ns).Create(job)
return err
}

View File

@ -1,70 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
// Deployment represents a Kubernetes Deployment.
type Deployment struct {
*base
Connection
}
// NewDeployment returns a new Deployment.
func NewDeployment(c Connection) *Deployment {
return &Deployment{&base{}, c}
}
// Get a deployment.
func (d *Deployment) Get(ns, n string) (interface{}, error) {
return d.DialOrDie().AppsV1().Deployments(ns).Get(n, metav1.GetOptions{})
}
// List all Deployments in a given namespace.
func (d *Deployment) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := d.DialOrDie().AppsV1().Deployments(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Deployment.
func (d *Deployment) Delete(ns, n string, cascade, force bool) error {
return d.DialOrDie().AppsV1().Deployments(ns).Delete(n, nil)
}
// Scale a Deployment.
func (d *Deployment) Scale(ns, n string, replicas int32) error {
scale, err := d.DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = d.DialOrDie().AppsV1().Deployments(ns).UpdateScale(n, scale)
return err
}
// Restart a Deployment rollout.
func (d *Deployment) Restart(ns, n string) error {
dp, err := d.DialOrDie().AppsV1().Deployments(ns).Get(n, metav1.GetOptions{})
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(dp)
if err != nil {
return err
}
_, err = d.DialOrDie().AppsV1().Deployments(ns).Patch(dp.Name, types.StrategicMergePatchType, update)
return err
}

View File

@ -1,64 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
// DaemonSet represents a Kubernetes DaemonSet
type DaemonSet struct {
*base
Connection
}
// NewDaemonSet returns a new DaemonSet.
func NewDaemonSet(c Connection) *DaemonSet {
return &DaemonSet{&base{}, c}
}
// Get a DaemonSet.
func (d *DaemonSet) Get(ns, n string) (interface{}, error) {
return d.DialOrDie().AppsV1().DaemonSets(ns).Get(n, metav1.GetOptions{})
}
// List all DaemonSets in a given namespace.
func (d *DaemonSet) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := d.DialOrDie().AppsV1().DaemonSets(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a DaemonSet.
func (d *DaemonSet) Delete(ns, n string, cascade, force bool) error {
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
return d.DialOrDie().AppsV1().DaemonSets(ns).Delete(n, &metav1.DeleteOptions{
PropagationPolicy: &p,
})
}
// Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(ns, n string) error {
ds, err := d.DialOrDie().AppsV1().DaemonSets(ns).Get(n, metav1.GetOptions{})
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(ds)
if err != nil {
return err
}
_, err = d.DialOrDie().AppsV1().DaemonSets(ns).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Endpoints represents a Kubernetes Endpoints.
type Endpoints struct {
*base
Connection
}
// NewEndpoints returns a new Endpoints.
func NewEndpoints(c Connection) *Endpoints {
return &Endpoints{&base{}, c}
}
// Get a Endpoint.
func (e *Endpoints) Get(ns, n string) (interface{}, error) {
return e.DialOrDie().CoreV1().Endpoints(ns).Get(n, metav1.GetOptions{})
}
// List all Endpoints in a given namespace.
func (e *Endpoints) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := e.DialOrDie().CoreV1().Endpoints(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Endpoint.
func (e *Endpoints) Delete(ns, n string, cascade, force bool) error {
return e.DialOrDie().CoreV1().Endpoints(ns).Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Event represents a Kubernetes Event.
type Event struct {
*base
Connection
}
// NewEvent returns a new Event.
func NewEvent(c Connection) *Event {
return &Event{&base{}, c}
}
// Get a Event.
func (e *Event) Get(ns, n string) (interface{}, error) {
return e.DialOrDie().CoreV1().Events(ns).Get(n, metav1.GetOptions{})
}
// List all Events in a given namespace.
func (e *Event) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := e.DialOrDie().CoreV1().Events(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete an Event.
func (e *Event) Delete(ns, n string, cascade, force bool) error {
return e.DialOrDie().CoreV1().Events(ns).Delete(n, nil)
}

View File

@ -1,74 +0,0 @@
package k8s
import (
"path"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GVR represents a fully qualified kubernetes resource.
type GVR string
// NewGVR returns a new gvr.
func NewGVR(g, v, r string) GVR {
return GVR(path.Join(g, v, r))
}
// ToGVR returns a new gvr from a string.
func ToGVR(gv, r string) GVR {
return GVR(path.Join(gv, r))
}
// ResName returns a full res name ie dp.v1.apps.
func (g GVR) ResName() string {
return g.ToR() + "." + g.ToV() + "." + g.ToG()
}
// AsGR returns the group version.
func (g GVR) AsGR() schema.GroupVersion {
return schema.GroupVersion{
Group: g.ToG(),
Version: g.ToV(),
}
}
// AsGVR returns a schema gvr instance.
func (g GVR) AsGVR() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: g.ToG(),
Version: g.ToV(),
Resource: g.ToR(),
}
}
// String returns a GVR as a string.
func (g GVR) String() string {
return string(g)
}
// ToV returns the resource version.
func (g GVR) ToV() string {
tokens := strings.Split(string(g), "/")
if len(tokens) < 2 {
return ""
}
return tokens[len(tokens)-2]
}
// ToR returns the resource name.
func (g GVR) ToR() string {
tokens := strings.Split(string(g), "/")
return tokens[len(tokens)-1]
}
// ToG returns the resource group name.
func (g GVR) ToG() string {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[0]
default:
return ""
}
}

View File

@ -1,146 +0,0 @@
package k8s_test
import (
"testing"
"github.com/derailed/k9s/internal/k8s"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestAsGR(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersion
}{
"full": {"apps/v1/deployments", schema.GroupVersion{"apps", "v1"}},
"core": {"v1/pods", schema.GroupVersion{"", "v1"}},
"bork": {"users", schema.GroupVersion{"", ""}},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).AsGR())
})
}
}
func TestNewGVR(t *testing.T) {
uu := map[string]struct {
g, v, r string
e string
}{
"full": {"apps", "v1", "deployments", "apps/v1/deployments"},
"core": {"", "v1", "pods", "v1/pods"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.NewGVR(u.g, u.v, u.r).String())
})
}
}
func TestToGVR(t *testing.T) {
uu := map[string]struct {
gv, r, e string
}{
"full": {"apps/v1", "deployments", "apps/v1/deployments"},
"core": {"v1", "pods", "v1/pods"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.ToGVR(u.gv, u.r).String())
})
}
}
func TestResName(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments.v1.apps"},
"core": {"v1/pods", "pods.v1."},
"k9s": {"users", "users.."},
"empty": {"", ".."},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ResName())
})
}
}
func TestToR(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments"},
"core": {"v1/pods", "pods"},
"k9s": {"users", "users"},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToR())
})
}
}
func TestToG(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "apps"},
"core": {"v1/pods", ""},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToG())
})
}
}
func TestToV(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "v1"},
"core": {"v1beta1/pods", "v1beta1"},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToV())
})
}
}
func TestToStringer(t *testing.T) {
uu := map[string]struct {
gvr string
}{
"full": {"apps/v1/deployments"},
"core": {"v1beta1/pods"},
"k9s": {"users"},
"empty": {""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, k8s.GVR(u.gvr).String())
})
}
}

View File

@ -1,27 +0,0 @@
package k8s
import (
"math"
"path"
"strings"
)
const megaByte = 1024 * 1024
// ToMB converts bytes to megabytes.
func ToMB(v int64) float64 {
return float64(v) / megaByte
}
func toPerc(v1, v2 float64) float64 {
if v2 == 0 {
return 0
}
return math.Round((v1 / v2) * 100)
}
func namespaced(n string) (string, string) {
ns, po := path.Split(n)
return strings.Trim(ns, "/"), po
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HorizontalPodAutoscalerV1 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV1 struct {
*base
Connection
}
// NewHorizontalPodAutoscalerV1 returns a new HorizontalPodAutoscaler.
func NewHorizontalPodAutoscalerV1(c Connection) *HorizontalPodAutoscalerV1 {
return &HorizontalPodAutoscalerV1{&base{}, c}
}
// Get a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV1) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HorizontalPodAutoscalers in a given namespace.
func (h *HorizontalPodAutoscalerV1) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV1) Delete(ns, n string, cascade, force bool) error {
return h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -1,41 +0,0 @@
package k8s
import (
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HorizontalPodAutoscalerV2Beta1 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV2Beta1 struct {
*base
Connection
}
// NewHorizontalPodAutoscalerV2Beta1 returns a new HorizontalPodAutoscaler.
func NewHorizontalPodAutoscalerV2Beta1(c Connection) *HorizontalPodAutoscalerV2Beta1 {
return &HorizontalPodAutoscalerV2Beta1{&base{}, c}
}
// Get a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV2Beta1) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV2beta1().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HorizontalPodAutoscalers in a given namespace.
func (h *HorizontalPodAutoscalerV2Beta1) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV2beta1().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
log.Error().Err(err).Msg("Beta1 Failed!")
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV2Beta1) Delete(ns, n string, cascade, force bool) error {
return h.DialOrDie().AutoscalingV2beta1().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -1,41 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var supportedAutoScalingAPIVersions = []string{"v2beta2", "v2beta1", "v1"}
// HorizontalPodAutoscalerV2Beta2 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV2Beta2 struct {
*base
Connection
}
// NewHorizontalPodAutoscalerV2Beta2 returns a new HorizontalPodAutoscalerV2Beta2.
func NewHorizontalPodAutoscalerV2Beta2(c Connection) *HorizontalPodAutoscalerV2Beta2 {
return &HorizontalPodAutoscalerV2Beta2{&base{}, c}
}
// Get a HorizontalPodAutoscalerV2Beta2.
func (h *HorizontalPodAutoscalerV2Beta2) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HorizontalPodAutoscalerV2Beta2s in a given namespace.
func (h *HorizontalPodAutoscalerV2Beta2) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a HorizontalPodAutoscalerV2Beta2.
func (h *HorizontalPodAutoscalerV2Beta2) Delete(ns, n string, cascade, force bool) error {
return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Ingress represents a Kubernetes Ingress.
type Ingress struct {
*base
Connection
}
// NewIngress returns a new Ingress.
func NewIngress(c Connection) *Ingress {
return &Ingress{&base{}, c}
}
// Get a Ingress.
func (i *Ingress) Get(ns, n string) (interface{}, error) {
return i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).Get(n, metav1.GetOptions{})
}
// List all Ingresses in a given namespace.
func (i *Ingress) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Ingress.
func (i *Ingress) Delete(ns, n string, cascade, force bool) error {
return i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).Delete(n, nil)
}

View File

@ -1,94 +0,0 @@
package k8s
import (
"fmt"
"strings"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
)
type (
// Job represents a Kubernetes Job.
Job struct {
*base
Connection
}
// Loggable represents a K8s resource that has containers and can be logged.
Loggable interface {
Containers(ns, n string, includeInit bool) ([]string, error)
Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request
}
)
// NewJob returns a new Job.
func NewJob(c Connection) *Job {
return &Job{&base{}, c}
}
// Get a Job.
func (j *Job) Get(ns, n string) (interface{}, error) {
return j.DialOrDie().BatchV1().Jobs(ns).Get(n, metav1.GetOptions{})
}
// List all Jobs in a given namespace.
func (j *Job) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := j.DialOrDie().BatchV1().Jobs(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Job.
func (j *Job) Delete(ns, n string, cascade, force bool) error {
return j.DialOrDie().BatchV1().Jobs(ns).Delete(n, nil)
}
// Containers returns all container names on job.
func (j *Job) Containers(ns, n string, includeInit bool) ([]string, error) {
pod, err := j.assocPod(ns, n)
if err != nil {
return nil, err
}
return NewPod(j).Containers(ns, pod, includeInit)
}
// Logs fetch container logs for a given job and container.
func (j *Job) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request {
pod, err := j.assocPod(ns, n)
if err != nil {
return nil
}
return NewPod(j).Logs(ns, pod, opts)
}
// Events retrieved jobs events.
func (j *Job) Events(ns, n string) (*v1.EventList, error) {
e := j.DialOrDie().CoreV1().Events(ns)
return e.List(metav1.ListOptions{
FieldSelector: e.GetFieldSelector(&n, &ns, nil, nil).String(),
})
}
func (j *Job) assocPod(ns, n string) (string, error) {
ee, err := j.Events(ns, n)
if err != nil {
return "", err
}
for _, e := range ee.Items {
if strings.Contains(e.Message, "Created pod: ") {
return strings.TrimSpace(strings.Replace(e.Message, "Created pod: ", "", 1)), nil
}
}
return "", fmt.Errorf("unable to find associated pod name for job: %s/%s", ns, n)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Node represents a Kubernetes node.
type Node struct {
*base
Connection
}
// NewNode returns a new Node.
func NewNode(c Connection) *Node {
return &Node{&base{}, c}
}
// Get a node.
func (n *Node) Get(_, name string) (interface{}, error) {
return n.DialOrDie().CoreV1().Nodes().Get(name, metav1.GetOptions{})
}
// List all nodes on the cluster.
func (n *Node) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := n.DialOrDie().CoreV1().Nodes().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a node.
func (n *Node) Delete(_, name string, cascade, force bool) error {
return n.DialOrDie().CoreV1().Nodes().Delete(name, nil)
}

View File

@ -1,46 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NetworkPolicy represents a Kubernetes NetworkPolicy
type NetworkPolicy struct {
*base
Connection
}
// NewNetworkPolicy returns a new NetworkPolicy.
func NewNetworkPolicy(c Connection) *NetworkPolicy {
return &NetworkPolicy{&base{}, c}
}
// Get a NetworkPolicy.
func (d *NetworkPolicy) Get(ns, n string) (interface{}, error) {
return d.DialOrDie().NetworkingV1().NetworkPolicies(ns).Get(n, metav1.GetOptions{})
}
// List all NetworkPolicys in a given namespace.
func (d *NetworkPolicy) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := d.DialOrDie().NetworkingV1().NetworkPolicies(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a NetworkPolicy.
func (d *NetworkPolicy) Delete(ns, n string, cascade, force bool) error {
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
return d.DialOrDie().NetworkingV1().NetworkPolicies(ns).Delete(n, &metav1.DeleteOptions{
PropagationPolicy: &p,
})
}

View File

@ -1,39 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Namespace represents a Kubernetes namespace.
type Namespace struct {
*base
Connection
}
// NewNamespace returns a new Namespace.
func NewNamespace(c Connection) *Namespace {
return &Namespace{&base{}, c}
}
// Get a active namespace.
func (n *Namespace) Get(_, name string) (interface{}, error) {
return n.DialOrDie().CoreV1().Namespaces().Get(name, metav1.GetOptions{})
}
// List all active namespaces on the cluster.
func (n *Namespace) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := n.DialOrDie().CoreV1().Namespaces().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a namespace.
func (n *Namespace) Delete(_, name string, cascade, force bool) error {
return n.DialOrDie().CoreV1().Namespaces().Delete(name, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PodDisruptionBudget represents a Kubernetes PodDisruptionBudget.
type PodDisruptionBudget struct {
*base
Connection
}
// NewPodDisruptionBudget returns a new PodDisruptionBudget.
func NewPodDisruptionBudget(c Connection) *PodDisruptionBudget {
return &PodDisruptionBudget{&base{}, c}
}
// Get a pdb.
func (p *PodDisruptionBudget) Get(ns, n string) (interface{}, error) {
return p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Get(n, metav1.GetOptions{})
}
// List all pdbs in a given namespace.
func (p *PodDisruptionBudget) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a pdb.
func (p *PodDisruptionBudget) Delete(ns, n string, cascade, force bool) error {
return p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Delete(n, nil)
}

View File

@ -1,78 +0,0 @@
package k8s
import (
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
)
const defaultKillGrace int64 = 5
// Pod represents a Kubernetes Pod.
type Pod struct {
*base
Connection
}
// NewPod returns a new Pod.
func NewPod(c Connection) *Pod {
return &Pod{base: &base{}, Connection: c}
}
// Get a pod.
func (p *Pod) Get(ns, name string) (interface{}, error) {
return p.DialOrDie().CoreV1().Pods(ns).Get(name, metav1.GetOptions{})
}
// List all pods in a given namespace.
func (p *Pod) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().Pods(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a pod.
func (p *Pod) Delete(ns, n string, cascade, force bool) error {
log.Debug().Msgf("Killing Pod %s %t:%t", n, cascade, force)
grace := defaultKillGrace
if force {
grace = 0
}
return p.DialOrDie().CoreV1().Pods(ns).Delete(n, &metav1.DeleteOptions{
GracePeriodSeconds: &grace,
})
}
// Containers returns all container names on pod
func (p *Pod) Containers(ns, n string, includeInit bool) ([]string, error) {
po, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
if err != nil {
return nil, err
}
cc := []string{}
for _, c := range po.Spec.Containers {
cc = append(cc, c.Name)
}
if includeInit {
for _, c := range po.Spec.InitContainers {
cc = append(cc, c.Name)
}
}
return cc, nil
}
// Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request {
return p.DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
}

View File

@ -1,41 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PersistentVolume represents a Kubernetes PersistentVolume.
type PersistentVolume struct {
*base
Connection
}
// NewPersistentVolume returns a new PersistentVolume.
func NewPersistentVolume(c Connection) *PersistentVolume {
return &PersistentVolume{&base{}, c}
}
// Get a PersistentVolume.
func (p *PersistentVolume) Get(_, n string) (interface{}, error) {
return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{})
}
// List all PersistentVolumes in a given namespace.
func (p *PersistentVolume) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a PersistentVolume.
func (p *PersistentVolume) Delete(_, n string, cascade, force bool) error {
return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim.
type PersistentVolumeClaim struct {
*base
Connection
}
// NewPersistentVolumeClaim returns a new PersistentVolumeClaim.
func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim {
return &PersistentVolumeClaim{&base{}, c}
}
// Get a PersistentVolumeClaim.
func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) {
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{})
}
// List all PersistentVolumeClaims in a given namespace.
func (p *PersistentVolumeClaim) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a PersistentVolumeClaim.
func (p *PersistentVolumeClaim) Delete(ns, n string, cascade, force bool) error {
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil)
}

View File

@ -1,58 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ReplicationController represents a Kubernetes ReplicationController.
type ReplicationController struct {
*base
Connection
}
// NewReplicationController returns a new ReplicationController.
func NewReplicationController(c Connection) *ReplicationController {
return &ReplicationController{&base{}, c}
}
// Get a RC.
func (r *ReplicationController) Get(ns, n string) (interface{}, error) {
return r.DialOrDie().CoreV1().ReplicationControllers(ns).Get(n, metav1.GetOptions{})
}
// List all RCs in a given namespace.
func (r *ReplicationController) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := r.DialOrDie().CoreV1().ReplicationControllers(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a RC.
func (r *ReplicationController) Delete(ns, n string, cascade, force bool) error {
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
return r.DialOrDie().CoreV1().ReplicationControllers(ns).Delete(n, &metav1.DeleteOptions{
PropagationPolicy: &p,
})
}
// Scale a ReplicationController.
func (r *ReplicationController) Scale(ns, n string, replicas int32) error {
scale, err := r.DialOrDie().CoreV1().ReplicationControllers(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = r.DialOrDie().CoreV1().ReplicationControllers(ns).UpdateScale(n, scale)
return err
}

View File

@ -1,103 +0,0 @@
package k8s
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
// Resource represents a Kubernetes Resource
type Resource struct {
*base
Connection
gvr GVR
}
// NewResource returns a new Resource.
func NewResource(c Connection, gvr GVR) *Resource {
return &Resource{base: &base{}, Connection: c, gvr: gvr}
}
// GetInfo returns info about apigroup.
func (r *Resource) GetInfo() GVR {
return r.gvr
}
func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
return r.DynDialOrDie().Resource(r.gvr.AsGVR())
}
// Get a Resource.
func (r *Resource) Get(ns, n string) (interface{}, error) {
return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{})
}
// List all Resources in a given namespace.
func (r *Resource) List(ns string, opts metav1.ListOptions) (Collection, error) {
obj, err := r.listAll(ns, r.gvr.ToR())
if err != nil {
return nil, err
}
return Collection{obj.(*metav1beta1.Table)}, nil
}
// Delete a Resource.
func (r *Resource) Delete(ns, n string, cascade, force bool) error {
return r.nsRes().Namespace(ns).Delete(n, nil)
}
// ----------------------------------------------------------------------------
// Helpers...
const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json"
func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
_, codec := r.codec()
c, err := r.getClient()
if err != nil {
return nil, err
}
return c.Get().
SetHeader("Accept", a).
Namespace(ns).
Resource(n).
VersionedParams(&metav1beta1.TableOptions{}, codec).
Do().Get()
}
func (r *Resource) getClient() (*rest.RESTClient, error) {
crConfig := r.RestConfigOrDie()
gv := r.gvr.AsGR()
crConfig.GroupVersion = &gv
crConfig.APIPath = "/apis"
if len(r.gvr.ToG()) == 0 {
crConfig.APIPath = "/api"
}
codec, _ := r.codec()
crConfig.NegotiatedSerializer = codec.WithoutConversion()
crRestClient, err := rest.RESTClientFor(crConfig)
if err != nil {
return nil, err
}
return crRestClient, nil
}
func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) {
scheme := runtime.NewScheme()
gv := r.gvr.AsGR()
metav1.AddToGroupVersion(scheme, gv)
scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme)
}

View File

@ -1,41 +0,0 @@
package k8s
import (
// rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Role represents a Kubernetes Role.
type Role struct {
*base
Connection
}
// NewRole returns a new Role.
func NewRole(c Connection) *Role {
return &Role{&base{}, c}
}
// Get a Role.
func (r *Role) Get(ns, n string) (interface{}, error) {
return r.DialOrDie().RbacV1().Roles(ns).Get(n, metav1.GetOptions{})
}
// List all Roles in a given namespace.
func (r *Role) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := r.DialOrDie().RbacV1().Roles(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Role.
func (r *Role) Delete(ns, n string, cascade, force bool) error {
return r.DialOrDie().RbacV1().Roles(ns).Delete(n, nil)
}

View File

@ -1,38 +0,0 @@
package k8s
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// RoleBinding represents a Kubernetes RoleBinding.
type RoleBinding struct {
*base
Connection
}
// NewRoleBinding returns a new RoleBinding.
func NewRoleBinding(c Connection) *RoleBinding {
return &RoleBinding{&base{}, c}
}
// Get a RoleBinding.
func (r *RoleBinding) Get(ns, n string) (interface{}, error) {
return r.DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{})
}
// List all RoleBindings in a given namespace.
func (r *RoleBinding) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := r.DialOrDie().RbacV1().RoleBindings(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a RoleBinding.
func (r *RoleBinding) Delete(ns, n string, cascade, force bool) error {
return r.DialOrDie().RbacV1().RoleBindings(ns).Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ReplicaSet represents a Kubernetes ReplicaSet.
type ReplicaSet struct {
*base
Connection
}
// NewReplicaSet returns a new ReplicaSet.
func NewReplicaSet(c Connection) *ReplicaSet {
return &ReplicaSet{&base{}, c}
}
// Get a ReplicaSet.
func (r *ReplicaSet) Get(ns, n string) (interface{}, error) {
return r.DialOrDie().AppsV1().ReplicaSets(ns).Get(n, metav1.GetOptions{})
}
// List all ReplicaSets in a given namespace.
func (r *ReplicaSet) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := r.DialOrDie().AppsV1().ReplicaSets(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a ReplicaSet.
func (r *ReplicaSet) Delete(ns, n string, cascade, force bool) error {
return r.DialOrDie().AppsV1().ReplicaSets(ns).Delete(n, nil)
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ServiceAccount manages a Kubernetes ServiceAccount.
type ServiceAccount struct {
*base
Connection
}
// NewServiceAccount instantiates a new ServiceAccount.
func NewServiceAccount(c Connection) *ServiceAccount {
return &ServiceAccount{&base{}, c}
}
// Get a ServiceAccount.
func (s *ServiceAccount) Get(ns, n string) (interface{}, error) {
return s.DialOrDie().CoreV1().ServiceAccounts(ns).Get(n, metav1.GetOptions{})
}
// List all ServiceAccounts in a given namespace.
func (s *ServiceAccount) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := s.DialOrDie().CoreV1().ServiceAccounts(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a ServiceAccount.
func (s *ServiceAccount) Delete(ns, n string, cascade, force bool) error {
return s.DialOrDie().CoreV1().ServiceAccounts(ns).Delete(n, nil)
}

View File

@ -1,41 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// StorageClass represents a Kubernetes StorageClass.
type StorageClass struct {
*base
Connection
}
// NewStorageClass returns a new StorageClass.
func NewStorageClass(c Connection) *StorageClass {
return &StorageClass{&base{}, c}
}
// Get a StorageClass.
func (p *StorageClass) Get(_, n string) (interface{}, error) {
return p.DialOrDie().StorageV1().StorageClasses().Get(n, metav1.GetOptions{})
}
// List all StorageClasses in a given namespace.
func (p *StorageClass) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := p.DialOrDie().StorageV1().StorageClasses().List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a StorageClass.
func (p *StorageClass) Delete(_, n string, cascade, force bool) error {
return p.DialOrDie().StorageV1().StorageClasses().Delete(n, nil)
}

View File

@ -1,76 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubectl/pkg/polymorphichelpers"
)
// StatefulSet manages a Kubernetes StatefulSet.
type StatefulSet struct {
*base
Connection
}
// NewStatefulSet instantiates a new StatefulSet.
func NewStatefulSet(c Connection) *StatefulSet {
return &StatefulSet{&base{}, c}
}
// Get a StatefulSet.
func (s *StatefulSet) Get(ns, n string) (interface{}, error) {
return s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{})
}
// List all StatefulSets in a given namespace.
func (s *StatefulSet) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := s.DialOrDie().AppsV1().StatefulSets(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a StatefulSet.
func (s *StatefulSet) Delete(ns, n string, cascade, force bool) error {
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
return s.DialOrDie().AppsV1().StatefulSets(ns).Delete(n, &metav1.DeleteOptions{
PropagationPolicy: &p,
})
}
// Scale a StatefulSet.
func (s *StatefulSet) Scale(ns, n string, replicas int32) error {
scale, err := s.DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
}
scale.Spec.Replicas = replicas
_, err = s.DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(n, scale)
return err
}
// Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(ns, n string) error {
sts, err := s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{})
if err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(sts)
if err != nil {
return err
}
_, err = s.DialOrDie().AppsV1().StatefulSets(ns).Patch(sts.Name, types.StrategicMergePatchType, update)
return err
}

View File

@ -1,40 +0,0 @@
package k8s
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Service represents a Kubernetes Service.
type Service struct {
*base
Connection
}
// NewService returns a new Service.
func NewService(c Connection) *Service {
return &Service{&base{}, c}
}
// Get a service.
func (s *Service) Get(ns, n string) (interface{}, error) {
return s.DialOrDie().CoreV1().Services(ns).Get(n, metav1.GetOptions{})
}
// List all Services in a given namespace.
func (s *Service) List(ns string, opts metav1.ListOptions) (Collection, error) {
rr, err := s.DialOrDie().CoreV1().Services(ns).List(opts)
if err != nil {
return nil, err
}
cc := make(Collection, len(rr.Items))
for i, r := range rr.Items {
cc[i] = r
}
return cc, nil
}
// Delete a Service.
func (s *Service) Delete(ns, n string, cascade, force bool) error {
return s.DialOrDie().CoreV1().Services(ns).Delete(n, nil)
}

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