bug fixes and cleanup #210 #209 #206

mine
derailed 2019-06-03 22:51:50 -06:00
parent f469070b35
commit 3357b1237b
26 changed files with 338 additions and 182 deletions

View File

@ -0,0 +1,27 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.7.3
## 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. Your support, kindness and awesome suggestions to make K9s better is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
---
## Resolved Bugs/Features
+ [Issue #210](https://github.com/derailed/k9s/issues/210)
+ [Issue #209](https://github.com/derailed/k9s/issues/209)
+ [Issue #206](https://github.com/derailed/k9s/issues/206) Thank you @carlowouters!!
---
<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

@ -191,7 +191,7 @@ func newTable() *Table {
func newTableHeader() *TableHeader { func newTableHeader() *TableHeader {
return &TableHeader{ return &TableHeader{
FgColor: "white", FgColor: "white",
BgColor: "black", BgColor: "red",
SorterColor: "aqua", SorterColor: "aqua",
} }
} }

View File

@ -80,23 +80,6 @@ func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
Do().Get() Do().Get()
} }
func (r *Resource) getOne(ns, n string) (runtime.Object, error) {
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
_, codec := r.codecs()
c, err := r.getClient()
if err != nil {
return nil, err
}
return c.Get().
SetHeader("Accept", a).
Namespace(ns).
Resource(n).
VersionedParams(&metav1beta1.TableOptions{}, codec).
Do().Get()
}
func (r *Resource) getClient() (*rest.RESTClient, error) { func (r *Resource) getClient() (*rest.RESTClient, error) {
crConfig := r.RestConfigOrDie() crConfig := r.RestConfigOrDie()
crConfig.GroupVersion = &schema.GroupVersion{Group: r.group, Version: r.version} crConfig.GroupVersion = &schema.GroupVersion{Group: r.group, Version: r.version}

View File

@ -287,9 +287,9 @@ func toRes(r v1.ResourceList) (string, string) {
func probe(p *v1.Probe) string { func probe(p *v1.Probe) string {
if p == nil { if p == nil {
return "on"
}
return "off" return "off"
}
return "on"
} }
func asMi(v int64) float64 { func asMi(v int64) float64 {

View File

@ -0,0 +1,110 @@
package resource
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func TestProbe(t *testing.T) {
uu := map[string]struct {
probe *v1.Probe
e string
}{
"defined": {&v1.Probe{}, "on"},
"undefined": {nil, "off"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, probe(u.probe))
})
}
}
func TestAsMi(t *testing.T) {
uu := map[string]struct {
mem int64
e float64
}{
"zero": {0, 0},
"1Mb": {1024 * 1024, 1.048576e+06},
"10Mb": {10 * 1024 * 1024, 1.048576e+07},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, asMi(u.mem))
})
}
}
func TestToRes(t *testing.T) {
uu := map[string]struct {
res v1.ResourceList
ecpu, emem string
}{
"cool": {v1.ResourceList{
v1.ResourceCPU: toQty("10m"),
v1.ResourceMemory: toQty("20Mi"),
},
"10", "20"},
"noRes": {v1.ResourceList{},
"0", "0"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
cpu, mem := toRes(u.res)
assert.Equal(t, u.ecpu, cpu)
assert.Equal(t, u.emem, mem)
})
}
}
func TestToState(t *testing.T) {
uu := map[string]struct {
state v1.ContainerState
e string
}{
"empty": {v1.ContainerState{},
MissingValue},
"running": {
v1.ContainerState{Running: &v1.ContainerStateRunning{}},
"Running",
},
"waiting": {
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}},
"Waiting",
},
"waitingReason": {
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}},
"blee",
},
"terminated": {
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}},
"Terminated",
},
"terminatedReason": {
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}},
"blee",
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, toState(u.state))
})
}
}
// ----------------------------------------------------------------------------
// Helpers...
func toQty(s string) resource.Quantity {
q, _ := resource.ParseQuantity(s)
return q
}

View File

@ -94,7 +94,7 @@ func (r *Custom) List(ns string) (Columnars, error) {
return nil, err return nil, err
} }
if len(ii) != 1 { if len(ii) == 0 {
return Columnars{}, errors.New("no resources found") return Columnars{}, errors.New("no resources found")
} }

