derailed 2020-05-29 13:23:42 -06:00
parent 3a02001695
commit 19a4594eea
8 changed files with 89 additions and 37 deletions

View File

@ -0,0 +1,36 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.20.2
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
---
Maintenance Release! Fixing a few issue in the v0.20 aftermath ;(
## Selection Marker
In this drop, we're adding the ability to set row marks ranges. There are situations where you've filtered a resource and need to delete part or all of the rows. In previous releases, you had to mark each rows one by one. Now you have the ability to select a beginning and an end range and all rows in between will now be marked! To mark a single row, you can use `space`. To select rows between your initial mark to the current selection use `Ctrl-space`. To nuke all marked rows use `Ctrl-\`. All credits and ATTA BOY goes to [Ryan Richard](https://github.com/cfryanr) for suggesting this feature!!
## Logs Got Some TLC!
Per [Raman Gupta](https://github.com/rocketraman) excellent suggestion, we've added a way to add a separator to your chatty logs to easily see the latest incoming logs. While in log view you can now press `m` to add the separator to the log stream. If you don't care about the log history and just want to see the latest incoming logs, pressing `c` will clear out the log viewer.
## Resolved Bugs/Features/PRs
- [Issue #741](https://github.com/derailed/k9s/issues/741)
- [Issue #740](https://github.com/derailed/k9s/issues/740)
- [Issue #739](https://github.com/derailed/k9s/issues/739)
- [Issue #727](https://github.com/derailed/k9s/issues/727)
- [Issue #723](https://github.com/derailed/k9s/issues/723)
- [PR #725](https://github.com/derailed/k9s/pull/725) Big Thanks To [Soupyt](https://github.com/soupyt)!!
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -135,7 +135,9 @@ func (a *APIClient) clearCache() {
// CanI checks if user has access to a certain resource. // CanI checks if user has access to a certain resource.
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) { func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
log.Debug().Msgf("Check Access %q::%q", ns, gvr) a.mx.Lock()
defer a.mx.Unlock()
if !a.connOK { if !a.connOK {
return false, errors.New("ACCESS -- No API server connection") return false, errors.New("ACCESS -- No API server connection")
} }
@ -149,7 +151,6 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
} }
} }
log.Debug().Msgf("----> Calling API")
dial, err := a.Dial() dial, err := a.Dial()
if err != nil { if err != nil {
return false, err return false, err
@ -171,9 +172,9 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
return auth, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr) return auth, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr)
} }
} }
auth = true auth = true
a.cache.Add(key, true, cacheExpiry) a.cache.Add(key, true, cacheExpiry)
return return
} }
@ -214,20 +215,22 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
} }
// CheckConnectivity return true if api server is cool or false otherwise. // CheckConnectivity return true if api server is cool or false otherwise.
func (a *APIClient) CheckConnectivity() (status bool) { func (a *APIClient) CheckConnectivity() (ok bool) {
a.mx.Lock()
defer a.mx.Unlock()
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
status = false ok = false
} }
if !status { if !ok {
a.clearCache() a.clearCache()
} }
a.connOK = status a.connOK = ok
}() }()
// Need to reload to pickup any kubeconfig changes. // Need to reload to pickup any kubeconfig changes.
config := NewConfig(a.config.flags) cfg, err := NewConfig(a.config.flags).RESTConfig()
cfg, err := config.RESTConfig()
if err != nil { if err != nil {
return false return false
} }
@ -237,7 +240,6 @@ func (a *APIClient) CheckConnectivity() (status bool) {
log.Error().Err(err).Msgf("Unable to connect to api server") log.Error().Err(err).Msgf("Unable to connect to api server")
return return
} }
log.Debug().Msgf("CONN-CHECK on %#v", cfg.Host)
// Check connection // Check connection
if _, err := client.ServerVersion(); err == nil { if _, err := client.ServerVersion(); err == nil {
@ -245,7 +247,7 @@ func (a *APIClient) CheckConnectivity() (status bool) {
log.Debug().Msgf("RESETING CON!!") log.Debug().Msgf("RESETING CON!!")
a.reset() a.reset()
} }
status = true ok = true
} else { } else {
log.Error().Err(err).Msgf("K9s can't connect to cluster") log.Error().Err(err).Msgf("K9s can't connect to cluster")
} }

View File

@ -38,6 +38,7 @@ type Log struct {
filter string filter string
lastSent int lastSent int
flushTimeout time.Duration flushTimeout time.Duration
filtering bool
} }
// NewLog returns a new model. // NewLog returns a new model.
@ -52,6 +53,9 @@ func NewLog(gvr client.GVR, opts dao.LogOptions, flushTimeout time.Duration) *Lo
// LogOptions returns the current log options. // LogOptions returns the current log options.
func (l *Log) LogOptions() dao.LogOptions { func (l *Log) LogOptions() dao.LogOptions {
l.mx.RLock()
defer l.mx.RUnlock()
return l.logOptions return l.logOptions
} }
@ -59,12 +63,18 @@ func (l *Log) LogOptions() dao.LogOptions {
func (l *Log) SinceSeconds() int64 { func (l *Log) SinceSeconds() int64 {
l.mx.RLock() l.mx.RLock()
defer l.mx.RUnlock() defer l.mx.RUnlock()
return l.logOptions.SinceSeconds return l.logOptions.SinceSeconds
} }
// SetLogOptions updates logger options. // SetLogOptions updates logger options.
func (l *Log) SetLogOptions(opts dao.LogOptions) { func (l *Log) SetLogOptions(opts dao.LogOptions) {
l.logOptions = opts l.mx.Lock()
{
l.logOptions = opts
}
l.mx.Unlock()
l.Restart() l.Restart()
} }
@ -96,6 +106,7 @@ func (l *Log) Clear() {
l.lines, l.lastSent = dao.LogItems{}, 0 l.lines, l.lastSent = dao.LogItems{}, 0
} }
l.mx.Unlock() l.mx.Unlock()
l.fireLogCleared() l.fireLogCleared()
} }
@ -148,16 +159,25 @@ func (l *Log) ClearFilter() {
l.fireLogChanged(l.lines) l.fireLogChanged(l.lines)
} }
// Filter filters the model using either fuzzy or regexp. // Filter filters th:e model using either fuzzy or regexp.
func (l *Log) Filter(q string) error { func (l *Log) Filter(q string) {
l.mx.RLock() log.Debug().Msgf("Filter %q", q)
defer l.mx.RUnlock() l.mx.Lock()
defer l.mx.Unlock()
l.filter = q l.filter = q
l.fireLogCleared() if l.filtering {
l.fireLogBuffChanged(l.lines) return
}
return nil l.filtering = true
go func(l *Log) {
<-time.After(500 * time.Millisecond)
l.mx.Lock()
defer l.mx.Unlock()
l.fireLogCleared()
l.fireLogBuffChanged(l.lines)
l.filtering = false
}(l)
} }
func (l *Log) load() error { func (l *Log) load() error {

View File

@ -75,13 +75,13 @@ func TestLogFilter(t *testing.T) {
m.Notify(true) m.Notify(true)
assert.Equal(t, 1, v.dataCalled) assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 2, v.clearCalled) assert.Equal(t, 1, v.clearCalled)
assert.Equal(t, 0, v.errCalled) assert.Equal(t, 0, v.errCalled)
assert.Equal(t, u.e, len(v.data)) assert.Equal(t, u.e, len(v.data))
m.ClearFilter() m.ClearFilter()
assert.Equal(t, 2, v.dataCalled) assert.Equal(t, 2, v.dataCalled)
assert.Equal(t, 3, v.clearCalled) assert.Equal(t, 2, v.clearCalled)
assert.Equal(t, 0, v.errCalled) assert.Equal(t, 0, v.errCalled)
assert.Equal(t, size, len(v.data)) assert.Equal(t, size, len(v.data))
}) })
@ -193,7 +193,7 @@ func TestLogTimedout(t *testing.T) {
} }
m.Notify(true) m.Notify(true)
assert.Equal(t, 1, v.dataCalled) assert.Equal(t, 1, v.dataCalled)
assert.Equal(t, 2, v.clearCalled) assert.Equal(t, 1, v.clearCalled)
assert.Equal(t, 0, v.errCalled) assert.Equal(t, 0, v.errCalled)
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" 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)) assert.Equal(t, e, string(v.data[0].Bytes))

View File

@ -142,7 +142,6 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
func (c *Command) defaultCmd() error { func (c *Command) defaultCmd() error {
if !c.app.Conn().ConnectionOK() { if !c.app.Conn().ConnectionOK() {
log.Debug().Msgf("YO!!")
return c.run("ctx", "", true) return c.run("ctx", "", true)
} }
view := c.app.Config.ActiveView() view := c.app.Config.ActiveView()

View File

@ -26,7 +26,7 @@ const (
logMessage = "Waiting for logs..." logMessage = "Waiting for logs..."
logFmt = " Logs([hilite:bg:]%s[-:bg:-])[[green:bg:b]%s[-:bg:-]] " logFmt = " Logs([hilite:bg:]%s[-:bg:-])[[green:bg:b]%s[-:bg:-]] "
logCoFmt = " Logs([hilite:bg:]%s:[hilite:bg:b]%s[-:bg:-])[[green:bg:b]%s[-:bg:-]] " logCoFmt = " Logs([hilite:bg:]%s:[hilite:bg:b]%s[-:bg:-])[[green:bg:b]%s[-:bg:-]] "
flushTimeout = 100 * time.Millisecond flushTimeout = 50 * time.Millisecond
) )
// Log represents a generic log viewer. // Log represents a generic log viewer.
@ -123,9 +123,7 @@ func (l *Log) LogChanged(lines dao.LogItems) {
// BufferChanged indicates the buffer was changed. // BufferChanged indicates the buffer was changed.
func (l *Log) BufferChanged(s string) { func (l *Log) BufferChanged(s string) {
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil { l.model.Filter(l.logs.cmdBuff.GetText())
l.app.Flash().Err(err)
}
l.updateTitle() l.updateTitle()
} }
@ -268,10 +266,9 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !l.logs.cmdBuff.IsActive() { if !l.logs.cmdBuff.IsActive() {
return evt return evt
} }
l.logs.cmdBuff.SetActive(false) l.logs.cmdBuff.SetActive(false)
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil { l.model.Filter(l.logs.cmdBuff.GetText())
l.app.Flash().Err(err)
}
l.updateTitle() l.updateTitle()
return nil return nil
@ -326,7 +323,7 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey {
} }
func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey { func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey {
fmt.Fprintln(l.ansiWriter, fmt.Sprintf("[white::b]%s[::]", strings.Repeat("-", 80))) fmt.Fprintln(l.ansiWriter, fmt.Sprintf("[white::b]%s[::]", strings.Repeat("", 80)))
return nil return nil
} }

View File

@ -85,9 +85,9 @@ func TestLogFilter(t *testing.T) {
l.SendKeys(ui.KeySlash) l.SendKeys(ui.KeySlash)
l.SendStrokes("zorg") l.SendStrokes("zorg")
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, "duhzorg", list.lines)
assert.Equal(t, 5, list.change) assert.Equal(t, 1, list.change)
assert.Equal(t, 5, list.clear) assert.Equal(t, 1, list.clear)
assert.Equal(t, 0, list.fail) assert.Equal(t, 0, list.fail)
} }

View File

@ -70,7 +70,6 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug().Msgf("LIST %q::%q -- %t::%t", gvr, ns, wait, inf.Informer().HasSynced())
if client.IsAllNamespace(ns) { if client.IsAllNamespace(ns) {
ns = client.AllNamespaces ns = client.AllNamespaces
} }
@ -109,7 +108,6 @@ func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug().Msgf("GET %q::%q -- %t::%t", gvr, path, wait, inf.Informer().HasSynced())
var o runtime.Object var o runtime.Object
if client.IsClusterScoped(ns) { if client.IsClusterScoped(ns) {
o, err = inf.Lister().Get(n) o, err = inf.Lister().Get(n)