diff --git a/internal/dao/container.go b/internal/dao/container.go index 2074607f26..20728e0077 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -6,11 +6,11 @@ package dao import ( "context" "fmt" - "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -46,14 +46,47 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error if err != nil { return nil, err } - res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) - for _, co := range po.Spec.InitContainers { - res = append(res, makeContainerRes(co, po, cmx[co.Name], true)) + + type containerGroup struct { + indexPrefix string + containers []v1.Container + statuses []v1.ContainerStatus } - for _, co := range po.Spec.Containers { - res = append(res, makeContainerRes(co, po, cmx[co.Name], false)) + containerGroups := []containerGroup{ + { + indexPrefix: "E", + containers: convertEphemeralContainersToContainers(po.Spec.EphemeralContainers), + statuses: po.Status.EphemeralContainerStatuses, + }, + { + indexPrefix: "I", + containers: po.Spec.InitContainers, + statuses: po.Status.InitContainerStatuses, + }, + { + indexPrefix: "M", + containers: po.Spec.Containers, + statuses: po.Status.ContainerStatuses, + }, } + res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.EphemeralContainers)+len(po.Spec.Containers)) + for _, group := range containerGroups { + for i, co := range group.containers { + var status *v1.ContainerStatus + if len(group.statuses) > i { + status = &group.statuses[i] + } + + res = append(res, makeContainerRes( + fmt.Sprintf("%s%d", group.indexPrefix, i+1), + &co, + status, + cmx[co.Name], + po.GetCreationTimestamp(), + )) + } + } return res, nil } @@ -68,29 +101,14 @@ func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, // ---------------------------------------------------------------------------- // Helpers... -func makeContainerRes(co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics, isInit bool) render.ContainerRes { +func makeContainerRes(index string, co *v1.Container, status *v1.ContainerStatus, cmx *mv1beta1.ContainerMetrics, age metav1.Time) render.ContainerRes { return render.ContainerRes{ - Container: &co, - Status: getContainerStatus(co.Name, po.Status), + Index: index, + Container: co, + Status: status, MX: cmx, - IsInit: isInit, - Age: po.GetCreationTimestamp(), - } -} - -func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { - for _, c := range status.ContainerStatuses { - if c.Name == co { - return &c - } - } - for _, c := range status.InitContainerStatuses { - if c.Name == co { - return &c - } + Age: age, } - - return nil } func (c *Container) fetchPod(fqn string) (*v1.Pod, error) { @@ -102,3 +120,12 @@ func (c *Container) fetchPod(fqn string) (*v1.Pod, error) { err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) return &po, err } + +// convertEphemeralContainersToContainers reduces EphemeralContainers to common fields with Containers +func convertEphemeralContainersToContainers(ecos []v1.EphemeralContainer) []v1.Container { + cos := make([]v1.Container, len(ecos)) + for i, eco := range ecos { + cos[i] = v1.Container(eco.EphemeralContainerCommon) + } + return cos +} diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index f53c82959d..43b698dfb5 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -5,6 +5,7 @@ package dao_test import ( "context" + "github.com/derailed/k9s/internal/render" "testing" "github.com/derailed/k9s/internal" @@ -34,6 +35,12 @@ func TestContainerList(t *testing.T) { oo, err := c.List(ctx, "") assert.Nil(t, err) assert.Equal(t, 1, len(oo)) + assert.IsType(t, render.ContainerRes{}, oo[0]) + cres := oo[0].(render.ContainerRes) + assert.Equal(t, "M1", cres.Index) + assert.Equal(t, "fred", cres.Container.Name) + assert.Equal(t, "fred", cres.Status.Name) + assert.NotEmpty(t, cres.Age) } // ---------------------------------------------------------------------------- diff --git a/internal/render/container.go b/internal/render/container.go index 35f700f267..cddb95e80b 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -72,12 +72,12 @@ func (c Container) ColorerFunc() model1.ColorerFunc { // Header returns a header row. func (Container) Header(ns string) model1.Header { return model1.Header{ + model1.HeaderColumn{Name: "IDX", Align: tview.AlignRight}, model1.HeaderColumn{Name: "NAME"}, model1.HeaderColumn{Name: "PF"}, model1.HeaderColumn{Name: "IMAGE"}, model1.HeaderColumn{Name: "READY"}, model1.HeaderColumn{Name: "STATE"}, - model1.HeaderColumn{Name: "INIT"}, model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, model1.HeaderColumn{Name: "PROBES(L:R)"}, model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, @@ -109,12 +109,12 @@ func (c Container) Render(o interface{}, name string, r *model1.Row) error { r.ID = co.Container.Name r.Fields = model1.Fields{ + co.Index, co.Container.Name, "●", co.Container.Image, ready, state, - boolToStr(co.IsInit), restarts, probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe), toMc(cur.cpu), @@ -238,10 +238,10 @@ func probe(p *v1.Probe) string { // ContainerRes represents a container and its metrics. type ContainerRes struct { + Index string Container *v1.Container Status *v1.ContainerStatus MX *mv1beta1.ContainerMetrics - IsInit bool Age metav1.Time } diff --git a/internal/render/container_test.go b/internal/render/container_test.go index f790b3a541..374873d7fd 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -5,6 +5,7 @@ package render_test import ( "fmt" + "sort" "testing" "time" @@ -17,26 +18,38 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) +func TestContainerRes_IndexSorting(t *testing.T) { + ephm1 := model1.Row{Fields: []string{"E1"}} + ephm10 := model1.Row{Fields: []string{"E10"}} + init1 := model1.Row{Fields: []string{"I1"}} + init2 := model1.Row{Fields: []string{"I2"}} + main1 := model1.Row{Fields: []string{"M1"}} + + rows := model1.Rows{ephm1, ephm10, init1, init2, main1} + sorter := model1.RowSorter{Rows: rows, Index: 0, Asc: true} + assert.True(t, sort.IsSorted(sorter)) +} + func TestContainer(t *testing.T) { var c render.Container cres := render.ContainerRes{ + Index: "M1", Container: makeContainer(), Status: makeContainerStatus(), MX: makeContainerMetrics(), - IsInit: false, Age: makeAge(), } var r model1.Row assert.Nil(t, c.Render(cres, "blee", &r)) assert.Equal(t, "fred", r.ID) assert.Equal(t, model1.Fields{ + "M1", "fred", "●", "img", "false", "Running", - "false", "0", "off:off", "10", @@ -58,10 +71,10 @@ func BenchmarkContainerRender(b *testing.B) { var c render.Container cres := render.ContainerRes{ + Index: "M1", Container: makeContainer(), Status: makeContainerStatus(), MX: makeContainerMetrics(), - IsInit: false, Age: makeAge(), } var r model1.Row diff --git a/internal/view/container.go b/internal/view/container.go index 01f4f1e5e5..868e79314f 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -35,6 +35,7 @@ func NewContainer(gvr client.GVR) ResourceViewer { c.GetTable().SetDecorateFn(c.decorateRows) c.AddBindKeysFn(c.bindKeys) c.GetTable().SetDecorateFn(c.portForwardIndicator) + c.GetTable().SetSortCol("IDX", true) return &c } @@ -90,6 +91,7 @@ func (c *Container) bindKeys(aa *ui.KeyActions) { ui.KeyF: ui.NewKeyAction("Show PortForward", c.showPFCmd, true), ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", c.GetTable().SortColCmd("RESTARTS", false), false), + ui.KeyShiftI: ui.NewKeyAction("Sort Index", c.GetTable().SortColCmd("IDX", true), false), }) aa.Merge(resourceSorters(c.GetTable())) } diff --git a/internal/view/container_test.go b/internal/view/container_test.go index cc1133e88a..84787f6d1b 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -16,5 +16,5 @@ func TestContainerNew(t *testing.T) { assert.Nil(t, c.Init(makeCtx())) assert.Equal(t, "Containers", c.Name()) - assert.Equal(t, 18, len(c.Hints())) + assert.Equal(t, 19, len(c.Hints())) }