fix con issue + #741 #740

mine
derailed 2020-05-29 09:52:59 -06:00
parent 67cdba1b30
commit 3a02001695
17 changed files with 226 additions and 89 deletions

View File

@ -136,6 +136,7 @@ func loadConfiguration() (*config.Config, error) {
return nil, err
}
conn, err := client.InitConnection(k8sCfg)
k9sCfg.SetConnection(conn)
if err != nil {
log.Error().Err(err).Msgf("failed to connect to cluster")
} else {
@ -143,12 +144,14 @@ func loadConfiguration() (*config.Config, error) {
if !k9sCfg.GetConnection().CheckConnectivity() {
log.Panic().Msgf("K9s can't connect to cluster")
}
if !k9sCfg.GetConnection().ConnectionOK() {
panic("No connectivity")
}
log.Info().Msg("✅ Kubernetes connectivity")
if err := k9sCfg.Save(); err != nil {
log.Error().Err(err).Msg("Config save")
}
}
k9sCfg.SetConnection(conn)
return k9sCfg, nil
}

View File

@ -28,7 +28,7 @@ const (
cacheExpiry = 5 * time.Minute
cacheMXKey = "metrics"
cacheMXAPIKey = "metricsAPI"
checkConnTimeout = 5 * time.Second
checkConnTimeout = 3 * time.Second
// CallTimeout represents default api call timeout.
CallTimeout = 5 * time.Second
@ -64,9 +64,10 @@ func InitConnection(config *Config) (*APIClient, error) {
config: config,
cache: cache.NewLRUExpireCache(cacheSize),
}
a.connOK = true
_, err := a.supportsMetricsResources()
if err == nil {
a.connOK = true
if err != nil {
a.connOK = false
}
return &a, err
@ -136,7 +137,7 @@ func (a *APIClient) clearCache() {
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
log.Debug().Msgf("Check Access %q::%q", ns, gvr)
if !a.connOK {
return false, errors.New("no API server connection")
return false, errors.New("ACCESS -- No API server connection")
}
if IsClusterWide(ns) {
ns = AllNamespaces
@ -148,6 +149,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
}
}
log.Debug().Msgf("----> Calling API")
dial, err := a.Dial()
if err != nil {
return false, err
@ -229,12 +231,13 @@ func (a *APIClient) CheckConnectivity() (status bool) {
if err != nil {
return false
}
cfg.Timeout = checkConnTimeout
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
log.Error().Err(err).Msgf("Unable to connect to api server")
return
}
log.Debug().Msgf("Checking APIServer on %#v", cfg.Host)
log.Debug().Msgf("CONN-CHECK on %#v", cfg.Host)
// Check connection
if _, err := client.ServerVersion(); err == nil {

View File

@ -6,7 +6,6 @@ import (
"strings"
"sync"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
@ -299,8 +298,6 @@ func (c *Config) RESTConfig() (*restclient.Config, error) {
}
c.restConfig.QPS = defaultQPS
c.restConfig.Burst = defaultBurst
c.restConfig.Timeout = checkConnTimeout
log.Debug().Msgf("Connecting to API Server %s", c.restConfig.Host)
return c.restConfig, nil
}

View File

@ -4,8 +4,10 @@ import (
"fmt"
)
// ColorFmt colorize a string with ansi colors.
const ColorFmt = "\x1b[%dm%s\x1b[0m"
const (
colorFmt = "\x1b[%dm%s\x1b[0m"
ansiColorFmt = "\033[38;5;%dm%s\033[0m"
)
// Paint describes a terminal color.
type Paint int
@ -30,5 +32,29 @@ func Colorize(s string, c Paint) string {
if c == 0 {
return s
}
return fmt.Sprintf(ColorFmt, c, s)
return fmt.Sprintf(colorFmt, c, s)
}
// AnsiColorize colors a string.
func AnsiColorize(s string, c int) string {
return fmt.Sprintf(ansiColorFmt, c, s)
}
// Highlight colorize bytes at given indices.
func Highlight(bb []byte, ii []int, c int) []byte {
b := make([]byte, 0, len(bb))
for i, j := 0, 0; i < len(bb); i++ {
if j < len(ii) && ii[j] == i {
b = append(b, colorizeByte(bb[i], 209)...)
j++
} else {
b = append(b, bb[i])
}
}
return b
}
func colorizeByte(b byte, color int) []byte {
return []byte(AnsiColorize(string(b), color))
}

View File

@ -25,3 +25,26 @@ func TestColorize(t *testing.T) {
})
}
}
func TestHighlight(t *testing.T) {
uu := map[string]struct {
text []byte
indices []int
color int
e string
}{
"white": {
text: []byte("the brown fox"),
color: 209,
indices: []int{4, 5, 6, 7, 8},
e: "the \x1b[38;5;209mb\x1b[0m\x1b[38;5;209mr\x1b[0m\x1b[38;5;209mo\x1b[0m\x1b[38;5;209mw\x1b[0m\x1b[38;5;209mn\x1b[0m fox",
},
}
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, string(color.Highlight([]byte(u.text), u.indices, u.color)))
})
}
}