View File

@ -25,6 +25,7 @@ func newContainerView(t string, app *appView, list resource.List, path string, e
{ {
v.path = &path v.path = &path
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.viewLogs
v.colorerFn = containerColorer v.colorerFn = containerColorer
v.current = app.content.GetPrimitive("main").(igniter) v.current = app.content.GetPrimitive("main").(igniter)
v.exitFn = exitFn v.exitFn = exitFn
@ -45,7 +46,6 @@ func (v *containerView) extraActions(aa keyActions) {
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true) aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
aa[tcell.KeyEscape] = newKeyAction("Back", v.backCmd, false) aa[tcell.KeyEscape] = newKeyAction("Back", v.backCmd, false)
aa[KeyP] = newKeyAction("Previous", v.backCmd, false) aa[KeyP] = newKeyAction("Previous", v.backCmd, false)
aa[tcell.KeyEnter] = newKeyAction("View Logs", v.logsCmd, false)
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(6, false), true) aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(6, false), true)
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(7, false), true) aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(7, false), true)
aa[KeyAltC] = newKeyAction("Sort CPU%", v.sortColCmd(8, false), true) aa[KeyAltC] = newKeyAction("Sort CPU%", v.sortColCmd(8, false), true)
@ -72,18 +72,22 @@ func (v *containerView) getSelection() string {
// Handlers... // Handlers...
func (v *containerView) viewLogs(app *appView, _, res, sel string) {
cell := v.getTV().GetCell(v.selectedRow, 3)
if cell != nil && strings.TrimSpace(cell.Text) != "Running" {
v.app.flash().err(errors.New("No logs for a non running container"))
return
}
v.showLogs(sel, v.list.GetName(), v, false)
}
func (v *containerView) logsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *containerView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() { if !v.rowSelected() {
return evt return evt
} }
cell := v.getTV().GetCell(v.selectedRow, 3) v.viewLogs(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
if cell != nil && strings.TrimSpace(cell.Text) != "Running" {
v.app.flash().err(errors.New("No logs for a non running container"))
return evt
}
v.showLogs(v.selectedItem, v.list.GetName(), v, false)
return nil return nil
} }

View File

