mine
derailed 2019-06-21 09:53:14 -06:00
parent 947af513ff
commit b723bd6a65
19 changed files with 122 additions and 88 deletions

View File

@ -79,5 +79,6 @@ func (p *Pod) Containers(ns, n string, includeInit bool) ([]string, error) {
// Logs fetch container logs for a given pod and container. // Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request { func (p *Pod) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request {
log.Debug().Msgf("Log Options %#v", *opts.TailLines)
return p.DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) return p.DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
} }

View File

@ -85,6 +85,11 @@ func (p *PortForward) Stop() {
close(p.stopChan) close(p.stopChan)
} }
// FQN returns the portforward unique id.
func (p *PortForward) FQN() string {
return p.path + ":" + p.container
}
// Start initiates a port forward session for a given pod and ports. // Start initiates a port forward session for a given pod and ports.
func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortForwarder, error) { func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortForwarder, error) {
p.path, p.container, p.ports, p.age = path, co, ports, time.Now() p.path, p.container, p.ports, p.age = path, co, ports, time.Now()

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -211,12 +210,17 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L
head := opts.NormalizeName() head := opts.NormalizeName()
scanner := bufio.NewScanner(stream) scanner := bufio.NewScanner(stream)
// count := 0
for scanner.Scan() { for scanner.Scan() {
txt := scanner.Text()
// log.Debug().Msgf("Pushing %d: %s", count, txt)
// count++
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case c <- head + strings.TrimSpace(scanner.Text()): case c <- head + txt:
default: default:
// Ensures we get back to scanning
} }
} }
} }

View File

