k9s/internal/view/pod.go

169 lines
4.6 KiB
Go

package view
import (
"errors"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
const (
podTitle = "Pods"
shellCheck = "command -v bash >/dev/null && exec bash || exec sh"
)
// Pod represents a pod viewer.
type Pod struct {
ResourceViewer
}
// NewPod returns a new viewer.
func NewPod(title, gvr string, list resource.List) ResourceViewer {
p := Pod{
ResourceViewer: NewLogsExtender(
NewResource(podTitle, gvr, list),
func() string { return "" },
),
}
p.BindKeys()
p.GetTable().SetEnterFn(p.showContainers)
return &p
}
func (p *Pod) BindKeys() {
p.Actions().Add(ui.KeyActions{
tcell.KeyCtrlK: ui.NewKeyAction("Kill", p.killCmd, true),
ui.KeyS: ui.NewKeyAction("Shell", p.shellCmd, true),
ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(1, true), false),
ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd(2, true), false),
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd(3, false), false),
ui.KeyShiftC: ui.NewKeyAction("Sort CPU", p.GetTable().SortColCmd(4, false), false),
ui.KeyShiftM: ui.NewKeyAction("Sort MEM", p.GetTable().SortColCmd(5, false), false),
ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", p.GetTable().SortColCmd(6, false), false),
ui.KeyShiftZ: ui.NewKeyAction("Sort MEM%", p.GetTable().SortColCmd(7, false), false),
ui.KeyShiftI: ui.NewKeyAction("Sort IP", p.GetTable().SortColCmd(8, true), false),
ui.KeyShiftO: ui.NewKeyAction("Sort Node", p.GetTable().SortColCmd(9, true), false),
})
}
func (p *Pod) showContainers(app *App, ns, res, sel string) {
ns, n := namespaced(sel)
o, err := p.App().factory.Get(ns, "v1/pods", n, labels.Everything())
if err != nil {
app.Flash().Err(err)
log.Error().Err(err).Msgf("Pod %s not found", sel)
return
}
var pod v1.Pod
if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod); err != nil {
app.Flash().Err(err)
}
list := resource.NewContainerList(app.Conn(), &pod)
// Spawn child view
p.App().inject(NewContainer(fqn(pod.Namespace, pod.Name), list))
}
// Protocol...
func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey {
sels := p.GetTable().GetSelectedItems()
if len(sels) == 0 {
return evt
}
p.GetTable().ShowDeleted()
for _, res := range sels {
p.App().Flash().Infof("Delete resource %s %s", p.List().GetName(), res)
if err := p.List().Resource().Delete(res, true, false); err != nil {
p.App().Flash().Errf("Delete failed with %s", err)
} else {
p.App().forwarders.Kill(res)
}
}
p.Refresh()
return nil
}
func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := p.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
row := p.GetTable().GetSelectedRowIndex()
status := ui.TrimCell(p.GetTable().SelectTable, row, p.GetTable().NameColIndex()+2)
if status != render.Running {
p.App().Flash().Errf("%s is not in a running state", sel)
return nil
}
cc, err := fetchContainers(p.List(), sel, false)
if err != nil {
p.App().Flash().Errf("Unable to retrieve containers %s", err)
return evt
}
if len(cc) == 1 {
p.shellIn(sel, "")
return nil
}
picker := NewPicker()
picker.populate(cc)
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
p.shellIn(sel, t)
})
p.App().inject(picker)
return evt
}
func (p *Pod) shellIn(path, co string) {
p.Stop()
shellIn(p.App(), path, co)
p.Start()
}
// ----------------------------------------------------------------------------
// Helpers...
func fetchContainers(l resource.List, po string, includeInit bool) ([]string, error) {
if len(po) == 0 {
return []string{}, nil
}
return l.Resource().(resource.Containers).Containers(po, includeInit)
}
func shellIn(a *App, path, co string) {
args := computeShellArgs(path, co, a.Config.K9s.CurrentContext, a.Conn().Config().Flags().KubeConfig)
log.Debug().Msgf("Shell args %v", args)
if !runK(true, a, args...) {
a.Flash().Err(errors.New("Shell exec failed"))
}
}
func computeShellArgs(path, co, context string, kcfg *string) []string {
args := make([]string, 0, 15)
args = append(args, "exec", "-it")
args = append(args, "--context", context)
ns, po := namespaced(path)
args = append(args, "-n", ns)
args = append(args, po)
if kcfg != nil && *kcfg != "" {
args = append(args, "--kubeconfig", *kcfg)
}
if co != "" {
args = append(args, "-c", co)
}
return append(args, "--", "sh", "-c", shellCheck)
}