194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
package view
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/derailed/k9s/internal/client"
|
|
"github.com/derailed/k9s/internal/dao"
|
|
"github.com/derailed/k9s/internal/render"
|
|
"github.com/derailed/k9s/internal/ui"
|
|
"github.com/derailed/k9s/internal/ui/dialog"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rs/zerolog/log"
|
|
"k8s.io/client-go/tools/portforward"
|
|
)
|
|
|
|
const (
|
|
containerTitle = "Containers"
|
|
portsCol = 13
|
|
)
|
|
|
|
// Container represents a container view.
|
|
type Container struct {
|
|
ResourceViewer
|
|
}
|
|
|
|
// NewContainer returns a new container view.
|
|
func NewContainer(gvr client.GVR) ResourceViewer {
|
|
c := Container{}
|
|
c.ResourceViewer = NewLogsExtender(NewBrowser(gvr), c.selectedContainer)
|
|
c.SetEnvFn(c.k9sEnv)
|
|
c.GetTable().SetEnterFn(c.viewLogs)
|
|
c.GetTable().SetColorerFn(render.Container{}.ColorerFunc())
|
|
c.SetBindKeysFn(c.bindKeys)
|
|
|
|
return &c
|
|
}
|
|
|
|
// Name returns the component name.
|
|
func (c *Container) Name() string { return containerTitle }
|
|
|
|
func (c *Container) bindDangerousKeys(aa ui.KeyActions) {
|
|
aa.Add(ui.KeyActions{
|
|
ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true),
|
|
})
|
|
}
|
|
|
|
func (c *Container) bindKeys(aa ui.KeyActions) {
|
|
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
|
|
|
|
if !c.App().Config.K9s.GetReadOnly() {
|
|
c.bindDangerousKeys(aa)
|
|
}
|
|
|
|
aa.Add(ui.KeyActions{
|
|
ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true),
|
|
ui.KeyShiftC: ui.NewKeyAction("Sort CPU", c.GetTable().SortColCmd(6, false), false),
|
|
ui.KeyShiftM: ui.NewKeyAction("Sort MEM", c.GetTable().SortColCmd(7, false), false),
|
|
ui.KeyShiftX: ui.NewKeyAction("Sort %CPU (REQ)", c.GetTable().SortColCmd(8, false), false),
|
|
ui.KeyShiftZ: ui.NewKeyAction("Sort %MEM (REQ)", c.GetTable().SortColCmd(9, false), false),
|
|
tcell.KeyCtrlX: ui.NewKeyAction("Sort %CPU (LIM)", c.GetTable().SortColCmd(8, false), false),
|
|
tcell.KeyCtrlZ: ui.NewKeyAction("Sort %MEM (LIM)", c.GetTable().SortColCmd(9, false), false),
|
|
})
|
|
}
|
|
|
|
func (c *Container) k9sEnv() K9sEnv {
|
|
env := defaultK9sEnv(c.App(), c.GetTable().GetSelectedItem(), c.GetTable().GetSelectedRow())
|
|
ns, n := client.Namespaced(c.GetTable().Path)
|
|
env["POD"] = n
|
|
env["NAMESPACE"] = ns
|
|
|
|
return env
|
|
}
|
|
|
|
func (c *Container) selectedContainer() string {
|
|
tokens := strings.Split(c.GetTable().GetSelectedItem(), "/")
|
|
return tokens[0]
|
|
}
|
|
|
|
func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) {
|
|
c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false)
|
|
}
|
|
|
|
// Handlers...
|
|
|
|
func (c *Container) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
sel := c.GetTable().GetSelectedItem()
|
|
if sel == "" {
|
|
return evt
|
|
}
|
|
|
|
c.Stop()
|
|
defer c.Start()
|
|
shellIn(c.App(), c.GetTable().Path, sel)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
path := c.GetTable().GetSelectedItem()
|
|
if path == "" {
|
|
return evt
|
|
}
|
|
|
|
if _, ok := c.App().factory.ForwarderFor(fwFQN(c.GetTable().Path, path)); ok {
|
|
c.App().Flash().Err(fmt.Errorf("A PortForward already exist on container %s", c.GetTable().Path))
|
|
return nil
|
|
}
|
|
|
|
ports, ok := c.isForwardable(path)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
dialog.ShowPortForward(c.App().Content.Pages, c.preparePort(ports), c.portForward)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) isForwardable(path string) ([]string, bool) {
|
|
state := c.GetTable().GetSelectedCell(3)
|
|
if state != "Running" {
|
|
c.App().Flash().Err(fmt.Errorf("Container %s is not running?", path))
|
|
return nil, false
|
|
}
|
|
|
|
portC := c.GetTable().GetSelectedCell(portsCol)
|
|
ports := strings.Split(portC, ",")
|
|
if len(ports) == 0 {
|
|
c.App().Flash().Err(errors.New("Container exposes no ports"))
|
|
return nil, false
|
|
}
|
|
|
|
return ports, true
|
|
}
|
|
|
|
func (c *Container) preparePort(pp []string) string {
|
|
var port string
|
|
for _, p := range pp {
|
|
if !isTCPPort(p) {
|
|
continue
|
|
}
|
|
port = strings.TrimSpace(p)
|
|
tokens := strings.Split(port, ":")
|
|
if len(tokens) == 2 {
|
|
port = tokens[1]
|
|
}
|
|
break
|
|
}
|
|
if port == "" {
|
|
c.App().Flash().Warn("No valid TCP port found on this container. User will specify...")
|
|
return "MY_TCP_PORT!"
|
|
}
|
|
|
|
return port
|
|
}
|
|
|
|
func (c *Container) portForward(address, lport, cport string) {
|
|
co := c.GetTable().GetSelectedCell(0)
|
|
pf := dao.NewPortForwarder(c.App().Conn())
|
|
path := c.GetTable().GetSelectedItem()
|
|
ports := []string{lport + ":" + cport}
|
|
fw, err := pf.Start(path, co, address, ports)
|
|
if err != nil {
|
|
c.App().Flash().Err(err)
|
|
return
|
|
}
|
|
|
|
log.Debug().Msgf(">>> Starting port forward %q %v", path, ports)
|
|
go runForward(c.App(), pf, fw)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Helpers...
|
|
|
|
func runForward(a *App, pf *dao.PortForwarder, f *portforward.PortForwarder) {
|
|
a.QueueUpdateDraw(func() {
|
|
a.factory.AddForwarder(pf)
|
|
a.Flash().Infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0])
|
|
dialog.DismissPortForward(a.Content.Pages)
|
|
})
|
|
|
|
pf.SetActive(true)
|
|
if err := f.ForwardPorts(); err != nil {
|
|
a.Flash().Err(err)
|
|
return
|
|
}
|
|
a.QueueUpdateDraw(func() {
|
|
a.factory.DeleteForwarder(pf.FQN())
|
|
pf.SetActive(false)
|
|
})
|
|
}
|