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.
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 {
log.Debug().Msgf(">>> Group %s", l.GroupVersion)
if l.GroupVersion == group {

View File

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

View File

@ -179,6 +179,7 @@ func (r *Container) Fields(ns string) Row {
}
cs = &c
}
if cs == nil {
for _, c := range r.pod.Status.InitContainerStatuses {
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,
i.Name,
i.Image,
boolToStr(cs.Ready),
toState(cs.State),
strconv.Itoa(int(cs.RestartCount)),
ready,
state,
restarts,
probe(i.LivenessProbe),
probe(i.ReadinessProbe),
cpu,

View File

@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/util/node"
)
const (
@ -212,7 +213,7 @@ func (r *Pod) Fields(ns string) Row {
return append(ff,
i.ObjectMeta.Name,
strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)),
r.phase(i.Status),
r.phase(i),
strconv.Itoa(rc),
ToMillicore(r.metrics.CurrentCPU),
ToMi(r.metrics.CurrentMEM),
@ -251,21 +252,76 @@ func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
return
}
func (*Pod) phase(s v1.PodStatus) string {
status := "Pending"
for _, cs := range s.ContainerStatuses {
func isSet(s *string) bool {
return s != nil && *s != ""
}
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 {
case cs.State.Running != nil:
status = "Running"
case cs.State.Waiting != nil:
status = cs.State.Waiting.Reason
case cs.State.Terminated != nil:
status = "Terminating"
if len(cs.State.Terminated.Reason) != 0 {
status = cs.State.Terminated.Reason
if cs.State.Terminated.ExitCode == 0 {
continue
}
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestPodStatuses(t *testing.T) {
@ -49,69 +51,132 @@ func TestPodStatuses(t *testing.T) {
func TestPodPhase(t *testing.T) {
uu := []struct {
s v1.PodStatus
p *v1.Pod
e string
}{
{
v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Name: "c1",
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{},
},
},
},
},
"Running",
},
{
v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Name: "c1",
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",
},
{makePodStatus("p1", v1.PodRunning, ""), "Running"},
{makePodStatus("p1", v1.PodRunning, "Evicted"), "Evicted"},
{makePodStatus("p1", v1.PodPending, ""), "Pending"},
{makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"},
{makePodStatus("p1", v1.PodFailed, ""), "Failed"},
{makePodStatus("p1", v1.PodUnknown, ""), "Unknown"},
{makePodCoInitTerminated("p1"), "Init:OOMKilled"},
{makePodCoInitWaiting("p1", ""), "Init:0/1"},
{makePodCoInitWaiting("p1", "Waiting"), "Init:Waiting"},
{makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"},
{makePodCoWaiting("p1", "Waiting"), "Waiting"},
{makePodCoWaiting("p1", ""), ""},
{makePodCoTerminated("p1", "OOMKilled", 0, true), "Terminated"},
{makePodCoTerminated("p1", "OOMKilled", 0, false), "OOMKilled"},
{makePodCoTerminated("p1", "", 0, true), "Terminated"},
{makePodCoTerminated("p1", "", 0, false), "ExitCode:1"},
{makePodCoTerminated("p1", "", 1, true), "Terminated"},
{makePodCoTerminated("p1", "", 1, false), "Signal:1"},
}
var p Pod
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"
"fmt"
"time"
"sync"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
@ -60,6 +61,7 @@ type (
cmdBuff *cmdBuff
cmdView *cmdView
actions keyActions
mx sync.Mutex
}
)
@ -143,6 +145,9 @@ func (a *appView) Run() {
}
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
a.mx.Lock()
defer a.mx.Unlock()
key := evt.Key()
if key == tcell.KeyRune {
if a.cmdBuff.isActive() {

View File

@ -5,6 +5,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
@ -24,6 +25,7 @@ type detailsView struct {
cmdBuff *cmdBuff
backFn actionHandler
numSelections int
mx sync.Mutex
}
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 {
v.mx.Lock()
defer v.mx.Unlock()
key := evt.Key()
if key == tcell.KeyRune {
if v.cmdBuff.isActive() {

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"sync"
"time"
"github.com/derailed/k9s/internal/resource"
@ -28,6 +29,7 @@ type logsView struct {
cancelFunc context.CancelFunc
autoScroll bool
showPrevious bool
mx sync.Mutex
}
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 {
v.mx.Lock()
defer v.mx.Unlock()
v.stop()
c := make(chan string)

View File

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

View File

@ -25,7 +25,7 @@ type loggable interface {
}
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.enterFn = v.listContainers

View File

@ -70,11 +70,6 @@ func newResourceView(title string, app *appView, list resource.List) resourceVie
details := newDetailsView(app, v.backCmd)
v.AddPage("details", details, true, false)
confirm := tview.NewModal().
AddButtons([]string{"Cancel", "OK"}).
SetTextColor(tcell.ColorFuchsia)
v.AddPage("confirm", confirm, false, false)
return &v
}
@ -180,9 +175,7 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
}
sel := v.getSelectedItem()
confirm := v.GetPrimitive("confirm").(*tview.Modal)
confirm.SetText(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel))
confirm.SetDoneFunc(func(_ int, button string) {
v.showModal(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel), func(_ int, button string) {
if button == "OK" {
v.getTV().setDeleted()
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.switchPage(v.list.GetName())
v.dismissModal()
})
v.SwitchToPage("confirm")
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) {
sel := v.getSelectedItem()
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()))
ns := v.namespaces[i]
v.doSwitchNamespace(ns)
return nil
}
@ -284,6 +292,7 @@ func (v *resourceView) doSwitchNamespace(ns string) {
v.list.SetNamespace(v.selectedNS)
}
v.update.Unlock()
v.refresh()
v.selectItem(0, 0)
v.getTV().resetTitle()

View File

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

View File

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