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
.idea
k9s.log
.envrc
cov.out
execs
@ -10,10 +9,8 @@ dist
notes
vendor
go.mod1
popeye1.go
gen.sh
cluster_info_test.go
*.test
*.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!
```yaml
# config.yml
k9s:
# Indicates api-server poll intervals.
# Represents ui poll intervals.
refreshRate: 2
# Indicates whether modification commands like delete/kill/edit are disabled. Default is false
readOnly: false
@ -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
# $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
K9s allows you to define your own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate available plugins. A plugin is defined as follows:
K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$HOME/.k9s/plugin.yml` to locate all available plugins. A plugin is defined as follows:
```yaml
# $HOME/.k9s/plugin.yml
@ -217,7 +252,7 @@ plugin:
description: Pod logs
scopes:
- po
command: /usr/local/bin/kubectl
command: kubectl
background: false
args:
- logs
@ -237,6 +272,8 @@ K9s does provide additional environment variables for you to customize your plug
* `$NAMESPACE` -- the selected resource namespace
* `$NAME` -- the selected resource name
* `$CONTAINER` -- the current container if applicable
* `$FILTER` -- the current filter if any
* `$KUBECONFIG` -- the KubeConfig location.
* `$CLUSTER` the active cluster name
* `$CONTEXT` the active context name
@ -244,13 +281,13 @@ K9s does provide additional environment variables for you to customize your plug
* `$GROUPS` the active groups
* `$COLX` the column at index X for the viewed resource
NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies.
> NOTE: This is an experimental feature! Options and layout may change in future K9s releases as this feature solidifies.
---
## Benchmarking
## Benchmark Your Applications
K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll) of Google fame. Hey is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!).
K9s integrates [Hey](https://github.com/rakyll/hey) from the brilliant and super talented [Jaana Dogan](https://github.com/rakyll). `Hey` is a CLI tool to benchmark HTTP endpoints similar to AB bench. This preliminary feature currently supports benchmarking port-forwards and services (Read the paint on this is way fresh!).
To setup a port-forward, you will need to navigate to the PodView, select a pod and a container that exposes a given port. Using `SHIFT-F` a dialog comes up to allow you to specify a local port to forward. Once acknowledged, you can navigate to the PortForward view (alias `pf`) listing out your active port-forwards. Selecting a port-forward and using `CTRL-B` will run a benchmark on that HTTP endpoint. To view the results of your benchmark runs, go to the Benchmarks view (alias `be`). You should now be able to select a benchmark and view the run stats details by pressing `<ENTER>`. NOTE: Port-forwards only last for the duration of the K9s session and will be terminated upon exit.
@ -272,11 +309,11 @@ benchmarks:
defaults:
# One concurrent connection
concurrency: 1
# 500 requests will be sent to an endpoint
# Number of requests that will be sent to an endpoint
requests: 500
containers:
# Containers section allows you to configure your http container's endpoints and benchmarking settings.
# NOTE: the container ID syntax uses namespace/pod_name:container_name
# NOTE: the container ID syntax uses namespace/pod-name:container-name
default/nginx:nginx:
# Benchmark a container named nginx using POST HTTP verb using http://localhost:port/bozo URL and headers.
concurrency: 1
@ -295,15 +332,15 @@ benchmarks:
# Similary you can Benchmark an HTTP service exposed either via nodeport, loadbalancer types.
# Service ID is ns/svc-name
default/nginx:
# Hit the service with 5 concurrent sessions
# Set the concurrency level
concurrency: 5
# Issues a total of 500 requests
# Number of requests to be sent
requests: 500
http:
method: GET
# This setting will depend on whether service is nodeport or loadbalancer. Nodeport may require vendor port tuneling setting.
# Set this to a node if nodeport or LB if applicable. IP or dns name.
host: 10.11.13.14
host: A.B.C.D
path: /bumblebeetuna
auth:
user: jean-baptiste-emmanuel
@ -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
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!
```yaml
# InTheNavy Skin...
# Skin InTheNavy...
k9s:
# General K9s styles
body:
@ -589,8 +591,8 @@ to make this project a reality!
## Meet The Core Team!
* [Fernand Galiana](https://github.com/derailed)
* <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io
* <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
* <img src="assets/mail.png" width="16" height="auto"/> fernand@imhotep.io
* <img src="assets/twitter.png" width="16" height="auto"/> [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
---

5
go.mod
View File

@ -32,6 +32,7 @@ require (
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/atotto/clipboard v0.1.2
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/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/fatih/color v1.6.0
@ -41,9 +42,13 @@ require (
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.5
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec // 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/rakyll/hey v0.1.2
github.com/rs/zerolog v1.17.2
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0

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/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo=
github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs=
@ -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/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@ -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/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
@ -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/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec h1:S6wtb5ie7KeMcuEaESj0RoSmpyGfvOSuunmKEdX7wg8=
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14=
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c h1:9RGaDpUySgRscx5oiagwUtm9vBZti/4+QYq2GM4FegE=
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c/go.mod h1:u/KO+e43wkagC0lqM1eaqNEWEBdg08Q1ugP/idj39MM=
github.com/openfaas/faas-provider v0.15.0 h1:3x5ma90FL7AqP4NOD6f03AY24y3xBeVF6xGLUx6Xrlc=
github.com/openfaas/faas-provider v0.15.0/go.mod h1:8Fagi2UeMfL+gZAqZWSMQg86i+w1+hBOKtwKRP5sLFI=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -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 v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -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.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8=

View File

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

View File

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

View File

@ -79,7 +79,7 @@ func (d *Deployment) Restart(path string) error {
}
// 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())
if err != nil {
return err

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/derailed/k9s/internal"
"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.
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())
if err != nil {
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)
}
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)
if !ok {
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)
oo, err := f.List("v1/pods", ns, true, lsel)
oo, err := f.List("v1/pods", ns, false, lsel)
if err != nil {
return err
}

View File

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

View File

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

View File

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

View File

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

View File

@ -80,7 +80,7 @@ func (s *StatefulSet) Restart(path string) error {
}
// 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())
if err != nil {
return err

View File

@ -22,7 +22,7 @@ type Service struct {
}
// TailLogs tail logs for all pods represented by this Service.
func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
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())
if err != nil {
return err

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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