@ -4,7 +4,6 @@ import (
"strings" "strings"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
) )
type contextView struct { type contextView struct {
@ -14,6 +13,7 @@ type contextView struct {
func newContextView(t string, app *appView, list resource.List) resourceViewer { func newContextView(t string, app *appView, list resource.List) resourceViewer {
v := contextView{newResourceView(t, app, list).(*resourceView)} v := contextView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.useCtx
v.getTV().cleanseFn = v.cleanser v.getTV().cleanseFn = v.cleanser
return &v return &v
@ -21,21 +21,14 @@ func newContextView(t string, app *appView, list resource.List) resourceViewer {
func (v *contextView) extraActions(aa keyActions) { func (v *contextView) extraActions(aa keyActions) {
delete(v.getTV().actions, KeyShiftA) delete(v.getTV().actions, KeyShiftA)
aa[tcell.KeyEnter] = newKeyAction("Switch", v.useCmd, true)
} }
func (v *contextView) useCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *contextView) useCtx(app *appView, _, res, sel string) {
if !v.rowSelected() { if err := v.useContext(sel); err != nil {
return evt app.flash().err(err)
return
} }
if err := v.useContext(v.selectedItem); err != nil { app.gotoResource("po", true)
v.app.flash().err(err)
return evt
}
v.app.gotoResource("po", true)
return nil
} }
func (*contextView) cleanser(s string) string { func (*contextView) cleanser(s string) string {

View File

@ -16,6 +16,7 @@ type deployView struct {
func newDeployView(t string, app *appView, list resource.List) resourceViewer { func newDeployView(t string, app *appView, list resource.List) resourceViewer {
v := deployView{newResourceView(t, app, list).(*resourceView)} v := deployView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -23,7 +24,6 @@ func newDeployView(t string, app *appView, list resource.List) resourceViewer {
func (v *deployView) extraActions(aa keyActions) { func (v *deployView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *deployView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *deployView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -36,30 +36,25 @@ func (v *deployView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tc
} }
} }
func (v *deployView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *deployView) showPods(app *appView, _, res, sel string) {
if !v.rowSelected() { ns, n := namespaced(sel)
return evt d := k8s.NewDeployment(app.conn())
}
ns, n := namespaced(v.selectedItem)
d := k8s.NewDeployment(v.app.conn())
dep, err := d.Get(ns, n) dep, err := d.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching Deployment %s", v.selectedItem) log.Error().Err(err).Msgf("Fetching Deployment %s", sel)
v.app.flash().err(err) app.flash().err(err)
return evt return
} }
dp := dep.(*v1.Deployment) dp := dep.(*v1.Deployment)
l, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
sel, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Converting selector for Deployment %s", v.selectedItem) log.Error().Err(err).Msgf("Converting selector for Deployment %s", sel)
v.app.flash().err(err) app.flash().err(err)
return evt return
} }
showPods(v.app, ns, "Deployment", v.selectedItem, sel.String(), "", v.backCmd)
return nil showPods(app, ns, "Deployment", sel, l.String(), "", v.backCmd)
} }
func (v *deployView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *deployView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -16,6 +16,7 @@ type daemonSetView struct {
func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer { func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer {
v := daemonSetView{newResourceView(t, app, list).(*resourceView)} v := daemonSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -23,7 +24,6 @@ func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer
func (v *daemonSetView) extraActions(aa keyActions) { func (v *daemonSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *daemonSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *daemonSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -36,30 +36,25 @@ func (v *daemonSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey)
} }
} }
func (v *daemonSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *daemonSetView) showPods(app *appView, _, res, sel string) {
if !v.rowSelected() { ns, n := namespaced(sel)
return evt d := k8s.NewDaemonSet(app.conn())
}
ns, n := namespaced(v.selectedItem)
d := k8s.NewDaemonSet(v.app.conn())
dset, err := d.Get(ns, n) dset, err := d.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching DeaemonSet %s", v.selectedItem) log.Error().Err(err).Msgf("Fetching DeaemonSet %s", sel)
v.app.flash().err(err) v.app.flash().err(err)
return evt return
} }
ds := dset.(*extv1beta1.DaemonSet) ds := dset.(*extv1beta1.DaemonSet)
l, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
sel, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", v.selectedItem) log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", sel)
v.app.flash().err(err) app.flash().err(err)
return evt return
} }
showPods(v.app, ns, "DaemonSet", v.selectedItem, sel.String(), "", v.backCmd)
return nil showPods(app, ns, "DaemonSet", sel, l.String(), "", v.backCmd)
} }
func (v *daemonSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *daemonSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -92,6 +92,7 @@ func (v *dumpView) registerActions() {
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false) v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, true) v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, true)
v.actions[tcell.KeyCtrlD] = newKeyAction("Delete", v.deleteCmd, true) v.actions[tcell.KeyCtrlD] = newKeyAction("Delete", v.deleteCmd, true)
v.actions[tcell.KeyCtrlS] = newKeyAction("Save", v.app.noopCmd, false)
vu := v.getTV() vu := v.getTV()
vu.setActions(v.actions) vu.setActions(v.actions)

View File

@ -1,7 +1,6 @@
package views package views
import ( import (
"fmt"
"testing" "testing"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
@ -106,7 +105,6 @@ func TestStripPort(t *testing.T) {
for k, u := range uu { for k, u := range uu {
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
fmt.Println("TCP?", u.port, isTCPPort(u.port))
assert.Equal(t, u.e, stripPort(u.port)) assert.Equal(t, u.e, stripPort(u.port))
}) })
} }

View File

