From eebe9e78bb9b40dc887126254a66f107043ba00c Mon Sep 17 00:00:00 2001 From: Pavel Tumik Date: Tue, 12 May 2020 14:54:56 -0700 Subject: [PATCH 1/8] color cpu and memory limit fields in different color if close to threshold --- internal/view/container.go | 5 +++++ internal/view/helpers.go | 38 ++++++++++++++++++++++++++++++++++++++ internal/view/pod.go | 5 +++++ 3 files changed, 48 insertions(+) diff --git a/internal/view/container.go b/internal/view/container.go index 2f2993cb..28178723 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -27,11 +27,16 @@ func NewContainer(gvr client.GVR) ResourceViewer { c.SetEnvFn(c.k9sEnv) c.GetTable().SetEnterFn(c.viewLogs) c.GetTable().SetColorerFn(render.Container{}.ColorerFunc()) + c.GetTable().SetDecorateFn(c.decorateRows) c.SetBindKeysFn(c.bindKeys) return &c } +func (c *Container) decorateRows(data render.TableData) render.TableData { + return decorateCpuMemHeaderRows(c.App(), data) +} + // Name returns the component name. func (c *Container) Name() string { return containerTitle } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 2ec5223c..96522367 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "github.com/derailed/k9s/internal" @@ -177,3 +178,40 @@ func fqn(ns, n string) string { } return ns + "/" + n } + +func decorateCpuMemHeaderRows(app *App, data render.TableData) render.TableData { + for colIndex, header := range data.Header { + check := "" + if header.Name == "%CPU/L" { + check = "cpu" + } + if header.Name == "%MEM/L" { + check = "memory" + } + if len(check) == 0 { + continue + } + for _, re := range data.RowEvents { + if re.Row.Fields[colIndex] == render.NAValue { + continue + } + n, err := strconv.Atoi(re.Row.Fields[colIndex]) + if err != nil { + continue + } + if n > 100 { + n = 100 + } + severity := app.Config.K9s.Thresholds.LevelFor(check, n) + if severity == config.SeverityLow { + continue + } + color := app.Config.K9s.Thresholds.SeverityColor(check, n) + if len(color) > 0 { + re.Row.Fields[colIndex] = "[" + color + "]" + re.Row.Fields[colIndex] + } + } + } + + return data +} diff --git a/internal/view/pod.go b/internal/view/pod.go index 90aa4cb4..b0cf1ae0 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -34,10 +34,15 @@ func NewPod(gvr client.GVR) ResourceViewer { p.SetBindKeysFn(p.bindKeys) p.GetTable().SetEnterFn(p.showContainers) p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) + p.GetTable().SetDecorateFn(p.decorateRows) return &p } +func (p *Pod) decorateRows(data render.TableData) render.TableData { + return decorateCpuMemHeaderRows(p.App(), data) +} + func (p *Pod) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ tcell.KeyCtrlK: ui.NewKeyAction("Kill", p.killCmd, true), From 7ece29f3420862b05799af89793fb2ff2980e324 Mon Sep 17 00:00:00 2001 From: Pavel Tumik Date: Thu, 28 May 2020 16:19:08 -0700 Subject: [PATCH 2/8] fix color formatting --- internal/view/helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 96522367..e3d68206 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -208,7 +208,7 @@ func decorateCpuMemHeaderRows(app *App, data render.TableData) render.TableData } color := app.Config.K9s.Thresholds.SeverityColor(check, n) if len(color) > 0 { - re.Row.Fields[colIndex] = "[" + color + "]" + re.Row.Fields[colIndex] + re.Row.Fields[colIndex] = "[" + color + "::b]" + re.Row.Fields[colIndex] } } } From 0fc2bb13a789df338845ea3beefb287e624c1a1a Mon Sep 17 00:00:00 2001 From: groselt Date: Sat, 30 May 2020 17:04:36 +0100 Subject: [PATCH 3/8] Sanitize log filename before saving Remove invalid path characters --- internal/view/log.go | 13 ++++++++++++- internal/view/log_test.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/internal/view/log.go b/internal/view/log.go index a06e040c..07f43029 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "regexp" "strings" "time" @@ -29,6 +30,9 @@ const ( flushTimeout = 50 * time.Millisecond ) +// InvalidCharsRX contains invalid filename characters. +var invalidPathCharsRX = regexp.MustCompile(`[:/\\]+`) + // Log represents a generic log viewer. type Log struct { *tview.Flex @@ -284,12 +288,19 @@ func (l *Log) SaveCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +// SanitizeFilename removes characters not allowed by OS +func SanitizeFilename(name string) string { + processedString := invalidPathCharsRX.ReplaceAllString(name, "-") + + return processedString +} + func ensureDir(dir string) error { return os.MkdirAll(dir, 0744) } func saveData(cluster, name, data string) (string, error) { - dir := filepath.Join(config.K9sDumpDir, cluster) + dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } diff --git a/internal/view/log_test.go b/internal/view/log_test.go index 049636a3..dd47b30e 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -43,6 +43,23 @@ func TestLogViewSave(t *testing.T) { assert.Equal(t, len(c2), len(c1)+1) } +func TestSanitizedFilename(t *testing.T) { + uu := []struct { + name string + expected string + }{ + {"alpha", "alpha"}, + {"123", "123"}, + {"with/slash", "with-slash"}, + {"with:colon", "with-colon"}, + {":many:invalid\\characters\\", "-many-invalid-characters-"}, + } + + for _, u := range uu { + assert.Equal(t, u.expected, view.SanitizeFilename(u.name)) + } +} + // ---------------------------------------------------------------------------- // Helpers... From 7d2eb83f1578283ec7ccd0638540eab10b489d1e Mon Sep 17 00:00:00 2001 From: groselt Date: Sat, 30 May 2020 18:09:41 +0100 Subject: [PATCH 4/8] Unify sanitizing of filenames --- internal/view/log.go | 2 +- internal/view/table_helper.go | 4 ++-- internal/view/yaml.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/view/log.go b/internal/view/log.go index 07f43029..23b08a77 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -306,7 +306,7 @@ func saveData(cluster, name, data string) (string, error) { } now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.log", strings.Replace(name, "/", "-", -1), now) + fName := fmt.Sprintf("%s-%d.log", SanitizeFilename(name), now) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 64d86469..6348966f 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -18,12 +18,12 @@ import ( func computeFilename(cluster, ns, title, path string) (string, error) { now := time.Now().UnixNano() - dir := filepath.Join(config.K9sDumpDir, cluster) + dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } - name := title + "-" + strings.Replace(path, "/", "-", -1) + name := title + "-" + SanitizeFilename(path) if path == "" { name = title } diff --git a/internal/view/yaml.go b/internal/view/yaml.go index 90ff4258..2c00a9c3 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -61,13 +61,13 @@ func enableRegion(str string) string { } func saveYAML(cluster, name, data string) (string, error) { - dir := filepath.Join(config.K9sDumpDir, cluster) + dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.yml", strings.Replace(name, "/", "-", -1), now) + fName := fmt.Sprintf("%s-%d.yml", SanitizeFilename(name), now) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY From 6e5afd6d56d05de5b96b693cdf422f6d06adf4dd Mon Sep 17 00:00:00 2001 From: groselt Date: Sat, 30 May 2020 18:17:05 +0100 Subject: [PATCH 5/8] Make sanitizeFilename private --- internal/view/log.go | 7 +++---- internal/view/log_test.go | 17 ----------------- internal/view/table_helper.go | 4 ++-- internal/view/yaml.go | 4 ++-- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/internal/view/log.go b/internal/view/log.go index 23b08a77..47385d64 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -288,8 +288,7 @@ func (l *Log) SaveCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -// SanitizeFilename removes characters not allowed by OS -func SanitizeFilename(name string) string { +func sanitizeFilename(name string) string { processedString := invalidPathCharsRX.ReplaceAllString(name, "-") return processedString @@ -300,13 +299,13 @@ func ensureDir(dir string) error { } func saveData(cluster, name, data string) (string, error) { - dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) + dir := filepath.Join(config.K9sDumpDir, sanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.log", SanitizeFilename(name), now) + fName := fmt.Sprintf("%s-%d.log", sanitizeFilename(name), now) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY diff --git a/internal/view/log_test.go b/internal/view/log_test.go index dd47b30e..049636a3 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -43,23 +43,6 @@ func TestLogViewSave(t *testing.T) { assert.Equal(t, len(c2), len(c1)+1) } -func TestSanitizedFilename(t *testing.T) { - uu := []struct { - name string - expected string - }{ - {"alpha", "alpha"}, - {"123", "123"}, - {"with/slash", "with-slash"}, - {"with:colon", "with-colon"}, - {":many:invalid\\characters\\", "-many-invalid-characters-"}, - } - - for _, u := range uu { - assert.Equal(t, u.expected, view.SanitizeFilename(u.name)) - } -} - // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 6348966f..8e18c48e 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -18,12 +18,12 @@ import ( func computeFilename(cluster, ns, title, path string) (string, error) { now := time.Now().UnixNano() - dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) + dir := filepath.Join(config.K9sDumpDir, sanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } - name := title + "-" + SanitizeFilename(path) + name := title + "-" + sanitizeFilename(path) if path == "" { name = title } diff --git a/internal/view/yaml.go b/internal/view/yaml.go index 2c00a9c3..6831490d 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -61,13 +61,13 @@ func enableRegion(str string) string { } func saveYAML(cluster, name, data string) (string, error) { - dir := filepath.Join(config.K9sDumpDir, SanitizeFilename(cluster)) + dir := filepath.Join(config.K9sDumpDir, sanitizeFilename(cluster)) if err := ensureDir(dir); err != nil { return "", err } now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.yml", SanitizeFilename(name), now) + fName := fmt.Sprintf("%s-%d.yml", sanitizeFilename(name), now) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY From 79c43dc478ed9eb4d8aa5ef6a8d7d999072dda7d Mon Sep 17 00:00:00 2001 From: Jeroen Lanckmans Date: Tue, 16 Jun 2020 20:25:17 +0200 Subject: [PATCH 6/8] Add plugin example to list all resources in a namespace --- plugins/README.md | 13 +++++++------ plugins/get-all.yml | 13 +++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 plugins/get-all.yml diff --git a/plugins/README.md b/plugins/README.md index af9331bc..7ee0f973 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -2,9 +2,10 @@ K9s plugins extend the tool to provide additonal functionality via actions to further help you observe or administer your Kubernetes clusters. -| Plugin-Name | Description | Available on Views | Shortcut | Kubectl plugin, external dependencies | -|-----------------|--------------------------------|--------------------|----------|-------------------------------------------| -| log_stern.yml | View resource logs using stern | pods | Ctrl-l | | -| log_jq.yml | View resource logs using jq | pods | Ctrl-j | kubetcl-plugins/kubectl-jq | -| job_suspend.yml | Suspends a running cronjob | cronjobs | Ctrl-s | | -| dive.yml | Dive image layers | containers | d | [Dive](https://github.com/wagoodman/dive) | +| Plugin-Name | Description | Available on Views | Shortcut | Kubectl plugin, external dependencies | +|-----------------|----------------------------------|--------------------|----------|---------------------------------------------------------------------------------------| +| log_stern.yml | View resource logs using stern | pods | Ctrl-l | | +| log_jq.yml | View resource logs using jq | pods | Ctrl-j | kubetcl-plugins/kubectl-jq | +| job_suspend.yml | Suspends a running cronjob | cronjobs | Ctrl-s | | +| dive.yml | Dive image layers | containers | d | [Dive](https://github.com/wagoodman/dive) | +| get-all.yml | get all resources in a namespace | all | g | [Krew](https://krew.sigs.k8s.io/), [ketall](https://github.com/corneliusweig/ketall/) | diff --git a/plugins/get-all.yml b/plugins/get-all.yml new file mode 100644 index 00000000..20001d0e --- /dev/null +++ b/plugins/get-all.yml @@ -0,0 +1,13 @@ +plugin: + #get all resources in a namespace using the krew get-all plugin + get-all: + shortCut: g + confirm: false + description: get-all + scopes: + - all + command: sh + background: false + args: + - -c + - "kubectl get-all -n $NAMESPACE | less" \ No newline at end of file From 624c6427d2dce41001310561c4755f9a11e5c85d Mon Sep 17 00:00:00 2001 From: Antoine Meausoone Date: Tue, 30 Jun 2020 22:09:02 +0200 Subject: [PATCH 7/8] feat(log): add copy to clipboard link in log view Closes derailed/k9s#395 --- internal/view/log.go | 12 +++++++++++- internal/view/log_int_test.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/view/log.go b/internal/view/log.go index 8d8238ac..c6b2636b 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/atotto/clipboard" "io" "os" "path/filepath" @@ -184,13 +185,14 @@ func (l *Log) bindKeys() { ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true), ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true), tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false), - ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true), + ui.KeyL: ui.NewKeyAction("Clear", l.clearCmd, true), ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true), ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true), ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true), ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true), ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.toggleTextWrapCmd, true), tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true), + ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, true), }) } @@ -288,6 +290,14 @@ func (l *Log) SaveCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +func (l *Log) cpCmd(evt *tcell.EventKey) *tcell.EventKey { + l.app.Flash().Info("Content copied to clipboard...") + if err := clipboard.WriteAll(l.logs.GetText(true)); err != nil { + l.app.Flash().Err(err) + } + return nil +} + func sanitizeFilename(name string) string { processedString := invalidPathCharsRX.ReplaceAllString(name, "-") diff --git a/internal/view/log_int_test.go b/internal/view/log_int_test.go index 80cef2f6..1c89ce69 100644 --- a/internal/view/log_int_test.go +++ b/internal/view/log_int_test.go @@ -16,7 +16,7 @@ func TestLogAutoScroll(t *testing.T) { v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")}) v.GetModel().Notify() - assert.Equal(t, 14, len(v.Hints())) + assert.Equal(t, 15, len(v.Hints())) v.toggleAutoScrollCmd(nil) assert.Equal(t, "Autoscroll: Off FullScreen: Off Timestamps: Off Wrap: Off", v.Indicator().GetText(true)) From fae083b90bc5d0b8bebfac5c38937fe0277963d9 Mon Sep 17 00:00:00 2001 From: Antoine Meausoone Date: Thu, 2 Jul 2020 21:27:26 +0200 Subject: [PATCH 8/8] feat(log): use better key --- internal/view/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/view/log.go b/internal/view/log.go index c6b2636b..9218a383 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -185,7 +185,7 @@ func (l *Log) bindKeys() { ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true), ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true), tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false), - ui.KeyL: ui.NewKeyAction("Clear", l.clearCmd, true), + tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true), ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true), ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true), ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),