misc bug fixes and cleanup

mine
derailed 2019-04-26 18:10:11 -06:00
parent ee61a1b578
commit f770348f74
14 changed files with 281 additions and 94 deletions

View File

@ -0,0 +1,30 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.5.2
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
+ [Issue #173](https://github.com/derailed/k9s/issues/173)
+ [Issue #174](https://github.com/derailed/k9s/issues/174)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -135,7 +135,11 @@ func (a *APIClient) IsNamespaced(res string) bool {
// SupportsResource checks for resource supported version against the server. // SupportsResource checks for resource supported version against the server.
func (a *APIClient) SupportsResource(group string) bool { func (a *APIClient) SupportsResource(group string) bool {
list, _ := a.DialOrDie().Discovery().ServerPreferredResources() list, err := a.DialOrDie().Discovery().ServerPreferredResources()
if err != nil {
log.Debug().Err(err).Msg("Unable to dial api server")
return false
}
for _, l := range list { for _, l := range list {
log.Debug().Msgf(">>> Group %s", l.GroupVersion) log.Debug().Msgf(">>> Group %s", l.GroupVersion)
if l.GroupVersion == group { if l.GroupVersion == group {

View File

@ -115,8 +115,6 @@ func (b *Base) Describe(kind, pa string, flags *genericclioptions.ConfigFlags) (
return "", err return "", err
} }
log.Debug().Msgf("Describer %#v", d)
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true}) return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
} }

View File

@ -179,6 +179,7 @@ func (r *Container) Fields(ns string) Row {
} }
cs = &c cs = &c
} }
if cs == nil { if cs == nil {
for _, c := range r.pod.Status.InitContainerStatuses { for _, c := range r.pod.Status.InitContainerStatuses {
if c.Name != i.Name { if c.Name != i.Name {
@ -188,12 +189,17 @@ func (r *Container) Fields(ns string) Row {
} }
} }
ready, state, restarts := "false", MissingValue, "0"
if cs != nil {
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
}
return append(ff, return append(ff,
i.Name, i.Name,
i.Image, i.Image,
boolToStr(cs.Ready), ready,
toState(cs.State), state,
strconv.Itoa(int(cs.RestartCount)), restarts,
probe(i.LivenessProbe), probe(i.LivenessProbe),
probe(i.ReadinessProbe), probe(i.ReadinessProbe),
cpu, cpu,

View File

@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/util/node"
) )
const ( const (
@ -212,7 +213,7 @@ func (r *Pod) Fields(ns string) Row {
return append(ff, return append(ff,
i.ObjectMeta.Name, i.ObjectMeta.Name,
strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)), strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)),
r.phase(i.Status), r.phase(i),
strconv.Itoa(rc), strconv.Itoa(rc),
ToMillicore(r.metrics.CurrentCPU), ToMillicore(r.metrics.CurrentCPU),
ToMi(r.metrics.CurrentMEM), ToMi(r.metrics.CurrentMEM),
@ -251,21 +252,76 @@ func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
return return
} }
func (*Pod) phase(s v1.PodStatus) string { func isSet(s *string) bool {
status := "Pending" return s != nil && *s != ""
for _, cs := range s.ContainerStatuses { }
func (*Pod) phase(po *v1.Pod) string {
status := string(po.Status.Phase)
if po.Status.Reason != "" {
if po.DeletionTimestamp != nil && po.Status.Reason == node.NodeUnreachablePodReason {
return "Unknown"
}
status = po.Status.Reason
}
var init bool
for i, cs := range po.Status.InitContainerStatuses {
switch { switch {
case cs.State.Running != nil:
status = "Running"
case cs.State.Waiting != nil:
status = cs.State.Waiting.Reason
case cs.State.Terminated != nil: case cs.State.Terminated != nil:
status = "Terminating" if cs.State.Terminated.ExitCode == 0 {
if len(cs.State.Terminated.Reason) != 0 { continue
status = cs.State.Terminated.Reason
} }
if cs.State.Terminated.Reason != "" {
status = "Init:" + cs.State.Terminated.Reason
init = true
break
}
if cs.State.Terminated.Signal != 0 {
status = fmt.Sprintf("Init:Signal:%d", cs.State.Terminated.Signal)
} else {
status = fmt.Sprintf("Init:ExitCode:%d", cs.State.Terminated.ExitCode)
}
case cs.State.Waiting != nil && cs.State.Waiting.Reason != "" && cs.State.Waiting.Reason != "PodInitializing":
status = "Init:" + cs.State.Waiting.Reason
default:
status = fmt.Sprintf("Init:%d/%d", i, len(po.Spec.InitContainers))
}
init = true
break
}
if init {
return status
}
var running bool
for i := len(po.Status.ContainerStatuses) - 1; i >= 0; i-- {
cs := po.Status.ContainerStatuses[i]
switch {
case cs.State.Waiting != nil && cs.State.Waiting.Reason != "":
status = cs.State.Waiting.Reason
case cs.State.Terminated != nil && cs.State.Terminated.Reason != "":
status = cs.State.Terminated.Reason
case cs.State.Terminated != nil:
if cs.State.Terminated.Signal != 0 {
status = fmt.Sprintf("Signal:%d", cs.State.Terminated.Signal)
} else {
status = fmt.Sprintf("ExitCode:%d", cs.State.Terminated.ExitCode)
}
case cs.Ready && cs.State.Running != nil:
running = true
} }
} }
return status if status == "Completed" && running {
status = "Running"
}
if po.DeletionTimestamp == nil {
return status
}
return "Terminated"
} }

View File

@ -2,9 +2,11 @@ package resource
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
func TestPodStatuses(t *testing.T) { func TestPodStatuses(t *testing.T) {
@ -49,69 +51,132 @@ func TestPodStatuses(t *testing.T) {
func TestPodPhase(t *testing.T) { func TestPodPhase(t *testing.T) {
uu := []struct { uu := []struct {
s v1.PodStatus p *v1.Pod
e string e string
}{ }{
{ {makePodStatus("p1", v1.PodRunning, ""), "Running"},
v1.PodStatus{ {makePodStatus("p1", v1.PodRunning, "Evicted"), "Evicted"},
ContainerStatuses: []v1.ContainerStatus{ {makePodStatus("p1", v1.PodPending, ""), "Pending"},
{ {makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"},
Name: "c1", {makePodStatus("p1", v1.PodFailed, ""), "Failed"},
State: v1.ContainerState{ {makePodStatus("p1", v1.PodUnknown, ""), "Unknown"},
Running: &v1.ContainerStateRunning{}, {makePodCoInitTerminated("p1"), "Init:OOMKilled"},
}, {makePodCoInitWaiting("p1", ""), "Init:0/1"},
}, {makePodCoInitWaiting("p1", "Waiting"), "Init:Waiting"},
}, {makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"},
}, {makePodCoWaiting("p1", "Waiting"), "Waiting"},
"Running", {makePodCoWaiting("p1", ""), ""},
}, {makePodCoTerminated("p1", "OOMKilled", 0, true), "Terminated"},
{ {makePodCoTerminated("p1", "OOMKilled", 0, false), "OOMKilled"},
v1.PodStatus{ {makePodCoTerminated("p1", "", 0, true), "Terminated"},
ContainerStatuses: []v1.ContainerStatus{ {makePodCoTerminated("p1", "", 0, false), "ExitCode:1"},
{ {makePodCoTerminated("p1", "", 1, true), "Terminated"},
Name: "c1", {makePodCoTerminated("p1", "", 1, false), "Signal:1"},
State: v1.ContainerState{
Waiting: &v1.ContainerStateWaiting{
Reason: "blee",
},
},
},
},
},
"blee",
},
{
v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Name: "c1",
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{},
},
},
},
},
"Terminating",
},
{
v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Name: "c1",
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Reason: "blee",
},
},
},
},
},
"blee",
},
} }
var p Pod var p Pod
for _, u := range uu { for _, u := range uu {
assert.Equal(t, u.e, p.phase(u.s)) assert.Equal(t, u.e, p.phase(u.p))
}
}
func makePodStatus(n string, phase v1.PodPhase, reason string) *v1.Pod {
po := makePod(n)
po.Status = v1.PodStatus{
Phase: phase,
Reason: reason,
}
return po
}
func makePodCoInitTerminated(n string) *v1.Pod {
po := makePod(n)
po.Status.InitContainerStatuses = []v1.ContainerStatus{
{
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Reason: "OOMKilled",
ExitCode: 1,
},
},
},
}
return po
}
func makePodCoInitWaiting(n, reason string) *v1.Pod {
po := makePod(n)
po.Status.InitContainerStatuses = []v1.ContainerStatus{
{
State: v1.ContainerState{
Waiting: &v1.ContainerStateWaiting{
Reason: reason,
},
},
},
}
return po
}
func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod {
po := makePod(n)
if deleted {
po.DeletionTimestamp = &metav1.Time{time.Now()}
}
po.Status.ContainerStatuses = []v1.ContainerStatus{
{
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Reason: reason,
Signal: signal,
ExitCode: 1,
},
},
},
}
return po
}
func makePodCoWaiting(n, reason string) *v1.Pod {
po := makePod(n)
po.Status.ContainerStatuses = []v1.ContainerStatus{
{
State: v1.ContainerState{
Waiting: &v1.ContainerStateWaiting{
Reason: reason,
},
},
},
}
return po
}
func makePod(n string) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: n,
Namespace: "default",
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Name: "ic1",
},
},
Containers: []v1.Container{
{
Name: "c1",
},
},
},
} }
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"time" "time"
"sync"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
@ -60,6 +61,7 @@ type (
cmdBuff *cmdBuff cmdBuff *cmdBuff
cmdView *cmdView cmdView *cmdView
actions keyActions actions keyActions
mx sync.Mutex
} }
) )
@ -143,6 +145,9 @@ func (a *appView) Run() {
} }
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
a.mx.Lock()
defer a.mx.Unlock()
key := evt.Key() key := evt.Key()
if key == tcell.KeyRune { if key == tcell.KeyRune {
if a.cmdBuff.isActive() { if a.cmdBuff.isActive() {

View File

@ -5,6 +5,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -24,6 +25,7 @@ type detailsView struct {
cmdBuff *cmdBuff cmdBuff *cmdBuff
backFn actionHandler backFn actionHandler
numSelections int numSelections int
mx sync.Mutex
} }
func newDetailsView(app *appView, backFn actionHandler) *detailsView { func newDetailsView(app *appView, backFn actionHandler) *detailsView {
@ -63,6 +65,9 @@ func (v *detailsView) setCategory(n string) {
} }
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey { func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
v.mx.Lock()
defer v.mx.Unlock()
key := evt.Key() key := evt.Key()
if key == tcell.KeyRune { if key == tcell.KeyRune {
if v.cmdBuff.isActive() { if v.cmdBuff.isActive() {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
@ -28,6 +29,7 @@ type logsView struct {
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
autoScroll bool autoScroll bool
showPrevious bool showPrevious bool
mx sync.Mutex
} }
func newLogsView(pview string, parent loggable) *logsView { func newLogsView(pview string, parent loggable) *logsView {
@ -148,6 +150,9 @@ func (v *logsView) load(i int) {
} }
func (v *logsView) doLoad(path, co string) error { func (v *logsView) doLoad(path, co string) error {
v.mx.Lock()
defer v.mx.Unlock()
v.stop() v.stop()
c := make(chan string) c := make(chan string)

View File

@ -6,6 +6,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview" "github.com/derailed/tview"
@ -77,7 +78,7 @@ func newKeyAction(d string, a actionHandler, display bool) keyAction {
} }
func newMenuView() *menuView { func newMenuView() *menuView {
v := menuView{tview.NewTable()} v := menuView{Table: tview.NewTable()}
return &v return &v
} }
@ -106,9 +107,14 @@ func (a keyActions) toHints() hints {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type menuView struct { type menuView struct {
*tview.Table *tview.Table
mx sync.Mutex
} }
func (v *menuView) populateMenu(hh hints) { func (v *menuView) populateMenu(hh hints) {
v.mx.Lock()
defer v.mx.Unlock()
v.Clear() v.Clear()
sort.Sort(hh) sort.Sort(hh)

View File

@ -25,7 +25,7 @@ type loggable interface {
} }
func newPodView(t string, app *appView, list resource.List) resourceViewer { func newPodView(t string, app *appView, list resource.List) resourceViewer {
v := podView{newResourceView(t, app, list).(*resourceView)} v := podView{resourceView: newResourceView(t, app, list).(*resourceView)}
{ {
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.listContainers v.enterFn = v.listContainers

View File

@ -70,11 +70,6 @@ func newResourceView(title string, app *appView, list resource.List) resourceVie
details := newDetailsView(app, v.backCmd) details := newDetailsView(app, v.backCmd)
v.AddPage("details", details, true, false) v.AddPage("details", details, true, false)
confirm := tview.NewModal().
AddButtons([]string{"Cancel", "OK"}).
SetTextColor(tcell.ColorFuchsia)
v.AddPage("confirm", confirm, false, false)
return &v return &v
} }
@ -180,9 +175,7 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
sel := v.getSelectedItem() sel := v.getSelectedItem()
confirm := v.GetPrimitive("confirm").(*tview.Modal) v.showModal(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel), func(_ int, button string) {
confirm.SetText(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel))
confirm.SetDoneFunc(func(_ int, button string) {
if button == "OK" { if button == "OK" {
v.getTV().setDeleted() v.getTV().setDeleted()
v.app.flash(flashInfo, fmt.Sprintf("Deleting %s %s", v.list.GetName(), sel)) v.app.flash(flashInfo, fmt.Sprintf("Deleting %s %s", v.list.GetName(), sel))
@ -192,13 +185,27 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
v.refresh() v.refresh()
} }
} }
v.switchPage(v.list.GetName()) v.dismissModal()
}) })
v.SwitchToPage("confirm")
return nil return nil
} }
func (v *resourceView) showModal(msg string, done func(int, string)) {
confirm := tview.NewModal().
AddButtons([]string{"Cancel", "OK"}).
SetTextColor(tcell.ColorFuchsia).
SetText(msg).
SetDoneFunc(done)
v.AddPage("confirm", confirm, false, false)
v.ShowPage("confirm")
}
func (v *resourceView) dismissModal() {
v.RemovePage("confirm")
v.switchPage(v.list.GetName())
}
func (v *resourceView) defaultEnter(app *appView, ns, resource, selection string) { func (v *resourceView) defaultEnter(app *appView, ns, resource, selection string) {
sel := v.getSelectedItem() sel := v.getSelectedItem()
yaml, err := v.list.Resource().Describe(v.title, sel, v.app.flags) yaml, err := v.list.Resource().Describe(v.title, sel, v.app.flags)
@ -270,6 +277,7 @@ func (v *resourceView) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
i, _ := strconv.Atoi(string(evt.Rune())) i, _ := strconv.Atoi(string(evt.Rune()))
ns := v.namespaces[i] ns := v.namespaces[i]
v.doSwitchNamespace(ns) v.doSwitchNamespace(ns)
return nil return nil
} }
@ -284,6 +292,7 @@ func (v *resourceView) doSwitchNamespace(ns string) {
v.list.SetNamespace(v.selectedNS) v.list.SetNamespace(v.selectedNS)
} }
v.update.Unlock() v.update.Unlock()
v.refresh() v.refresh()
v.selectItem(0, 0) v.selectItem(0, 0)
v.getTV().resetTitle() v.getTV().resetTitle()

View File

@ -7,7 +7,6 @@ import (
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
@ -85,17 +84,14 @@ func (v *replicaSetView) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
confirm := v.GetPrimitive("confirm").(*tview.Modal) v.showModal(fmt.Sprintf("Rollback %s %s?", v.list.GetName(), v.selectedItem), func(_ int, button string) {
confirm.SetText(fmt.Sprintf("Rollback %s %s?", v.list.GetName(), v.selectedItem))
confirm.SetDoneFunc(func(_ int, button string) {
if button == "OK" { if button == "OK" {
v.app.flash(flashInfo, fmt.Sprintf("Rolling back %s %s", v.list.GetName(), v.selectedItem)) v.app.flash(flashInfo, fmt.Sprintf("Rolling back %s %s", v.list.GetName(), v.selectedItem))
rollback(v.app, v.selectedItem) rollback(v.app, v.selectedItem)
v.refresh() v.refresh()
} }
v.switchPage(v.list.GetName()) v.dismissModal()
}) })
v.SwitchToPage("confirm")
return nil return nil
} }

View File

@ -3,6 +3,7 @@ package views
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -111,6 +112,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := v.actions[key]; ok { if a, ok := v.actions[key]; ok {
log.Debug().Msgf(">> TableView handled %s", tcell.KeyNames[key]) log.Debug().Msgf(">> TableView handled %s", tcell.KeyNames[key])
log.Debug().Msgf("Go Routine %d", runtime.NumGoroutine())
return a.action(evt) return a.action(evt)
} }