@ -16,6 +16,7 @@ type jobView struct {
func newJobView(t string, app *appView, list resource.List) resourceViewer { func newJobView(t string, app *appView, list resource.List) resourceViewer {
v := jobView{resourceView: newResourceView(t, app, list).(*resourceView)} v := jobView{resourceView: newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false) v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
picker := newSelectList(&v) picker := newSelectList(&v)
@ -99,33 +100,27 @@ func (v *jobView) showLogs(path, co, view string, parent loggable, prev bool) {
func (v *jobView) extraActions(aa keyActions) { func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true) aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyShiftL] = newKeyAction("Logs Previous", v.prevLogsCmd, true) aa[KeyShiftL] = newKeyAction("Logs Previous", v.prevLogsCmd, true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *jobView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *jobView) showPods(app *appView, _, res, sel string) {
if !v.rowSelected() { ns, n := namespaced(sel)
return evt j := k8s.NewJob(app.conn())
}
ns, n := namespaced(v.selectedItem)
j := k8s.NewJob(v.app.conn())
job, err := j.Get(ns, n) job, err := j.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching Job %s", v.selectedItem) log.Error().Err(err).Msgf("Fetching Job %s", sel)
v.app.flash().err(err) app.flash().err(err)
return evt return
} }
jo := job.(*batchv1.Job) jo := job.(*batchv1.Job)
l, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
sel, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Converting selector for Job %s", v.selectedItem) log.Error().Err(err).Msgf("Converting selector for Job %s", sel)
v.app.flash().err(err) app.flash().err(err)
return evt return
} }
showPods(v.app, "", "Job", v.selectedItem, sel.String(), "", v.backCmd)
return nil showPods(app, "", "Job", sel, l.String(), "", v.backCmd)
} }
func (v *jobView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *jobView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -12,6 +12,7 @@ type nodeView struct {
func newNodeView(t string, app *appView, list resource.List) resourceViewer { func newNodeView(t string, app *appView, list resource.List) resourceViewer {
v := nodeView{newResourceView(t, app, list).(*resourceView)} v := nodeView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -19,7 +20,6 @@ func newNodeView(t string, app *appView, list resource.List) resourceViewer {
func (v *nodeView) extraActions(aa keyActions) { func (v *nodeView) extraActions(aa keyActions) {
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, false), true) aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, false), true)
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(8, false), true) aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(8, false), true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -32,14 +32,8 @@ func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcel
} }
} }
func (v *nodeView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *nodeView) showPods(app *appView, _, res, sel string) {
if !v.rowSelected() { showPods(app, "", "Node", sel, "", "spec.nodeName="+sel, v.backCmd)
return evt
}
showPods(v.app, "", "Node", v.selectedItem, "", "spec.nodeName="+v.selectedItem, v.backCmd)
return nil
} }
func (v *nodeView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *nodeView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -25,24 +25,19 @@ func newNamespaceView(t string, app *appView, list resource.List) resourceViewer
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.selectedFn = v.getSelectedItem v.selectedFn = v.getSelectedItem
v.decorateFn = v.decorate v.decorateFn = v.decorate
v.enterFn = v.switchNs
v.getTV().cleanseFn = v.cleanser v.getTV().cleanseFn = v.cleanser
return &v return &v
} }
func (v *namespaceView) extraActions(aa keyActions) { func (v *namespaceView) extraActions(aa keyActions) {
aa[tcell.KeyEnter] = newKeyAction("Switch", v.switchNsCmd, true)
aa[KeyU] = newKeyAction("Use", v.useNsCmd, true) aa[KeyU] = newKeyAction("Use", v.useNsCmd, true)
} }
func (v *namespaceView) switchNsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *namespaceView) switchNs(app *appView, _, res, sel string) {
if !v.rowSelected() { v.useNamespace(sel)
return evt app.gotoResource("po", true)
}
v.useNamespace(v.getSelectedItem())
v.app.gotoResource("po", true)
return nil
} }
func (v *namespaceView) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *namespaceView) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -22,7 +22,7 @@ func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
} }
var row int var row int
for k, rev := range table.Rows { for _, rev := range table.Rows {
ageIndex := len(rev.Fields) - 1 ageIndex := len(rev.Fields) - 1
for index, field := range rev.Fields { for index, field := range rev.Fields {
// Date field comes out as timestamp. // Date field comes out as timestamp.
@ -31,7 +31,6 @@ func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
if err == nil { if err == nil {
field = duration.HumanDuration(dur) field = duration.HumanDuration(dur)
} }
table.Rows[k].Fields[index] = field
} }
width := len(field) + colPadding width := len(field) + colPadding
if width > pads[index] { if width > pads[index] {

View File

@ -64,20 +64,17 @@ func (v *podView) extraActions(aa keyActions) {
} }
func (v *podView) listContainers(app *appView, _, res, sel string) { func (v *podView) listContainers(app *appView, _, res, sel string) {
if !v.rowSelected() {
return
}
po, err := v.app.informer.Get(watch.PodIndex, sel, metav1.GetOptions{}) po, err := v.app.informer.Get(watch.PodIndex, sel, metav1.GetOptions{})
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Unable to retrieve pod %s", sel) log.Error().Err(err).Msgf("Unable to retrieve pod %s", sel)
app.flash().errf("Unable to retrieve pods %s", err) app.flash().errf("Unable to retrieve pods %s", err)
return return
} }
pod := po.(*v1.Pod) pod := po.(*v1.Pod)
mx := k8s.NewMetricsServer(app.conn()) mx := k8s.NewMetricsServer(app.conn())
list := resource.NewContainerList(app.conn(), mx, pod) list := resource.NewContainerList(app.conn(), mx, pod)
title := skinTitle(fmt.Sprintf(containerFmt, "Containers", sel), v.app.styles.Style) title := skinTitle(fmt.Sprintf(containerFmt, "Containers", sel), app.styles.Style)
// Stop my updater // Stop my updater
if v.cancelFn != nil { if v.cancelFn != nil {

View File

@ -0,0 +1,43 @@
package views
import (
"github.com/derailed/tview"
"github.com/gdamore/tcell"
)
type portSelector struct {
title, port string
ok, cancel func()
}
func newSelector(title, port string, okFn, cancelFn func()) *portSelector {
return &portSelector{
title: title,
port: port,
ok: okFn,
cancel: cancelFn,
}
}
func (p *portSelector) show(app *appView) {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor).
SetButtonTextColor(tview.Styles.PrimaryTextColor).
SetLabelColor(tcell.ColorAqua).
SetFieldTextColor(tcell.ColorOrange)
f1 := p.port
f.AddInputField("Pod Port:", f1, 20, nil, func(changed string) {
f1 = changed
})
f.AddButton("OK", p.ok)
f.AddButton("Cancel", p.cancel)
modal := tview.NewModalForm("<"+p.title+">", f)
modal.SetDoneFunc(func(_ int, b string) {
p.cancel()
})
}

View File

@ -185,6 +185,11 @@ func (v *resourceView) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.getTV().filterCmd(evt) == nil { if v.getTV().filterCmd(evt) == nil {
return nil return nil
} }
if v.selectedItem == "" {
return nil
}
if v.enterFn != nil { if v.enterFn != nil {
v.enterFn(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem) v.enterFn(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
} else { } else {

View File

@ -23,6 +23,7 @@ type replicaSetView struct {
func newReplicaSetView(t string, app *appView, list resource.List) resourceViewer { func newReplicaSetView(t string, app *appView, list resource.List) resourceViewer {
v := replicaSetView{newResourceView(t, app, list).(*resourceView)} v := replicaSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -31,7 +32,6 @@ func (v *replicaSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
aa[tcell.KeyCtrlB] = newKeyAction("Rollback", v.rollbackCmd, true) aa[tcell.KeyCtrlB] = newKeyAction("Rollback", v.rollbackCmd, true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *replicaSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *replicaSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -44,30 +44,24 @@ func (v *replicaSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey)
} }
} }
func (v *replicaSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *replicaSetView) showPods(app *appView, ns, res, sel string) {
if !v.rowSelected() { ns, n := namespaced(sel)
return evt rset := k8s.NewReplicaSet(app.conn())
}
ns, n := namespaced(v.selectedItem)
rset := k8s.NewReplicaSet(v.app.conn())
r, err := rset.Get(ns, n) r, err := rset.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching ReplicaSet %s", v.selectedItem) log.Error().Err(err).Msgf("Fetching ReplicaSet %s", sel)
v.app.flash().errf("Replicaset failed %s", err) app.flash().errf("Replicaset failed %s", err)
return evt
} }
rs := r.(*v1.ReplicaSet) rs := r.(*v1.ReplicaSet)
l, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
sel, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", v.selectedItem) log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", sel)
v.app.flash().errf("Selector failed %s", err) app.flash().errf("Selector failed %s", err)
return evt return
} }
showPods(v.app, "", "ReplicaSet", v.selectedItem, sel.String(), "", v.backCmd)
return nil showPods(app, "", "ReplicaSet", sel, l.String(), "", v.backCmd)
} }
func (v *replicaSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *replicaSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -70,9 +70,9 @@ func less(asc bool, c1, c2 string) bool {
return !b return !b
} }
func isDurationSort(asc bool, c1, c2 string) (bool, bool) { func isDurationSort(asc bool, s1, s2 string) (bool, bool) {
d1, ok1 := isDuration(c1) d1, ok1 := isDuration(s1)
d2, ok2 := isDuration(c2) d2, ok2 := isDuration(s2)
if !ok1 || !ok2 { if !ok1 || !ok2 {
return false, false return false, false
} }
@ -80,7 +80,7 @@ func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
if asc { if asc {
return d1 <= d2, true return d1 <= d2, true
} }
return d1 > d2, true return d1 >= d2, true
} }
func isMetricSort(asc bool, c1, c2 string) (bool, bool) { func isMetricSort(asc bool, c1, c2 string) (bool, bool) {

View File

@ -33,6 +33,7 @@ func TestGroupSort(t *testing.T) {
{true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}}, {true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}},
{false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}}, {false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}},
{true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}}, {true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}},
{true, []string{"3y37d", "2y4d"}, []string{"2y4d", "3y37d"}},
} }
for _, u := range uu { for _, u := range uu {
@ -80,3 +81,24 @@ func TestRowSort(t *testing.T) {
assert.Equal(t, u.expect, r.rows) assert.Equal(t, u.expect, r.rows)
} }
} }
func TestIsDurationSort(t *testing.T) {
uu := map[string]struct {
s1, s2 string
asc, e bool
}{
"ascLess": {"10h5m", "2h10m", true, false},
"descGreater": {"10h5m", "2h10m", false, true},
"ascEqual": {"2h10m", "2h10m", true, true},
"descEqual": {"2h10m", "2h10m", false, true},
"ascGreater": {"10h10m", "2h5m", true, false},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
less, ok := isDurationSort(u.asc, u.s1, u.s2)
assert.True(t, ok)
assert.Equal(t, u.e, less)
})
}
}