@ -3,7 +3,6 @@ package views
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -95,7 +94,7 @@ func (v *aliasView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
func (v *aliasView) runCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *aliasView) runCmd(evt *tcell.EventKey) *tcell.EventKey {
r, _ := v.GetSelection() r, _ := v.GetSelection()
if r > 0 { if r > 0 {
v.app.gotoResource(strings.TrimSpace(v.GetCell(r, 0).Text), true) v.app.gotoResource(trimCell(v.tableView, r, 0), true)
} }
return nil return nil

View File

@ -55,25 +55,25 @@ type (
cancel context.CancelFunc cancel context.CancelFunc
informer *watch.Informer informer *watch.Informer
stopCh chan struct{} stopCh chan struct{}
forwarders []forwarder forwarders map[string]forwarder
} }
) )
// NewApp returns a K9s app instance. // NewApp returns a K9s app instance.
func NewApp(cfg *config.Config) *appView { func NewApp(cfg *config.Config) *appView {
v := appView{ v := appView{
shellView: newShellView(), shellView: newShellView(),
cmdBuff: newCmdBuff(':'), cmdBuff: newCmdBuff(':'),
forwarders: make(map[string]forwarder),
} }
v.config = cfg v.config = cfg
v.initBench(cfg.K9s.CurrentCluster) v.initBench(cfg.K9s.CurrentCluster)
v.refreshStyles() v.refreshStyles()
v.command = newCommand(&v)
v.views["menu"] = newMenuView(v.styles) v.views["menu"] = newMenuView(v.styles)
v.views["logo"] = newLogoView(v.styles) v.views["logo"] = newLogoView(v.styles)
v.views["cmd"] = newCmdView(v.styles, '🐶') v.views["cmd"] = newCmdView(v.styles, '🐶')
v.command = newCommand(&v)
v.views["flash"] = newFlashView(&v, "Initializing...") v.views["flash"] = newFlashView(&v, "Initializing...")
v.views["crumbs"] = newCrumbsView(v.styles) v.views["crumbs"] = newCrumbsView(v.styles)
v.views["clusterInfo"] = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection())) v.views["clusterInfo"] = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
@ -173,11 +173,11 @@ func (a *appView) BailOut() {
} }
func (a *appView) stopForwarders() { func (a *appView) stopForwarders() {
for _, f := range a.forwarders { for k, f := range a.forwarders {
log.Debug().Msgf("Deleting forwarder %s", f.Path()) log.Debug().Msgf("Deleting forwarder %s", f.Path())
f.Stop() f.Stop()
delete(a.forwarders, k)
} }
a.forwarders = []forwarder{}
} }
func (a *appView) conn() k8s.Connection { func (a *appView) conn() k8s.Connection {

View File

@ -109,14 +109,13 @@ func (v *benchView) getTitle() string {
} }
func (v *benchView) selChanged(r, c int) { func (v *benchView) selChanged(r, c int) {
log.Info().Msgf("Bench sel changed %d:%d", r, c)
tv := v.getTV() tv := v.getTV()
if r == 0 || tv.GetCell(r, 0) == nil { if r == 0 || tv.GetCell(r, 0) == nil {
v.selectedItem = "" v.selectedItem = ""
return return
} }
v.selectedRow = r v.selectedRow = r
v.selectedItem = strings.TrimSpace(tv.GetCell(r, 7).Text) v.selectedItem = trimCell(tv, r, 7)
} }
func (v *benchView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *benchView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -157,7 +156,7 @@ func (v *benchView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
dir := filepath.Join(K9sBenchDir, v.app.config.K9s.CurrentCluster) dir := filepath.Join(K9sBenchDir, v.app.config.K9s.CurrentCluster)
showModal(v.Pages, fmt.Sprintf("Deleting `%s are you sure?", sel), "table", func() { showModal(v.Pages, fmt.Sprintf("Delete benchmark `%s?", sel), "table", func() {
if err := os.Remove(filepath.Join(dir, sel)); err != nil { if err := os.Remove(filepath.Join(dir, sel)); err != nil {
v.app.flash().errf("Unable to delete file %s", err) v.app.flash().errf("Unable to delete file %s", err)
return return

View File

@ -3,6 +3,7 @@ package views
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strings" "strings"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
@ -55,7 +56,7 @@ func (v *containerView) selectedContainer() string {
} }
func (v *containerView) viewLogs(app *appView, _, res, sel string) { func (v *containerView) viewLogs(app *appView, _, res, sel string) {
status := strings.TrimSpace(v.masterPage().GetCell(v.selectedRow, 3).Text) status := trimCell(v.masterPage(), v.selectedRow, 3)
if status == "Running" || status == "Completed" { if status == "Running" || status == "Completed" {
v.showLogs(false) v.showLogs(false)
return return
@ -81,8 +82,19 @@ func (v *containerView) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
portC := v.masterPage().GetCell(v.selectedRow, 10) if _, ok := v.app.forwarders[fwFQN(*v.path, v.selectedItem)]; ok {
ports := strings.Split(portC.Text, ",") v.app.flash().err(fmt.Errorf("A PortForward already exist on container %s", *v.path))
return nil
}
state := trimCell(v.masterPage(), v.selectedRow, 3)
if state != "Running" {
v.app.flash().err(fmt.Errorf("Container %s is not running?", v.selectedItem))
return nil
}
portC := trimCell(v.masterPage(), v.selectedRow, 10)
ports := strings.Split(portC, ",")
if len(ports) == 0 { if len(ports) == 0 {
v.app.flash().err(errors.New("Container exposes no ports")) v.app.flash().err(errors.New("Container exposes no ports"))
return nil return nil
@ -123,20 +135,18 @@ func (v *containerView) portForward(lport, cport string) {
func (v *containerView) runForward(pf *k8s.PortForward, f *portforward.PortForwarder) { func (v *containerView) runForward(pf *k8s.PortForward, f *portforward.PortForwarder) {
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {
v.app.forwarders = append(v.app.forwarders, pf) v.app.forwarders[pf.FQN()] = pf
v.app.flash().infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0]) v.app.flash().infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0])
v.dismissModal() v.dismissModal()
}) })
pf.SetActive(true) pf.SetActive(true)
if err := f.ForwardPorts(); err == nil { if err := f.ForwardPorts(); err != nil {
v.app.flash().err(err) v.app.flash().err(err)
return return
} }
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {
if len(v.app.forwarders) > 0 { delete(v.app.forwarders, pf.FQN())
v.app.forwarders = v.app.forwarders[:len(v.app.forwarders)-1]
}
pf.SetActive(false) pf.SetActive(false)
}) })
} }

View File

@ -7,7 +7,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
@ -110,7 +109,7 @@ func (v *dumpView) selChanged(r, c int) {
return return
} }
v.selectedRow = r v.selectedRow = r
v.selectedItem = strings.TrimSpace(tv.GetCell(r, 0).Text) v.selectedItem = trimCell(tv, r, 0)
} }
func (v *dumpView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *dumpView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -118,7 +117,6 @@ func (v *dumpView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcel
tv := v.getTV() tv := v.getTV()
tv.sortCol.index, tv.sortCol.asc = tv.nameColIndex()+col, asc tv.sortCol.index, tv.sortCol.asc = tv.nameColIndex()+col, asc
tv.refresh() tv.refresh()
return nil return nil
} }
} }
@ -147,7 +145,7 @@ func (v *dumpView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
dir := filepath.Join(config.K9sDumpDir, v.app.config.K9s.CurrentCluster) dir := filepath.Join(config.K9sDumpDir, v.app.config.K9s.CurrentCluster)
showModal(v.Pages, fmt.Sprintf("Deleting `%s are you sure?", sel), "table", func() { showModal(v.Pages, fmt.Sprintf("Delete screen dump `%s?", sel), "table", func() {
if err := os.Remove(filepath.Join(dir, sel)); err != nil { if err := os.Remove(filepath.Join(dir, sel)); err != nil {
v.app.flash().errf("Unable to delete file %s", err) v.app.flash().errf("Unable to delete file %s", err)
return return

View File

@ -151,13 +151,13 @@ func (v *forwardView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
Path: "/", Path: "/",
}, },
} }
co := strings.TrimSpace(tv.GetCell(r, 2).Text) co := trimCell(tv, r, 2)
if b, ok := v.app.bench.Benchmarks.Containers[containerID(sel, co)]; ok { if b, ok := v.app.bench.Benchmarks.Containers[containerID(sel, co)]; ok {
cfg = b cfg = b
} }
cfg.Name = sel cfg.Name = sel
base := strings.TrimSpace(tv.GetCell(r, 4).Text) base := trimCell(tv, r, 4)
var err error var err error
if v.bench, err = newBenchmark(base, cfg); err != nil { if v.bench, err = newBenchmark(base, cfg); err != nil {
v.app.flash().errf("Bench failed %v", err) v.app.flash().errf("Bench failed %v", err)
@ -196,8 +196,7 @@ func (v *forwardView) getSelectedItem() string {
if r == 0 { if r == 0 {
return "" return ""
} }
return fwFQN(fqn(trimCell(tv, r, 0), trimCell(tv, r, 1)), trimCell(tv, r, 2))
return fqn(strings.TrimSpace(tv.GetCell(r, 0).Text), strings.TrimSpace(tv.GetCell(r, 1).Text))
} }
func (v *forwardView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *forwardView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
@ -206,27 +205,21 @@ func (v *forwardView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
tv.cmdBuff.reset() tv.cmdBuff.reset()
return nil return nil
} }
sel := v.getSelectedItem() sel := v.getSelectedItem()
if sel == "" { if sel == "" {
return nil return nil
} }
showModal(v.Pages, fmt.Sprintf("Deleting `%s are you sure?", sel), "table", func() { showModal(v.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), "table", func() {
index := -1 fw, ok := v.app.forwarders[sel]
for i, f := range v.app.forwarders { if !ok {
if sel == f.Path() { log.Debug().Msgf("Unable to find forwarder %s", sel)
index = i
}
}
if index == -1 {
return return
} }
v.app.forwarders[index].Stop() fw.Stop()
if index == 0 && len(v.app.forwarders) == 1 { delete(v.app.forwarders, sel)
v.app.forwarders = []forwarder{}
} else {
v.app.forwarders = append(v.app.forwarders[:index], v.app.forwarders[index+1:]...)
}
log.Debug().Msgf("PortForwards after delete: %#v", v.app.forwarders) log.Debug().Msgf("PortForwards after delete: %#v", v.app.forwarders)
v.getTV().update(v.hydrate()) v.getTV().update(v.hydrate())
v.app.flash().infof("PortForward %s deleted!", sel) v.app.flash().infof("PortForward %s deleted!", sel)

View File

@ -20,6 +20,11 @@ const (
minusSign = "↓" minusSign = "↓"
) )
// FwFQN returns a fully qualified ns/name:container id.
func fwFQN(po, co string) string {
return po + ":" + co
}
func isTCPPort(p string) bool { func isTCPPort(p string) bool {
return !strings.Contains(p, "UDP") return !strings.Contains(p, "UDP")
} }

View File

@ -18,6 +18,7 @@ import (
type ( type (
logFrame struct { logFrame struct {
*tview.Flex *tview.Flex
app *appView app *appView
actions keyActions actions keyActions
backFn actionHandler backFn actionHandler
@ -25,6 +26,7 @@ type (
logView struct { logView struct {
*logFrame *logFrame
logs *detailsView logs *detailsView
status *statusView status *statusView
ansiWriter io.Writer ansiWriter io.Writer
@ -119,12 +121,14 @@ func (v *logView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
func (v *logView) logLine(line string) { func (v *logView) logLine(line string) {
fmt.Fprintln(v.ansiWriter, tview.Escape(line)) fmt.Fprintln(v.ansiWriter, tview.Escape(line))
log.Debug().Msgf("LOG LINES %d", v.logs.GetLineCount())
} }
func (v *logView) flush(index int, buff []string) { func (v *logView) flush(index int, buff []string) {
if index == 0 { if index == 0 {
return return
} }
v.logLine(strings.Join(buff[:index], "\n")) v.logLine(strings.Join(buff[:index], "\n"))
if atomic.LoadInt32(&v.autoScroll) == 1 { if atomic.LoadInt32(&v.autoScroll) == 1 {
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {

View File

@ -12,11 +12,8 @@ import (
) )
const ( const (
maxBuff1 int64 = 200 logBuffSize = 100
refreshRate = 200 * time.Millisecond flushTimeout = 200 * time.Millisecond
maxCleanse = 100
logBuffSize = 100
flushTimeout = 200 * time.Millisecond
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) "
@ -103,10 +100,11 @@ func (v *logsView) doLoad(path, co string, prevLogs bool) error {
l.setTitle(path, co) l.setTitle(path, co)
c := make(chan string, 10) c := make(chan string, 10)
go updateLogs(c, l) go updateLogs(c, l, logBuffSize)
res, ok := v.parent.getList().Resource().(resource.Tailable) res, ok := v.parent.getList().Resource().(resource.Tailable)
if !ok { if !ok {
close(c)
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource()) return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource())
} }
@ -132,19 +130,18 @@ func (v *logsView) logOpts(path, co string, prevLogs bool) resource.LogOptions {
Lines: int64(v.app.config.K9s.LogRequestSize), Lines: int64(v.app.config.K9s.LogRequestSize),
Previous: prevLogs, Previous: prevLogs,
} }
} }
func updateLogs(c <-chan string, l *logView) {
buff, index := make([]string, logBuffSize), 0 func updateLogs(c <-chan string, l *logView, buffSize int) {
buff, index := make([]string, buffSize), 0
for { for {
select { select {
case line, ok := <-c: case line, ok := <-c:
if !ok { if !ok {
l.flush(index, buff) l.flush(index, buff)
index = 0
return return
} }
if index < logBuffSize { if index < buffSize {
buff[index] = line buff[index] = line
index++ index++
continue continue
@ -152,6 +149,7 @@ func updateLogs(c <-chan string, l *logView) {
l.flush(index, buff) l.flush(index, buff)
index = 0 index = 0
buff[index] = line buff[index] = line
index++
case <-time.After(flushTimeout): case <-time.After(flushTimeout):
l.flush(index, buff) l.flush(index, buff)
index = 0 index = 0

View File

@ -0,0 +1,30 @@
package views
import (
"fmt"
"sync"
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestUpdateLogs(t *testing.T) {
v := newLogView("test", NewApp(config.NewConfig(ks{})), nil)
var wg sync.WaitGroup
wg.Add(1)
c := make(chan string, 10)
go func() {
defer wg.Done()
updateLogs(c, v, 10)
}()
for i := 0; i < 500; i++ {
c <- fmt.Sprintf("log %d", i)
}
close(c)
wg.Wait()
assert.Equal(t, 500, v.logs.GetLineCount())
}

View File

@ -2,7 +2,6 @@ package views
import ( import (
"path" "path"
"strings"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview" "github.com/derailed/tview"
@ -107,12 +106,12 @@ func (v *masterDetail) selectItem(r, c int) {
return return
} }
col0 := cleanCell(t, r, 0) col0 := trimCell(t, r, 0)
switch v.currentNS { switch v.currentNS {
case resource.NotNamespaced: case resource.NotNamespaced:
v.selectedItem = col0 v.selectedItem = col0
case resource.AllNamespace, resource.AllNamespaces: case resource.AllNamespace, resource.AllNamespaces:
v.selectedItem = path.Join(col0, cleanCell(t, r, 1)) v.selectedItem = path.Join(col0, trimCell(t, r, 1))
default: default:
v.selectedItem = path.Join(v.currentNS, col0) v.selectedItem = path.Join(v.currentNS, col0)
} }
@ -126,7 +125,3 @@ func (v *masterDetail) defaultActions() {
v.extraActionsFn(v.actions) v.extraActionsFn(v.actions)
} }
} }
func cleanCell(v *tableView, r, c int) string {
return strings.TrimSpace(v.GetCell(r, c).Text)
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/watch"
@ -152,12 +151,7 @@ func (v *podView) viewLogs(prev bool) bool {
return false return false
} }
r := v.selectedRow status := trimCellRelative(v.masterPage(), v.selectedRow, 2)
col := 2
if v.list.AllNamespaces() {
col = 3
}
status := strings.TrimSpace(v.masterPage().GetCell(r, col).Text)
if status == "Running" || status == "Completed" { if status == "Running" || status == "Completed" {
v.showLogs(v.selectedItem, "", v, prev) v.showLogs(v.selectedItem, "", v, prev)
return true return true

View File

@ -151,7 +151,7 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
msg := fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel) msg := fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel)
showDeleteDialog(v.Pages, msg, func(cascade, force bool) { showDeleteDialog(v.Pages, msg, func(cascade, force bool) {
v.masterPage().setDeleted() v.masterPage().setDeleted()
v.app.flash().infof("Deleting %s %s", v.list.GetName(), sel) v.app.flash().infof("Delete resource %s %s", v.list.GetName(), sel)
if err := v.list.Resource().Delete(sel, cascade, force); err != nil { if err := v.list.Resource().Delete(sel, cascade, force); err != nil {
v.app.flash().errf("Delete failed with %s", err) v.app.flash().errf("Delete failed with %s", err)
} else { } else {

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"time" "time"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
@ -107,7 +106,7 @@ func (v *subjectView) selChanged(r, _ int) {
v.selectedItem = "" v.selectedItem = ""
return return
} }
v.selectedItem = strings.TrimSpace(v.GetCell(r, 0).Text) v.selectedItem = trimCell(v.tableView, r, 0)
} }
func (v *subjectView) SetSubject(s string) { func (v *subjectView) SetSubject(s string) {

View File

@ -102,19 +102,8 @@ func (v *svcView) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
func trimCell(tv *tableView, row, col int) (string, error) {
c := tv.GetCell(row, tv.nameColIndex()+col)
if c == nil {
return "", fmt.Errorf("No cell at location [%d:%d]", row, col)
}
return strings.TrimSpace(c.Text), nil
}
func (v *svcView) checkSvc(row int) error { func (v *svcView) checkSvc(row int) error {
svcType, err := trimCell(v.masterPage(), row, 1) svcType := trimCellRelative(v.masterPage(), row, 1)
if err != nil {
return err
}
if svcType != "NodePort" && svcType != "LoadBalancer" { if svcType != "NodePort" && svcType != "LoadBalancer" {
return errors.New("You must select a reachable service") return errors.New("You must select a reachable service")
} }
@ -122,10 +111,7 @@ func (v *svcView) checkSvc(row int) error {
} }
func (v *svcView) getExternalPort(row int) (string, error) { func (v *svcView) getExternalPort(row int) (string, error) {
ports, err := trimCell(v.masterPage(), row, 5) ports := trimCellRelative(v.masterPage(), row, 5)
if err != nil {
return "", err
}
pp := strings.Split(ports, " ") pp := strings.Split(ports, " ")
if len(pp) == 0 { if len(pp) == 0 {

View File

@ -12,6 +12,7 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/rs/zerolog/log"
) )
const ( const (
@ -33,6 +34,19 @@ var (
type cleanseFn func(string) string type cleanseFn func(string) string
func trimCellRelative(tv *tableView, row, col int) string {
return trimCell(tv, row, tv.nameColIndex()+col)
}
func trimCell(tv *tableView, row, col int) string {
c := tv.GetCell(row, col)
if c == nil {
log.Error().Err(fmt.Errorf("No cell at location [%d:%d]", row, col)).Msg("Trim cell failed!")
return ""
}
return strings.TrimSpace(c.Text)
}
func saveTable(cluster, name string, data resource.TableData) (string, error) { func saveTable(cluster, name string, data resource.TableData) (string, error) {
dir := filepath.Join(config.K9sDumpDir, cluster) dir := filepath.Join(config.K9sDumpDir, cluster)
if err := ensureDir(dir); err != nil { if err := ensureDir(dir); err != nil {