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 {
return &TableHeader{
FgColor: "white",
BgColor: "black",
BgColor: "red",
SorterColor: "aqua",
}
}

View File

@ -80,23 +80,6 @@ func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
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) {
crConfig := r.RestConfigOrDie()
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 {
if p == nil {
return "on"
return "off"
}
return "off"
return "on"
}
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
}
if len(ii) != 1 {
if len(ii) == 0 {
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.extraActionsFn = v.extraActions
v.enterFn = v.viewLogs
v.colorerFn = containerColorer
v.current = app.content.GetPrimitive("main").(igniter)
v.exitFn = exitFn
@ -45,7 +46,6 @@ func (v *containerView) extraActions(aa keyActions) {
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
aa[tcell.KeyEscape] = newKeyAction("Back", 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[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(7, false), true)
aa[KeyAltC] = newKeyAction("Sort CPU%", v.sortColCmd(8, false), true)
@ -72,18 +72,22 @@ func (v *containerView) getSelection() string {
// 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 {
if !v.rowSelected() {
return evt
}
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 evt
}
v.showLogs(v.selectedItem, v.list.GetName(), v, false)
v.viewLogs(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
return nil
}

View File

@ -4,7 +4,6 @@ import (
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
)
type contextView struct {
@ -14,6 +13,7 @@ type contextView struct {
func newContextView(t string, app *appView, list resource.List) resourceViewer {
v := contextView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.useCtx
v.getTV().cleanseFn = v.cleanser
return &v
@ -21,21 +21,14 @@ func newContextView(t string, app *appView, list resource.List) resourceViewer {
func (v *contextView) extraActions(aa keyActions) {
delete(v.getTV().actions, KeyShiftA)
aa[tcell.KeyEnter] = newKeyAction("Switch", v.useCmd, true)
}
func (v *contextView) useCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
func (v *contextView) useCtx(app *appView, _, res, sel string) {
if err := v.useContext(sel); err != nil {
app.flash().err(err)
return
}
if err := v.useContext(v.selectedItem); err != nil {
v.app.flash().err(err)
return evt
}
v.app.gotoResource("po", true)
return nil
app.gotoResource("po", true)
}
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 {
v := deployView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -23,7 +24,6 @@ func newDeployView(t string, app *appView, list resource.List) resourceViewer {
func (v *deployView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, 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 {
@ -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 {
if !v.rowSelected() {
return evt
}
ns, n := namespaced(v.selectedItem)
d := k8s.NewDeployment(v.app.conn())
func (v *deployView) showPods(app *appView, _, res, sel string) {
ns, n := namespaced(sel)
d := k8s.NewDeployment(app.conn())
dep, err := d.Get(ns, n)
if err != nil {
log.Error().Err(err).Msgf("Fetching Deployment %s", v.selectedItem)
v.app.flash().err(err)
return evt
log.Error().Err(err).Msgf("Fetching Deployment %s", sel)
app.flash().err(err)
return
}
dp := dep.(*v1.Deployment)
sel, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
l, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
if err != nil {
log.Error().Err(err).Msgf("Converting selector for Deployment %s", v.selectedItem)
v.app.flash().err(err)
return evt
log.Error().Err(err).Msgf("Converting selector for Deployment %s", sel)
app.flash().err(err)
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 {

View File

@ -16,6 +16,7 @@ type daemonSetView struct {
func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer {
v := daemonSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -23,7 +24,6 @@ func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer
func (v *daemonSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, 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 {
@ -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 {
if !v.rowSelected() {
return evt
}
ns, n := namespaced(v.selectedItem)
d := k8s.NewDaemonSet(v.app.conn())
func (v *daemonSetView) showPods(app *appView, _, res, sel string) {
ns, n := namespaced(sel)
d := k8s.NewDaemonSet(app.conn())
dset, err := d.Get(ns, n)
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)
return evt
return
}
ds := dset.(*extv1beta1.DaemonSet)
sel, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
l, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", v.selectedItem)
v.app.flash().err(err)
return evt
log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", sel)
app.flash().err(err)
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 {

View File

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

View File

@ -1,7 +1,6 @@
package views
import (
"fmt"
"testing"
"github.com/derailed/k9s/internal/config"
@ -106,7 +105,6 @@ func TestStripPort(t *testing.T) {
for k, u := range uu {
t.Run(k, func(t *testing.T) {
fmt.Println("TCP?", u.port, isTCPPort(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 {
v := jobView{resourceView: newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
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) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, 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 {
if !v.rowSelected() {
return evt
}
ns, n := namespaced(v.selectedItem)
j := k8s.NewJob(v.app.conn())
func (v *jobView) showPods(app *appView, _, res, sel string) {
ns, n := namespaced(sel)
j := k8s.NewJob(app.conn())
job, err := j.Get(ns, n)
if err != nil {
log.Error().Err(err).Msgf("Fetching Job %s", v.selectedItem)
v.app.flash().err(err)
return evt
log.Error().Err(err).Msgf("Fetching Job %s", sel)
app.flash().err(err)
return
}
jo := job.(*batchv1.Job)
sel, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
l, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
if err != nil {
log.Error().Err(err).Msgf("Converting selector for Job %s", v.selectedItem)
v.app.flash().err(err)
return evt
log.Error().Err(err).Msgf("Converting selector for Job %s", sel)
app.flash().err(err)
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 {

View File

@ -12,6 +12,7 @@ type nodeView struct {
func newNodeView(t string, app *appView, list resource.List) resourceViewer {
v := nodeView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -19,7 +20,6 @@ func newNodeView(t string, app *appView, list resource.List) resourceViewer {
func (v *nodeView) extraActions(aa keyActions) {
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, 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 {
@ -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 {
if !v.rowSelected() {
return evt
}
showPods(v.app, "", "Node", v.selectedItem, "", "spec.nodeName="+v.selectedItem, v.backCmd)
return nil
func (v *nodeView) showPods(app *appView, _, res, sel string) {
showPods(app, "", "Node", sel, "", "spec.nodeName="+sel, v.backCmd)
}
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.selectedFn = v.getSelectedItem
v.decorateFn = v.decorate
v.enterFn = v.switchNs
v.getTV().cleanseFn = v.cleanser
return &v
}
func (v *namespaceView) extraActions(aa keyActions) {
aa[tcell.KeyEnter] = newKeyAction("Switch", v.switchNsCmd, true)
aa[KeyU] = newKeyAction("Use", v.useNsCmd, true)
}
func (v *namespaceView) switchNsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
v.useNamespace(v.getSelectedItem())
v.app.gotoResource("po", true)
return nil
func (v *namespaceView) switchNs(app *appView, _, res, sel string) {
v.useNamespace(sel)
app.gotoResource("po", true)
}
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
for k, rev := range table.Rows {
for _, rev := range table.Rows {
ageIndex := len(rev.Fields) - 1
for index, field := range rev.Fields {
// Date field comes out as timestamp.
@ -31,7 +31,6 @@ func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
if err == nil {
field = duration.HumanDuration(dur)
}
table.Rows[k].Fields[index] = field
}
width := len(field) + colPadding
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) {
if !v.rowSelected() {
return
}
po, err := v.app.informer.Get(watch.PodIndex, sel, metav1.GetOptions{})
if err != nil {
log.Error().Err(err).Msgf("Unable to retrieve pod %s", sel)
app.flash().errf("Unable to retrieve pods %s", err)
return
}
pod := po.(*v1.Pod)
mx := k8s.NewMetricsServer(app.conn())
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
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 {
return nil
}
if v.selectedItem == "" {
return nil
}
if v.enterFn != nil {
v.enterFn(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
} else {

View File

@ -23,6 +23,7 @@ type replicaSetView struct {
func newReplicaSetView(t string, app *appView, list resource.List) resourceViewer {
v := replicaSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -31,7 +32,6 @@ func (v *replicaSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), 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 {
@ -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 {
if !v.rowSelected() {
return evt
}
ns, n := namespaced(v.selectedItem)
rset := k8s.NewReplicaSet(v.app.conn())
func (v *replicaSetView) showPods(app *appView, ns, res, sel string) {
ns, n := namespaced(sel)
rset := k8s.NewReplicaSet(app.conn())
r, err := rset.Get(ns, n)
if err != nil {
log.Error().Err(err).Msgf("Fetching ReplicaSet %s", v.selectedItem)
v.app.flash().errf("Replicaset failed %s", err)
return evt
log.Error().Err(err).Msgf("Fetching ReplicaSet %s", sel)
app.flash().errf("Replicaset failed %s", err)
}
rs := r.(*v1.ReplicaSet)
sel, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
l, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
if err != nil {
log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", v.selectedItem)
v.app.flash().errf("Selector failed %s", err)
return evt
log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", sel)
app.flash().errf("Selector failed %s", err)
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 {

View File

@ -70,9 +70,9 @@ func less(asc bool, c1, c2 string) bool {
return !b
}
func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
d1, ok1 := isDuration(c1)
d2, ok2 := isDuration(c2)
func isDurationSort(asc bool, s1, s2 string) (bool, bool) {
d1, ok1 := isDuration(s1)
d2, ok2 := isDuration(s2)
if !ok1 || !ok2 {
return false, false
}
@ -80,7 +80,7 @@ func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
if asc {
return d1 <= d2, true
}
return d1 > d2, true
return d1 >= d2, true
}
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"}},
{false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}},
{true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}},
{true, []string{"3y37d", "2y4d"}, []string{"2y4d", "3y37d"}},
}
for _, u := range uu {
@ -80,3 +81,24 @@ func TestRowSort(t *testing.T) {
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 {
v := statefulSetView{newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -23,7 +24,6 @@ func newStatefulSetView(t string, app *appView, list resource.List) resourceView
func (v *statefulSetView) extraActions(aa keyActions) {
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(1, 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 {
@ -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 {
if !v.rowSelected() {
return evt
}
ns, n := namespaced(v.selectedItem)
d := k8s.NewStatefulSet(v.app.conn())
s, err := d.Get(ns, n)
func (v *statefulSetView) showPods(app *appView, ns, res, sel string) {
ns, n := namespaced(sel)
s := k8s.NewStatefulSet(app.conn())
st, err := s.Get(ns, n)
if err != nil {
log.Error().Err(err).Msgf("Fetching StatefulSet %s", v.selectedItem)
v.app.flash().errf("Unable to fetch statefulset %s", err)
return evt
log.Error().Err(err).Msgf("Fetching StatefulSet %s", sel)
app.flash().errf("Unable to fetch statefulset %s", err)
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 {
log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", v.selectedItem)
v.app.flash().errf("Selector failed %s", err)
return evt
log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", sel)
app.flash().errf("Selector failed %s", err)
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 {

View File

@ -22,6 +22,7 @@ type svcView struct {
func newSvcView(t string, app *appView, list resource.List) resourceViewer {
v := svcView{resourceView: newResourceView(t, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions
v.enterFn = v.showPods
return &v
}
@ -29,7 +30,6 @@ func newSvcView(t string, app *appView, list resource.List) resourceViewer {
func (v *svcView) extraActions(aa keyActions) {
aa[tcell.KeyCtrlB] = newKeyAction("Bench", v.benchCmd, 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)
}
@ -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 {
if !v.rowSelected() {
return evt
}
s := k8s.NewService(v.app.conn())
ns, n := namespaced(v.selectedItem)
res, err := s.Get(ns, n)
func (v *svcView) showPods(app *appView, ns, res, sel string) {
s := k8s.NewService(app.conn())
ns, n := namespaced(sel)
svc, err := s.Get(ns, n)
if err != nil {
log.Error().Err(err).Msgf("Fetch service %s", v.selectedItem)
return nil
}
if svc, ok := res.(*v1.Service); ok {
v.showSvcPods(ns, svc.Spec.Selector, v.backCmd)
log.Error().Err(err).Msgf("Fetch service %s", sel)
app.flash().err(err)
return
}
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 {
@ -111,15 +107,12 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
}
svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text)
log.Debug().Msgf("Service Type %q", svcType)
if svcType != "NodePort" && svcType != "LoadBalancer" {
v.app.flash().err(errors.New("You must select a reachable service"))
return nil
}
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, " ")
if len(pp) == 0 {
v.app.flash().err(errors.New("No ports found"))

View File

@ -16,6 +16,7 @@ import (
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/util/duration"
)
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.SetExpansion(1)
c.SetTextColor(fg)
if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) {
c.SetAlign(tview.AlignRight)
}
c.SetTextColor(fg)
c.SetBackgroundColor(bg)
}
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) {
if header == "AGE" {
dur, err := time.ParseDuration(field)
if err == nil {
field = duration.HumanDuration(dur)
}
}
field += deltas(delta, field)
align := tview.AlignLeft
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {

View File

@ -47,6 +47,16 @@ func TestTVSortRows(t *testing.T) {
resource.Row{"x", "y"},
[]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