View File

@ -8,6 +8,7 @@ import (
"strings"
"time"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"
"github.com/sahilm/fuzzy"
@ -51,6 +52,18 @@ func (l *LogItem) ID() string {
return l.Container
}
func (l *LogItem) Clone() *LogItem {
bytes := make([]byte, len(l.Bytes))
copy(bytes, l.Bytes)
return &LogItem{
Container: l.Container,
Pod: l.Pod,
Timestamp: l.Timestamp,
SingleContainer: l.SingleContainer,
Bytes: bytes,
}
}
// Info returns pod and container information.
func (l *LogItem) Info() string {
return fmt.Sprintf("%q::%q", l.Pod, l.Container)
@ -61,26 +74,19 @@ func (l *LogItem) IsEmpty() bool {
return len(l.Bytes) == 0
}
const colorFmt = "\033[38;5;%dm%s\033[0m"
// colorize me
func colorize(s string, c int) string {
return fmt.Sprintf(colorFmt, c, s)
}
// Render returns a log line as string.
func (l *LogItem) Render(c int, showTime bool) []byte {
bb := make([]byte, 0, 30+len(l.Bytes)+len(l.Info()))
if showTime {
bb = append(bb, colorize(fmt.Sprintf("%-30s ", l.Timestamp), 106)...)
bb = append(bb, color.AnsiColorize(fmt.Sprintf("%-30s ", l.Timestamp), 106)...)
}
if l.Pod != "" {
bb = append(bb, []byte(colorize(l.Pod, c))...)
bb = append(bb, []byte(color.AnsiColorize(l.Pod, c))...)
bb = append(bb, ':')
}
if !l.SingleContainer && l.Container != "" {
bb = append(bb, []byte(colorize(l.Container, c))...)
bb = append(bb, []byte(color.AnsiColorize(l.Container, c))...)
bb = append(bb, ' ')
}
bb = append(bb, []byte(tview.Escape(string(l.Bytes)))...)
@ -139,45 +145,54 @@ func (l LogItems) DumpDebug(m string) {
}
// Filter filters out log items based on given filter.
func (l LogItems) Filter(q string) ([]int, error) {
func (l LogItems) Filter(q string) ([]int, [][]int, error) {
if q == "" {
return nil, nil
return nil, nil, nil
}
if IsFuzzySelector(q) {
return l.fuzzyFilter(strings.TrimSpace(q[2:])), nil
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]))
return mm, ii, nil
}
indexes, err := l.filterLogs(q)
matches, indices, err := l.filterLogs(q)
if err != nil {
log.Error().Err(err).Msgf("Logs filter failed")
return nil, err
return nil, nil, err
}
return indexes, nil
return matches, indices, nil
}
var fuzzyRx = regexp.MustCompile(`\A\-f`)
func (l LogItems) fuzzyFilter(q string) []int {
func (l LogItems) fuzzyFilter(q string) ([]int, [][]int) {
q = strings.TrimSpace(q)
matches := make([]int, 0, len(l))
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
mm := fuzzy.Find(q, l.Lines())
for _, m := range mm {
matches = append(matches, m.Index)
indices = append(indices, m.MatchedIndexes)
}
return matches
return matches, indices
}
func (l LogItems) filterLogs(q string) ([]int, error) {
func (l LogItems) filterLogs(q string) ([]int, [][]int, error) {
rx, err := regexp.Compile(`(?i)` + q)
if err != nil {
return nil, err
return nil, nil, err
}
matches := make([]int, 0, len(l))
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
for i, line := range l.Lines() {
if rx.MatchString(line) {
if locs := rx.FindStringIndex(line); locs != nil {
matches = append(matches, i)
ii := make([]int, 0, 10)
for i := 0; i < len(locs); i += 2 {
for j := locs[i]; j < locs[i+1]; j++ {
ii = append(ii, j)
}
}
indices = append(indices, ii)
}
}
return matches, nil
return matches, indices, nil
}

View File

@ -70,7 +70,7 @@ func TestLogItemsFilter(t *testing.T) {
for _, i := range ii {
i.Pod, i.Container = n, u.opts.Container
}
res, err := ii.Filter(u.q)
res, _, err := ii.Filter(u.q)
assert.Equal(t, u.err, err)
if err == nil {
assert.Equal(t, u.e, res)

View File

@ -275,7 +275,7 @@ func loadRBAC(m ResourceMetas) {
func loadPreferred(f Factory, m ResourceMetas) error {
if !f.Client().ConnectionOK() {
log.Error().Msgf("no API server connection")
log.Error().Msgf("PreferredRES - No API server connection")
return nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log"
@ -277,21 +278,24 @@ func applyFilter(q string, lines dao.LogItems) (dao.LogItems, error) {
if q == "" {
return lines, nil
}
indexes, err := lines.Filter(q)
matches, indices, err := lines.Filter(q)
if err != nil {
return nil, err
}
// No filter!
if indexes == nil {
if matches == nil {
return lines, nil
}
// Blank filter
if len(indexes) == 0 {
if len(matches) == 0 {
return nil, nil
}
filtered := make(dao.LogItems, 0, len(indexes))
for _, idx := range indexes {
filtered = append(filtered, lines[idx])
filtered := make(dao.LogItems, 0, len(matches))
for i, idx := range matches {
item := lines[idx].Clone()
item.Bytes = color.Highlight(item.Bytes, indices[i], 209)
filtered = append(filtered, item)
}
return filtered, nil

View File

@ -195,7 +195,8 @@ func TestLogTimedout(t *testing.T) {
assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 2, v.clearCalled)
assert.Equal(t, 0, v.errCalled)
assert.Equal(t, dao.LogItems{data[0]}, v.data)
const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m"
assert.Equal(t, e, string(v.data[0].Bytes))
}
// ----------------------------------------------------------------------------

View File

@ -3,6 +3,7 @@ package ui
import (
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// SelectTable represents a table with selections.
@ -51,6 +52,17 @@ func (s *SelectTable) GetSelectedItems() []string {
return items
}
// GetRowID returns the row id at at given location.
func (s *SelectTable) GetRowID(index int) (string, bool) {
cell := s.GetCell(index, 0)
if cell == nil {
return "", false
}
id, ok := cell.GetReference().(string)
return id, ok
}
// GetSelectedItem returns the currently selected item name.
func (s *SelectTable) GetSelectedItem() string {
if s.GetSelectedRowIndex() == 0 || s.model.Empty() {
@ -138,6 +150,68 @@ func (s *SelectTable) ToggleMark() {
)
}
// ToggleSpanMark toggles marked row
func (s *SelectTable) SpanMark() {
selIndex, prev := s.GetSelectedRowIndex(), -1
if selIndex <= 0 {
return
}
// Look back to find previous mark
for i := selIndex - 1; i > 0; i-- {
id, ok := s.GetRowID(i)
if !ok {
break
}
if _, ok := s.marks[id]; ok {
prev = i
break
}
}
if prev != -1 {
s.markRange(prev, selIndex)
return
}
// Look forward to see if we have a mark
for i := selIndex; i < s.GetRowCount(); i++ {
id, ok := s.GetRowID(i)
if !ok {
break
}
if _, ok := s.marks[id]; ok {
prev = i
break
}
}
s.markRange(prev, selIndex)
}
func (s *SelectTable) markRange(prev, curr int) {
if prev < 0 {
return
}
if prev > curr {
prev, curr = curr, prev
}
log.Debug().Msgf("Span Range %d::%d", prev, curr)
for i := prev + 1; i <= curr; i++ {
id, ok := s.GetRowID(i)
if !ok {
break
}
s.marks[id] = struct{}{}
cell := s.GetCell(s.GetSelectedRowIndex(), 0)
if cell == nil {
break
}
s.SetSelectedStyle(
tcell.ColorBlack,
cell.Color,
tcell.AttrBold,
)
}
}
// IsMarked returns true if this item was marked.
func (s *Table) IsMarked(item string) bool {
_, ok := s.marks[item]

View File

@ -399,13 +399,9 @@ func (b *Browser) defaultContext() context.Context {
if b.Path != "" {
ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
}
// BOZO!!
// ctx = context.WithValue(ctx, internal.KeyLabels, "")
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText()))
}
// BOZO!!
// ctx = context.WithValue(ctx, internal.KeyFields, "")
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace()))
return ctx

View File

@ -39,6 +39,7 @@ func (c *Command) Init() error {
c.alias = dao.NewAlias(c.app.factory)
if _, err := c.alias.Ensure(); err != nil {
log.Error().Err(err).Msgf("command init failed!")
return err
}
customViewers = loadCustomViewers()
@ -106,21 +107,6 @@ func (c *Command) xrayCmd(cmd string) error {
return c.exec(cmd, "xrays", x, true)
}
// BOZO!!
// func (c *Command) checkAccess(gvr string) error {
// m, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
// if err != nil {
// return err
// }
// ns := client.CleanseNamespace(c.app.Config.ActiveNamespace())
// if dao.IsK8sMeta(m) && c.app.ConOK() {
// if _, e := c.app.factory.CanForResource(ns, gvr, client.MonitorAccess); e != nil {
// return e
// }
// }
// return nil
// }
// Exec the Command by showing associated display.
func (c *Command) run(cmd, path string, clearStack bool) error {
if c.specialCmd(cmd, path) {
@ -131,9 +117,6 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
if err != nil {
return err
}
//if err := c.checkAccess(gvr); err != nil {
// return err
//}
switch cmds[0] {
case "ctx", "context", "contexts":
@ -159,6 +142,7 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
func (c *Command) defaultCmd() error {
if !c.app.Conn().ConnectionOK() {
log.Debug().Msgf("YO!!")
return c.run("ctx", "", true)
}
view := c.app.Config.ActiveView()

View File

@ -220,11 +220,11 @@ func (h *Help) showGeneral() model.MenuHints {
},
{
Mnemonic: "tab",
Description: "Next Field",
Description: "Field Next",
},
{
Mnemonic: "backtab",
Description: "Previous Field",
Description: "Field Previous",
},
{
Mnemonic: "Ctrl-r",
@ -232,7 +232,7 @@ func (h *Help) showGeneral() model.MenuHints {
},
{
Mnemonic: "Ctrl-u",
Description: "Clear command",
Description: "Command Clear",
},
{
Mnemonic: "Ctrl-e",
@ -248,7 +248,11 @@ func (h *Help) showGeneral() model.MenuHints {
},
{
Mnemonic: "Ctrl-space",
Description: "Clear Marks",
Description: "Mark Range",
},
{
Mnemonic: "Ctrl-\\",
Description: "Mark Clear",
},
{
Mnemonic: "Ctrl-s",

View File

@ -183,6 +183,7 @@ func (l *Log) bindKeys() {
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.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),
@ -324,6 +325,11 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey {
return nil
}
func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey {
fmt.Fprintln(l.ansiWriter, fmt.Sprintf("[white::b]%s[::]", strings.Repeat("-", 80)))
return nil
}
func (l *Log) toggleTimestampCmd(evt *tcell.EventKey) *tcell.EventKey {
if l.app.InCmdMode() {
return evt

View File

@ -16,7 +16,7 @@ func TestLogAutoScroll(t *testing.T) {
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
v.GetModel().Notify(true)
assert.Equal(t, 13, len(v.Hints()))
assert.Equal(t, 14, len(v.Hints()))
v.toggleAutoScrollCmd(nil)
assert.Equal(t, "Autoscroll: Off FullScreen: Off Timestamps: Off Wrap: Off", v.Indicator().GetText(true))
@ -85,7 +85,7 @@ func TestLogFilter(t *testing.T) {
l.SendKeys(ui.KeySlash)
l.SendStrokes("zorg")
assert.Equal(t, "zorg", list.lines)
assert.Equal(t, "\x1b[38;5;209mz\x1b[0m\x1b[38;5;209mo\x1b[0m\x1b[38;5;209mr\x1b[0m\x1b[38;5;209mg\x1b[0m", list.lines)
assert.Equal(t, 5, list.change)
assert.Equal(t, 5, list.clear)
assert.Equal(t, 0, list.fail)

View File

@ -150,14 +150,15 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
func (t *Table) bindKeys() {
t.Actions().Add(ui.KeyActions{
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
tcell.KeyCtrlW: ui.NewKeyAction("Toggle Wide", t.toggleWideCmd, false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Mark Range", t.markSpanCmd, false),
tcell.KeyCtrlBackslash: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
tcell.KeyCtrlW: ui.NewKeyAction("Toggle Wide", t.toggleWideCmd, false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
})
}
@ -188,22 +189,22 @@ func (t *Table) cpCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (t *Table) markCmd(evt *tcell.EventKey) *tcell.EventKey {
path := t.GetSelectedItem()
if path == "" {
return evt
}
t.ToggleMark()
t.Refresh()
return nil
}
func (t *Table) markSpanCmd(evt *tcell.EventKey) *tcell.EventKey {
t.SpanMark()
t.Refresh()
return nil
}
func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
path := t.GetSelectedItem()
if path == "" {
return evt
}
t.ClearMarks()
t.Refresh()
return nil
}