derailed 2020-02-12 16:43:22 -07:00
parent e43260ed05
commit 7ad07b791d
22 changed files with 217 additions and 137 deletions

5
.gitignore vendored
View File

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

108
README.md
View File

@ -145,16 +145,16 @@ K9s uses aliases to navigate most K8s resources.
--- ---
## K9s config file ($HOME/.k9s/config.yml) ## K9s Configuration
K9s keeps its configurations in a .k9s directory in your home directory. K9s keeps its configurations in a .k9s directory in your home directory `$HOME/.k9s/config.yml`.
> NOTE: This is still in flux and will change while in pre-release stage! > NOTE: This is still in flux and will change while in pre-release stage!
```yaml ```yaml
# config.yml # config.yml
k9s: k9s:
# Indicates api-server poll intervals. # Represents ui poll intervals.
refreshRate: 2 refreshRate: 2
# Indicates whether modification commands like delete/kill/edit are disabled. Default is false # Indicates whether modification commands like delete/kill/edit are disabled. Default is false
readOnly: false readOnly: false
@ -189,9 +189,9 @@ K9s uses aliases to navigate most K8s resources.
--- ---
## Aliases ## Command Aliases
In K9s, you can define your own command aliases (shortnames) to access your resources. In your `$HOME/.k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file: In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file:
```yaml ```yaml
# $HOME/.k9s/alias.yml # $HOME/.k9s/alias.yml
@ -204,9 +204,44 @@ Using this alias file, you can now type pp/crb to list pods or clusterrolebindin
--- ---
## HotKey Support
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps:
1. Create a file named `$HOME/.k9s/hotkey.yml`
2. Add the following to your `hotkey.yml`. You can use resource name/short name to specify a command ie same as typing it while in command mode.
```yaml
# $HOME/.k9s/hotkey.yml
hotKey:
# Hitting Shift-0 navigates to your pod view
shift-0:
shortCut: Shift-0
description: Viewing pods
command: pods
# Hitting Shift-1 navigates to your deployments
shift-1:
shortCut: Shift-1
description: View deployments
command: dp
# Hitting Shift-2 navigates to your xray deployments
shift-2:
shortCut: Shift-4
description: Xray Deployments
command: xray deploy
```
Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them.
You can choose any keyboard shotcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
> NOTE: This feature/configuration might change in future releases!
---
## Plugins ## Plugins
K9s allows you to define your own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate available plugins. A plugin is defined as follows: K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate all available plugins. A plugin is defined as follows:
```yaml ```yaml
# $HOME/.k9s/plugin.yml # $HOME/.k9s/plugin.yml
@ -217,7 +252,7 @@ plugin:
description: Pod logs description: Pod logs
scopes: scopes:
- po - po
command: /usr/local/bin/kubectl command: kubectl
background: false background: false
args: args:
- logs - logs
@ -237,6 +272,8 @@ K9s does provide additional environment variables for you to customize your plug
* `$NAMESPACE` -- the selected resource namespace * `$NAMESPACE` -- the selected resource namespace
* `$NAME` -- the selected resource name * `$NAME` -- the selected resource name
* `$CONTAINER` -- the current container if applicable
* `$FILTER` -- the current filter if any
* `$KUBECONFIG` -- the KubeConfig location. * `$KUBECONFIG` -- the KubeConfig location.
* `$CLUSTER` the active cluster name * `$CLUSTER` the active cluster name
* `$CONTEXT` the active context name * `$CONTEXT` the active context name
@ -244,13 +281,13 @@ K9s does provide additional environment variables for you to customize your plug
* `$GROUPS` the active groups * `$GROUPS` the active groups
* `$COLX` the column at index X for the viewed resource * `$COLX` the column at index X for the viewed resource
NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies. > NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies.
--- ---
## Benchmarking ## Benchmark Your Applications
K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll) of Google fame. Hey is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!). K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll). `Hey` is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!).
To setup a port-forward, you will need to navigate to the PodView, select a pod and a container that exposes a given port. Using `SHIFT-F` a dialog comes up to allow you to specify a local port to forward. Once acknowledged, you can navigate to the PortForward view (alias `pf`) listing out your active port-forwards. Selecting a port-forward and using `CTRL-B` will run a benchmark on that HTTP endpoint. To view the results of your benchmark runs, go to the Benchmarks view (alias `be`). You should now be able to select a benchmark and view the run stats details by pressing `<ENTER>`. NOTE: Port-forwards only last for the duration of the K9s session and will be terminated upon exit. To setup a port-forward, you will need to navigate to the PodView, select a pod and a container that exposes a given port. Using `SHIFT-F` a dialog comes up to allow you to specify a local port to forward. Once acknowledged, you can navigate to the PortForward view (alias `pf`) listing out your active port-forwards. Selecting a port-forward and using `CTRL-B` will run a benchmark on that HTTP endpoint. To view the results of your benchmark runs, go to the Benchmarks view (alias `be`). You should now be able to select a benchmark and view the run stats details by pressing `<ENTER>`. NOTE: Port-forwards only last for the duration of the K9s session and will be terminated upon exit.
@ -272,11 +309,11 @@ benchmarks:
defaults: defaults:
# One concurrent connection # One concurrent connection
concurrency: 1 concurrency: 1
# 500 requests will be sent to an endpoint # Number of requests that will be sent to an endpoint
requests: 500 requests: 500
containers: containers:
# Containers section allows you to configure your http container's endpoints and benchmarking settings. # Containers section allows you to configure your http container's endpoints and benchmarking settings.
# NOTE: the container ID syntax uses namespace/pod_name:container_name # NOTE: the container ID syntax uses namespace/pod-name:container-name
default/nginx:nginx: default/nginx:nginx:
# Benchmark a container named nginx using POST HTTP verb using http://localhost:port/bozo URL and headers. # Benchmark a container named nginx using POST HTTP verb using http://localhost:port/bozo URL and headers.
concurrency: 1 concurrency: 1
@ -295,15 +332,15 @@ benchmarks:
# Similary you can Benchmark an HTTP service exposed either via nodeport, loadbalancer types. # Similary you can Benchmark an HTTP service exposed either via nodeport, loadbalancer types.
# Service ID is ns/svc-name # Service ID is ns/svc-name
default/nginx: default/nginx:
# Hit the service with 5 concurrent sessions # Set the concurrency level
concurrency: 5 concurrency: 5
# Issues a total of 500 requests # Number of requests to be sent
requests: 500 requests: 500
http: http:
method: GET method: GET
# This setting will depend on whether service is nodeport or loadbalancer. Nodeport may require vendor port tuneling setting. # This setting will depend on whether service is nodeport or loadbalancer. Nodeport may require vendor port tuneling setting.
# Set this to a node if nodeport or LB if applicable. IP or dns name. # Set this to a node if nodeport or LB if applicable. IP or dns name.
host: 10.11.13.14 host: A.B.C.D
path: /bumblebeetuna path: /bumblebeetuna
auth: auth:
user: jean-baptiste-emmanuel user: jean-baptiste-emmanuel
@ -312,41 +349,6 @@ benchmarks:
--- ---
## HotKeys
Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps:
1. In your .k9s home directory create a file named `hotkey.yml`
2. Add the following to your `hotkey.yml`. You can use short names or resource name to specify a command ie same as typing it in command mode.
```yaml
hotKey:
shift-0:
shortCut: Shift-0
description: View pods
command: pods
shift-1:
shortCut: Shift-1
description: View deployments
command: dp
shift-2:
shortCut: Shift-2
description: View services
command: service
shift-3:
shortCut: Shift-3
description: View statefulsets
command: sts
```
Not feeling so hot? Your custom hotkeys list will be listed in the help view.`<?>`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them.
You can choose any keyboard shotcuts that make sense to you, provided they are not part of the standard K9s shortcuts list.
NOTE: This feature/configuration might change in future releases!
---
## K9s RBAC FU ## 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. 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.
@ -459,7 +461,7 @@ Colors can be defined by name or uing an hex representation. Of recent, we've ad
> NOTE: This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly! > NOTE: This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly!
```yaml ```yaml
# InTheNavy Skin... # Skin InTheNavy...
k9s: k9s:
# General K9s styles # General K9s styles
body: body:
@ -589,8 +591,8 @@ to make this project a reality!
## Meet The Core Team! ## Meet The Core Team!
* [Fernand Galiana](https://github.com/derailed) * [Fernand Galiana](https://github.com/derailed)
* <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io * <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io
* <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en) * <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
--- ---

5
go.mod
View File

@ -32,6 +32,7 @@ require (
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/atotto/clipboard v0.1.2 github.com/atotto/clipboard v0.1.2
github.com/derailed/tview v0.3.4 github.com/derailed/tview v0.3.4
github.com/drone/envsubst v1.0.2 // indirect
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/fatih/color v1.6.0 github.com/fatih/color v1.6.0
@ -41,9 +42,13 @@ require (
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.5 github.com/mattn/go-runewidth v0.0.5
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec // indirect
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c
github.com/openfaas/faas-provider v0.15.0 // indirect
github.com/petergtz/pegomock v2.6.0+incompatible github.com/petergtz/pegomock v2.6.0+incompatible
github.com/rakyll/hey v0.1.2 github.com/rakyll/hey v0.1.2
github.com/rs/zerolog v1.17.2 github.com/rs/zerolog v1.17.2
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sahilm/fuzzy v0.1.0 github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0

14
go.sum
View File

@ -177,6 +177,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= 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/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo=
github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/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-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs= github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs=
@ -339,9 +341,11 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= 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/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 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/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@ -450,6 +454,7 @@ github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pR
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
@ -497,6 +502,12 @@ github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2
github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/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/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/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec h1:S6wtb5ie7KeMcuEaESj0RoSmpyGfvOSuunmKEdX7wg8=
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14=
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c h1:9RGaDpUySgRscx5oiagwUtm9vBZti/4+QYq2GM4FegE=
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c/go.mod h1:u/KO+e43wkagC0lqM1eaqNEWEBdg08Q1ugP/idj39MM=
github.com/openfaas/faas-provider v0.15.0 h1:3x5ma90FL7AqP4NOD6f03AY24y3xBeVF6xGLUx6Xrlc=
github.com/openfaas/faas-provider v0.15.0/go.mod h1:8Fagi2UeMfL+gZAqZWSMQg86i+w1+hBOKtwKRP5sLFI=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 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.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -557,6 +568,8 @@ github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvf
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -631,6 +644,7 @@ github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 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 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 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 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8=

View File

@ -62,7 +62,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
} }
// TailLogs tails a given container logs // TailLogs tails a given container logs
func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { func (c *Container) TailLogs(ctx context.Context, logChan chan<- []byte, opts LogOptions) error {
fac, ok := ctx.Value(internal.KeyFactory).(Factory) fac, ok := ctx.Value(internal.KeyFactory).(Factory)
if !ok { if !ok {
return errors.New("Expecting an informer") return errors.New("Expecting an informer")

View File

@ -21,11 +21,11 @@ type CustomResourceDefinition struct {
// List returns a collection of nodes. // List returns a collection of nodes.
func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtime.Object, error) { func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtime.Object, error) {
strLabel, ok := ctx.Value(internal.KeyLabels).(string) strLabel, ok := ctx.Value(internal.KeyLabels).(string)
lsel := labels.Everything() labelSel := labels.Everything()
if sel, e := labels.ConvertSelectorToLabelsMap(strLabel); ok && e == nil { if sel, e := labels.ConvertSelectorToLabelsMap(strLabel); ok && e == nil {
lsel = sel.AsSelector() labelSel = sel.AsSelector()
} }
const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions" const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
return c.Factory.List(gvr, "-", true, lsel) return c.Factory.List(gvr, "-", true, labelSel)
} }

View File

@ -79,7 +79,7 @@ func (d *Deployment) Restart(path string) error {
} }
// TailLogs tail logs for all pods represented by this Deployment. // TailLogs tail logs for all pods represented by this Deployment.
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (d *Deployment) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything()) o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
@ -61,7 +62,7 @@ func (d *DaemonSet) Restart(path string) error {
} }
// TailLogs tail logs for all pods represented by this DaemonSet. // TailLogs tail logs for all pods represented by this DaemonSet.
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything()) o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything())
if err != nil { if err != nil {
return err return err
@ -79,7 +80,11 @@ func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptio
return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts) return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts)
} }
func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error { func podLogs(ctx context.Context, c chan<- []byte, sel map[string]string, opts LogOptions) error {
defer func(t time.Time) {
log.Debug().Msgf("POD LOGS %v", time.Since(t))
}(time.Now())
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok { if !ok {
return errors.New("expecting a context factory") return errors.New("expecting a context factory")
@ -94,7 +99,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
} }
ns, _ := client.Namespaced(opts.Path) ns, _ := client.Namespaced(opts.Path)
oo, err := f.List("v1/pods", ns, true, lsel) oo, err := f.List("v1/pods", ns, false, lsel)
if err != nil { if err != nil {
return err return err
} }

View File

@ -28,7 +28,7 @@ type Generic struct {
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) { func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, ok := ctx.Value(internal.KeyLabels).(string) labelSel, ok := ctx.Value(internal.KeyLabels).(string)
if !ok { if !ok {
log.Warn().Msgf("No label selector found in context. Listing all resources") log.Debug().Msgf("No label selector found in context. Listing all resources")
} }
if client.IsAllNamespace(ns) { if client.IsAllNamespace(ns) {
ns = client.AllNamespaces ns = client.AllNamespaces

View File

@ -23,7 +23,7 @@ type Job struct {
} }
// TailLogs tail logs for all pods represented by this Job. // TailLogs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (j *Job) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
o, err := j.Factory.Get(j.gvr.String(), opts.Path, true, labels.Everything()) o, err := j.Factory.Get(j.gvr.String(), opts.Path, true, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -47,19 +47,26 @@ func colorize(c color.Paint, txt string) string {
} }
// DecorateLog add a log header to display po/co information along with the log message. // DecorateLog add a log header to display po/co information along with the log message.
func (o LogOptions) DecorateLog(msg string) string { func (o LogOptions) DecorateLog(bytes []byte) []byte {
_, n := client.Namespaced(o.Path) if len(bytes) == 0 {
if msg == "" { return bytes
return msg
} }
bytes = bytes[:len(bytes)-1]
_, n := client.Namespaced(o.Path)
var prefix []byte
if o.MultiPods { if o.MultiPods {
return colorize(o.Color, n+":"+o.Container+" ") + msg prefix = []byte(colorize(o.Color, n+":"+o.Container+" "))
} }
if !o.SingleContainer { if !o.SingleContainer {
return colorize(o.Color, o.Container+" ") + msg prefix = []byte(colorize(o.Color, o.Container+" "))
} }
return msg if len(prefix) == 0 {
return bytes
}
return append(prefix, bytes...)
} }

View File

@ -148,14 +148,14 @@ func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
} }
// TailLogs tails a given container logs // TailLogs tails a given container logs
func (p *Pod) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (p *Pod) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
if !opts.HasContainer() { if !opts.HasContainer() {
return p.logs(ctx, c, opts) return p.logs(ctx, c, opts)
} }
return tailLogs(ctx, p, c, opts) return tailLogs(ctx, p, c, opts)
} }
func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error { func (p *Pod) logs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok { if !ok {
return errors.New("Expecting an informer") return errors.New("Expecting an informer")
@ -194,7 +194,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error
return nil return nil
} }
func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptions) error { func tailLogs(ctx context.Context, logger Logger, c chan<- []byte, opts LogOptions) error {
log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container) log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container)
o := v1.PodLogOptions{ o := v1.PodLogOptions{
Container: opts.Container, Container: opts.Container,
@ -206,11 +206,13 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
if err != nil { if err != nil {
return err return err
} }
ctxt, cancelFunc := context.WithCancel(ctx)
req.Context(ctxt) var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
req.Context(ctx)
var blocked int32 = 1 var blocked int32 = 1
go logsTimeout(cancelFunc, &blocked) go logsTimeout(cancel, &blocked)
// This call will block if nothing is in the stream!! // This call will block if nothing is in the stream!!
stream, err := req.Stream() stream, err := req.Stream()
@ -219,7 +221,7 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path) log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path)
return fmt.Errorf("Unable to obtain log stream for %s", opts.Path) return fmt.Errorf("Unable to obtain log stream for %s", opts.Path)
} }
go readLogs(ctx, stream, c, opts) go readLogs(stream, c, opts)
return nil return nil
} }
@ -232,7 +234,7 @@ func logsTimeout(cancel context.CancelFunc, blocked *int32) {
} }
} }
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { func readLogs(stream io.ReadCloser, c chan<- []byte, opts LogOptions) {
defer func() { defer func() {
log.Debug().Msgf(">>> Closing stream `%s", opts.Path) log.Debug().Msgf(">>> Closing stream `%s", opts.Path)
if err := stream.Close(); err != nil { if err := stream.Close(); err != nil {
@ -240,16 +242,18 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L
} }
}() }()
scanner := bufio.NewScanner(stream) r := bufio.NewReader(stream)
for scanner.Scan() { for {
select { bytes, err := r.ReadBytes('\n')
case <-ctx.Done(): if err != nil {
log.Warn().Err(err).Msg("Read error")
if err != io.EOF {
log.Error().Err(err).Msgf("stream reader failed")
}
return return
default:
c <- opts.DecorateLog(scanner.Text())
} }
c <- opts.DecorateLog(bytes)
} }
log.Error().Msgf("SCAN_ERR %#v", scanner.Err())
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -80,7 +80,7 @@ func (s *StatefulSet) Restart(path string) error {
} }
// TailLogs tail logs for all pods represented by this StatefulSet. // TailLogs tail logs for all pods represented by this StatefulSet.
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything()) o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -22,7 +22,7 @@ type Service struct {
} }
// TailLogs tail logs for all pods represented by this Service. // TailLogs tail logs for all pods represented by this Service.
func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (s *Service) TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error {
o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything()) o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -22,7 +23,6 @@ type Table struct {
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
ns, n := client.Namespaced(path) ns, n := client.Namespaced(path)
log.Debug().Msgf("TABLE-GET %q:%q", ns, t.gvr)
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
_, codec := t.codec() _, codec := t.codec()
@ -46,6 +46,11 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
// List all Resources in a given namespace. // List all Resources in a given namespace.
func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, ok := ctx.Value(internal.KeyLabels).(string)
if !ok {
log.Debug().Msgf("No label selector found in context. Listing all resources")
}
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
_, codec := t.codec() _, codec := t.codec()
@ -57,7 +62,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
SetHeader("Accept", a). SetHeader("Accept", a).
Namespace(ns). Namespace(ns).
Resource(t.gvr.R()). Resource(t.gvr.R()).
VersionedParams(&metav1beta1.TableOptions{}, codec). VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec).
Do().Get() Do().Get()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -73,7 +73,7 @@ type Accessor interface {
// Loggable represents resources with logs. // Loggable represents resources with logs.
type Loggable interface { type Loggable interface {
// TaiLogs streams resource logs. // TaiLogs streams resource logs.
TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error TailLogs(ctx context.Context, c chan<- []byte, opts LogOptions) error
} }
// Describer describes a resource. // Describer describes a resource.

View File

@ -0,0 +1,46 @@
package model_test
import (
"testing"
"github.com/derailed/k9s/internal/model"
"github.com/stretchr/testify/assert"
)
func TestClusterMetaDelta(t *testing.T) {
uu := map[string]struct {
o, n model.ClusterMeta
e bool
}{
"empty": {
o: model.NewClusterMeta(),
n: model.NewClusterMeta(),
},
"same": {
o: makeClusterMeta("fred"),
n: makeClusterMeta("fred"),
},
"diff": {
o: makeClusterMeta("fred"),
n: makeClusterMeta("freddie"),
e: true,
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.o.Deltas(u.n))
})
}
}
// Helpers...
func makeClusterMeta(cluster string) model.ClusterMeta {
m := model.NewClusterMeta()
m.Cluster = cluster
m.Cpu, m.Mem = 10, 20
return m
}

View File

@ -31,25 +31,23 @@ type LogsListener interface {
// Log represents a resource logger. // Log represents a resource logger.
type Log struct { type Log struct {
factory dao.Factory factory dao.Factory
lines []string lines []string
listeners []LogsListener listeners []LogsListener
gvr client.GVR gvr client.GVR
logOptions dao.LogOptions logOptions dao.LogOptions
cancelFn context.CancelFunc cancelFn context.CancelFunc
initialized bool mx sync.RWMutex
mx sync.RWMutex filter string
filter string lastSent int
lastSent int
} }
// NewLog returns a new model. // NewLog returns a new model.
func NewLog(gvr client.GVR, msg string, opts dao.LogOptions, timeOut time.Duration) *Log { func NewLog(gvr client.GVR, opts dao.LogOptions, timeOut time.Duration) *Log {
return &Log{ return &Log{
gvr: gvr, gvr: gvr,
logOptions: opts, logOptions: opts,
initialized: true, lines: nil,
lines: []string{msg},
} }
} }
@ -84,12 +82,11 @@ func (l *Log) Start() {
// Stop terminates log tailing. // Stop terminates log tailing.
func (l *Log) Stop() { func (l *Log) Stop() {
if l.cancelFn == nil { defer log.Debug().Msgf("<<<< Logger STOPPED!")
return if l.cancelFn != nil {
l.cancelFn()
l.cancelFn = nil
} }
log.Debug().Msgf("<<<< Logger STOP!")
l.cancelFn()
l.cancelFn = nil
} }
// Set sets the log lines (for testing only!) // Set sets the log lines (for testing only!)
@ -131,7 +128,7 @@ func (l *Log) load() error {
ctx = context.WithValue(context.Background(), internal.KeyFactory, l.factory) ctx = context.WithValue(context.Background(), internal.KeyFactory, l.factory)
ctx, l.cancelFn = context.WithCancel(ctx) ctx, l.cancelFn = context.WithCancel(ctx)
c := make(chan string, 10) c := make(chan []byte, 10)
go l.updateLogs(ctx, c) go l.updateLogs(ctx, c)
accessor, err := dao.AccessorFor(l.factory, l.gvr) accessor, err := dao.AccessorFor(l.factory, l.gvr)
@ -163,8 +160,7 @@ func (l *Log) Append(line string) {
l.mx.Lock() l.mx.Lock()
defer l.mx.Unlock() defer l.mx.Unlock()
if l.initialized { if l.lines == nil {
l.lines, l.initialized, l.lastSent = []string{}, false, 0
l.fireLogCleared() l.fireLogCleared()
} }
@ -190,20 +186,20 @@ func (l *Log) Notify(timedOut bool) {
} }
} }
func (l *Log) updateLogs(ctx context.Context, c <-chan string) { func (l *Log) updateLogs(ctx context.Context, c <-chan []byte) {
defer func() { defer func() {
log.Debug().Msgf("updateLogs view bailing out!") log.Debug().Msgf("updateLogs view bailing out!")
}() }()
for { for {
select { select {
case line, ok := <-c: case bytes, ok := <-c:
if !ok { if !ok {
log.Debug().Msgf("Closed channel detected. Bailing out...") log.Debug().Msgf("Closed channel detected. Bailing out...")
l.Append(line) l.Append(string(bytes))
l.Notify(false) l.Notify(false)
return return
} }
l.Append(line) l.Append(string(bytes))
var overflow bool var overflow bool
l.mx.RLock() l.mx.RLock()
{ {

View File

@ -18,7 +18,7 @@ import (
func TestLogFullBuffer(t *testing.T) { func TestLogFullBuffer(t *testing.T) {
size := 4 size := 4
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(size), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()
@ -60,7 +60,7 @@ func TestLogFilter(t *testing.T) {
for k := range uu { for k := range uu {
u := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(size), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()
@ -89,7 +89,7 @@ func TestLogFilter(t *testing.T) {
} }
func TestLogStartStop(t *testing.T) { func TestLogStartStop(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()
@ -110,7 +110,7 @@ func TestLogStartStop(t *testing.T) {
} }
func TestLogClear(t *testing.T) { func TestLogClear(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
assert.Equal(t, "fred", m.GetPath()) assert.Equal(t, "fred", m.GetPath())
assert.Equal(t, "blee", m.GetContainer()) assert.Equal(t, "blee", m.GetContainer())
@ -132,7 +132,7 @@ func TestLogClear(t *testing.T) {
} }
func TestLogBasic(t *testing.T) { func TestLogBasic(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(2), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(2), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()
@ -148,7 +148,7 @@ func TestLogBasic(t *testing.T) {
} }
func TestLogAppend(t *testing.T) { func TestLogAppend(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "blah blah", makeLogOpts(4), 5*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 5*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()
@ -161,17 +161,17 @@ func TestLogAppend(t *testing.T) {
m.Append(d) m.Append(d)
} }
assert.Equal(t, 1, v.dataCalled) assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, []string{}, v.data) assert.Equal(t, []string{"blah blah"}, v.data)
m.Notify(true) m.Notify(true)
assert.Equal(t, 2, v.dataCalled) assert.Equal(t, 2, v.dataCalled)
assert.Equal(t, 1, v.clearCalled) assert.Equal(t, 0, v.clearCalled)
assert.Equal(t, 0, v.errCalled) assert.Equal(t, 0, v.errCalled)
assert.Equal(t, data, v.data) assert.Equal(t, append([]string{"blah blah"}, data...), v.data)
} }
func TestLogTimedout(t *testing.T) { func TestLogTimedout(t *testing.T) {
m := model.NewLog(client.NewGVR("fred"), "Blee", makeLogOpts(4), 10*time.Millisecond) m := model.NewLog(client.NewGVR("fred"), makeLogOpts(4), 10*time.Millisecond)
m.Init(makeFactory()) m.Init(makeFactory())
v := newTestView() v := newTestView()

View File

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

View File

@ -134,7 +134,7 @@ func (a *App) toggleHeader(flag bool) {
} }
if a.showHeader { if a.showHeader {
flex.RemoveItemAtIndex(0) flex.RemoveItemAtIndex(0)
flex.AddItemAtIndex(0, a.buildHeader(), 7, 1, false) flex.AddItemAtIndex(0, a.buildHeader(), 8, 1, false)
} else { } else {
flex.RemoveItemAtIndex(0) flex.RemoveItemAtIndex(0)
flex.AddItemAtIndex(0, a.statusIndicator(), 1, 1, false) flex.AddItemAtIndex(0, a.statusIndicator(), 1, 1, false)
@ -144,7 +144,6 @@ func (a *App) toggleHeader(flag bool) {
func (a *App) buildHeader() tview.Primitive { func (a *App) buildHeader() tview.Primitive {
header := tview.NewFlex() header := tview.NewFlex()
header.SetBackgroundColor(a.Styles.BgColor()) header.SetBackgroundColor(a.Styles.BgColor())
header.SetBorderPadding(0, 0, 1, 1)
header.SetDirection(tview.FlexColumn) header.SetDirection(tview.FlexColumn)
if !a.showHeader { if !a.showHeader {
return header return header

View File

@ -21,7 +21,7 @@ import (
const ( const (
logTitle = "logs" logTitle = "logs"
logMessage = "Waiting for logs..." logMessage = "[:orange:b]Waiting for logs...[::]"
logCoFmt = " Logs([fg:bg:]%s:[hilite:bg:b]%s[-:bg:-]) " logCoFmt = " Logs([fg:bg:]%s:[hilite:bg:b]%s[-:bg:-]) "
logFmt = " Logs([fg:bg:]%s) " logFmt = " Logs([fg:bg:]%s) "
@ -49,7 +49,7 @@ func NewLog(gvr client.GVR, path, co string, prev bool) *Log {
l := Log{ l := Log{
Flex: tview.NewFlex(), Flex: tview.NewFlex(),
cmdBuff: ui.NewCmdBuff('/', ui.FilterBuff), cmdBuff: ui.NewCmdBuff('/', ui.FilterBuff),
model: model.NewLog(gvr, logMessage, buildLogOpts(path, co, prev, tailLineCount), defaultTimeout), model: model.NewLog(gvr, buildLogOpts(path, co, prev, tailLineCount), defaultTimeout),
} }
return &l return &l
@ -66,11 +66,13 @@ func (l *Log) Init(ctx context.Context) (err error) {
l.indicator = NewLogIndicator(l.app.Config, l.app.Styles) l.indicator = NewLogIndicator(l.app.Config, l.app.Styles)
l.AddItem(l.indicator, 1, 1, false) l.AddItem(l.indicator, 1, 1, false)
l.indicator.Refresh()
l.logs = NewDetails(l.app, "", "", false) l.logs = NewDetails(l.app, "", "", false)
if err = l.logs.Init(ctx); err != nil { if err = l.logs.Init(ctx); err != nil {
return err return err
} }
l.logs.SetText(logMessage)
l.logs.SetWrap(false) l.logs.SetWrap(false)
l.logs.SetMaxBuffer(l.app.Config.K9s.LogBufferSize) l.logs.SetMaxBuffer(l.app.Config.K9s.LogBufferSize)