parent
3a02001695
commit
19a4594eea
|
|
@ -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)
|
||||
|
|
@ -135,7 +135,9 @@ func (a *APIClient) clearCache() {
|
|||
|
||||
// CanI checks if user has access to a certain resource.
|
||||
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 {
|
||||
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()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
auth = true
|
||||
a.cache.Add(key, true, cacheExpiry)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -214,20 +215,22 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
|||
}
|
||||
|
||||
// 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() {
|
||||
if err := recover(); err != nil {
|
||||
status = false
|
||||
ok = false
|
||||
}
|
||||
if !status {
|
||||
if !ok {
|
||||
a.clearCache()
|
||||
}
|
||||
a.connOK = status
|
||||
a.connOK = ok
|
||||
}()
|
||||
|
||||
// Need to reload to pickup any kubeconfig changes.
|
||||
config := NewConfig(a.config.flags)
|
||||
cfg, err := config.RESTConfig()
|
||||
cfg, err := NewConfig(a.config.flags).RESTConfig()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -237,7 +240,6 @@ func (a *APIClient) CheckConnectivity() (status bool) {
|
|||
log.Error().Err(err).Msgf("Unable to connect to api server")
|
||||
return
|
||||
}
|
||||
log.Debug().Msgf("CONN-CHECK on %#v", cfg.Host)
|
||||
|
||||
// Check connection
|
||||
if _, err := client.ServerVersion(); err == nil {
|
||||
|
|
@ -245,7 +247,7 @@ func (a *APIClient) CheckConnectivity() (status bool) {
|
|||
log.Debug().Msgf("RESETING CON!!")
|
||||
a.reset()
|
||||
}
|
||||
status = true
|
||||
ok = true
|
||||
} else {
|
||||
log.Error().Err(err).Msgf("K9s can't connect to cluster")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type Log struct {
|
|||
filter string
|
||||
lastSent int
|
||||
flushTimeout time.Duration
|
||||
filtering bool
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (l *Log) LogOptions() dao.LogOptions {
|
||||
l.mx.RLock()
|
||||
defer l.mx.RUnlock()
|
||||
|
||||
return l.logOptions
|
||||
}
|
||||
|
||||
|
|
@ -59,12 +63,18 @@ func (l *Log) LogOptions() dao.LogOptions {
|
|||
func (l *Log) SinceSeconds() int64 {
|
||||
l.mx.RLock()
|
||||
defer l.mx.RUnlock()
|
||||
|
||||
return l.logOptions.SinceSeconds
|
||||
}
|
||||
|
||||
// SetLogOptions updates logger options.
|
||||
func (l *Log) SetLogOptions(opts dao.LogOptions) {
|
||||
l.logOptions = opts
|
||||
l.mx.Lock()
|
||||
{
|
||||
l.logOptions = opts
|
||||
}
|
||||
l.mx.Unlock()
|
||||
|
||||
l.Restart()
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +106,7 @@ func (l *Log) Clear() {
|
|||
l.lines, l.lastSent = dao.LogItems{}, 0
|
||||
}
|
||||
l.mx.Unlock()
|
||||
|
||||
l.fireLogCleared()
|
||||
}
|
||||
|
||||
|
|
@ -148,16 +159,25 @@ func (l *Log) ClearFilter() {
|
|||
l.fireLogChanged(l.lines)
|
||||
}
|
||||
|
||||
// Filter filters the model using either fuzzy or regexp.
|
||||
func (l *Log) Filter(q string) error {
|
||||
l.mx.RLock()
|
||||
defer l.mx.RUnlock()
|
||||
// Filter filters th:e model using either fuzzy or regexp.
|
||||
func (l *Log) Filter(q string) {
|
||||
log.Debug().Msgf("Filter %q", q)
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
l.filter = q
|
||||
l.fireLogCleared()
|
||||
l.fireLogBuffChanged(l.lines)
|
||||
|
||||
return nil
|
||||
if l.filtering {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -75,13 +75,13 @@ func TestLogFilter(t *testing.T) {
|
|||
|
||||
m.Notify(true)
|
||||
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, u.e, len(v.data))
|
||||
|
||||
m.ClearFilter()
|
||||
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, size, len(v.data))
|
||||
})
|
||||
|
|
@ -193,7 +193,7 @@ func TestLogTimedout(t *testing.T) {
|
|||
}
|
||||
m.Notify(true)
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 2, v.clearCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
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"
|
||||
assert.Equal(t, e, string(v.data[0].Bytes))
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ 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()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const (
|
|||
logMessage = "Waiting for logs..."
|
||||
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:-]] "
|
||||
flushTimeout = 100 * time.Millisecond
|
||||
flushTimeout = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// Log represents a generic log viewer.
|
||||
|
|
@ -123,9 +123,7 @@ func (l *Log) LogChanged(lines dao.LogItems) {
|
|||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (l *Log) BufferChanged(s string) {
|
||||
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil {
|
||||
l.app.Flash().Err(err)
|
||||
}
|
||||
l.model.Filter(l.logs.cmdBuff.GetText())
|
||||
l.updateTitle()
|
||||
}
|
||||
|
||||
|
|
@ -268,10 +266,9 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if !l.logs.cmdBuff.IsActive() {
|
||||
return evt
|
||||
}
|
||||
|
||||
l.logs.cmdBuff.SetActive(false)
|
||||
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil {
|
||||
l.app.Flash().Err(err)
|
||||
}
|
||||
l.model.Filter(l.logs.cmdBuff.GetText())
|
||||
l.updateTitle()
|
||||
|
||||
return nil
|
||||
|
|
@ -326,7 +323,7 @@ func (l *Log) clearCmd(*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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ func TestLogFilter(t *testing.T) {
|
|||
l.SendKeys(ui.KeySlash)
|
||||
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, 5, list.change)
|
||||
assert.Equal(t, 5, list.clear)
|
||||
assert.Equal(t, "duhzorg", list.lines)
|
||||
assert.Equal(t, 1, list.change)
|
||||
assert.Equal(t, 1, list.clear)
|
||||
assert.Equal(t, 0, list.fail)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug().Msgf("LIST %q::%q -- %t::%t", gvr, ns, wait, inf.Informer().HasSynced())
|
||||
if client.IsAllNamespace(ns) {
|
||||
ns = client.AllNamespaces
|
||||
}
|
||||
|
|
@ -109,7 +108,6 @@ func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug().Msgf("GET %q::%q -- %t::%t", gvr, path, wait, inf.Informer().HasSynced())
|
||||
var o runtime.Object
|
||||
if client.IsClusterScoped(ns) {
|
||||
o, err = inf.Lister().Get(n)
|
||||
|
|
|
|||
Loading…
Reference in New Issue