View File

@ -16,6 +16,7 @@ type statefulSetView struct {
func newStatefulSetView(t string, app *appView, list resource.List) resourceViewer { func newStatefulSetView(t string, app *appView, list resource.List) resourceViewer {
v := statefulSetView{newResourceView(t, app, list).(*resourceView)} v := statefulSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -23,7 +24,6 @@ func newStatefulSetView(t string, app *appView, list resource.List) resourceView
func (v *statefulSetView) extraActions(aa keyActions) { func (v *statefulSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(1, false), true) aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(1, false), true)
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(2, false), true) aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(2, false), true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
} }
func (v *statefulSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *statefulSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -36,30 +36,25 @@ func (v *statefulSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey
} }
} }
func (v *statefulSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *statefulSetView) showPods(app *appView, ns, res, sel string) {
if !v.rowSelected() { ns, n := namespaced(sel)
return evt s := k8s.NewStatefulSet(app.conn())
} st, err := s.Get(ns, n)
ns, n := namespaced(v.selectedItem)
d := k8s.NewStatefulSet(v.app.conn())
s, err := d.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching StatefulSet %s", v.selectedItem) log.Error().Err(err).Msgf("Fetching StatefulSet %s", sel)
v.app.flash().errf("Unable to fetch statefulset %s", err) app.flash().errf("Unable to fetch statefulset %s", err)
return evt return
} }
sts := s.(*v1.StatefulSet)
sel, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector) sts := st.(*v1.StatefulSet)
l, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", v.selectedItem) log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", sel)
v.app.flash().errf("Selector failed %s", err) app.flash().errf("Selector failed %s", err)
return evt return
} }
showPods(v.app, "", "StatefulSet", v.selectedItem, sel.String(), "", v.backCmd)
return nil showPods(app, "", "StatefulSet", sel, l.String(), "", v.backCmd)
} }
func (v *statefulSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *statefulSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -22,6 +22,7 @@ type svcView struct {
func newSvcView(t string, app *appView, list resource.List) resourceViewer { func newSvcView(t string, app *appView, list resource.List) resourceViewer {
v := svcView{resourceView: newResourceView(t, app, list).(*resourceView)} v := svcView{resourceView: newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v return &v
} }
@ -29,7 +30,6 @@ func newSvcView(t string, app *appView, list resource.List) resourceViewer {
func (v *svcView) extraActions(aa keyActions) { func (v *svcView) extraActions(aa keyActions) {
aa[tcell.KeyCtrlB] = newKeyAction("Bench", v.benchCmd, true) aa[tcell.KeyCtrlB] = newKeyAction("Bench", v.benchCmd, true)
aa[KeyAltB] = newKeyAction("Bench Stop", v.benchStopCmd, true) aa[KeyAltB] = newKeyAction("Bench Stop", v.benchStopCmd, true)
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
aa[KeyShiftT] = newKeyAction("Sort Type", v.sortColCmd(1, false), true) aa[KeyShiftT] = newKeyAction("Sort Type", v.sortColCmd(1, false), true)
} }
@ -44,23 +44,19 @@ func (v *svcView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell
} }
} }
func (v *svcView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *svcView) showPods(app *appView, ns, res, sel string) {
if !v.rowSelected() { s := k8s.NewService(app.conn())
return evt ns, n := namespaced(sel)
} svc, err := s.Get(ns, n)
s := k8s.NewService(v.app.conn())
ns, n := namespaced(v.selectedItem)
res, err := s.Get(ns, n)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetch service %s", v.selectedItem) log.Error().Err(err).Msgf("Fetch service %s", sel)
return nil app.flash().err(err)
} return
if svc, ok := res.(*v1.Service); ok {
v.showSvcPods(ns, svc.Spec.Selector, v.backCmd)
} }
return nil if s, ok := svc.(*v1.Service); ok {
v.showSvcPods(ns, s.Spec.Selector, v.backCmd)
}
} }
func (v *svcView) backCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *svcView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
@ -111,15 +107,12 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text) svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text)
log.Debug().Msgf("Service Type %q", svcType)
if svcType != "NodePort" && svcType != "LoadBalancer" { if svcType != "NodePort" && svcType != "LoadBalancer" {
v.app.flash().err(errors.New("You must select a reachable service")) v.app.flash().err(errors.New("You must select a reachable service"))
return nil return nil
} }
ports := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+5).Text) ports := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+5).Text)
// BOZO!! You Brute!!
// BOZO!! Will new much improv ie pop dialog and select port if multiport.
pp := strings.Split(ports, " ") pp := strings.Split(ports, " ")
if len(pp) == 0 { if len(pp) == 0 {
v.app.flash().err(errors.New("No ports found")) v.app.flash().err(errors.New("No ports found"))

View File

@ -16,6 +16,7 @@ import (
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/util/duration"
) )
const ( const (
@ -421,16 +422,23 @@ func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string,
c := tview.NewTableCell(v.sortIndicator(col, name)) c := tview.NewTableCell(v.sortIndicator(col, name))
{ {
c.SetExpansion(1) c.SetExpansion(1)
c.SetTextColor(fg)
if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) { if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) {
c.SetAlign(tview.AlignRight) c.SetAlign(tview.AlignRight)
} }
c.SetTextColor(fg)
c.SetBackgroundColor(bg) c.SetBackgroundColor(bg)
} }
v.SetCell(0, col, c) v.SetCell(0, col, c)
} }
func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col int, field, delta string, color tcell.Color, pads maxyPad) { func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col int, field, delta string, color tcell.Color, pads maxyPad) {
if header == "AGE" {
dur, err := time.ParseDuration(field)
if err == nil {
field = duration.HumanDuration(dur)
}
}
field += deltas(delta, field) field += deltas(delta, field)
align := tview.AlignLeft align := tview.AlignLeft
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) { if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {

View File

@ -47,6 +47,16 @@ func TestTVSortRows(t *testing.T) {
resource.Row{"x", "y"}, resource.Row{"x", "y"},
[]string{"row1", "row2"}, []string{"row1", "row2"},
}, },
{
resource.RowEvents{
"row1": {Fields: resource.Row{"2175h48m0.06015s", "y"}},
"row2": {Fields: resource.Row{"403h42m34.060166s", "b"}},
},
0,
true,
resource.Row{"403h42m34.060166s", "b"},
[]string{"row2", "row1"},
},
} }
var v *tableView var v *tableView