From a637d494bfa6f7e5d589fd535f9961553ccc9f6e Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sat, 2 Oct 2021 13:00:05 -0600 Subject: [PATCH] fix #1258 (#1270) --- go.mod | 2 +- go.sum | 4 +-- internal/view/container.go | 25 +++++++++++++++-- internal/view/helpers.go | 9 +++++++ internal/view/helpers_test.go | 35 ++++++++++++++++++++++++ internal/view/pf_dialog.go | 51 ++++++++++++++++++++++++++++------- internal/view/pf_extender.go | 14 +++++----- 7 files changed, 119 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 9d7f78ca..b9ae6990 100644 --- a/go.mod +++ b/go.mod @@ -157,7 +157,7 @@ require ( golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index d6ec48a8..d5dd4c96 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/adrg/xdg v0.3.0 h1:BO+k4wFj0IoTolBF1Apn8oZrX3LQrEbBA8+/9vyW9J4= -github.com/adrg/xdg v0.3.0/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= +github.com/adrg/xdg v0.3.4 h1:0BivHfQ0LSGQrFTaEZ0hyQLm/HAidci7m+1cT6wKKdA= +github.com/adrg/xdg v0.3.4/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/internal/view/container.go b/internal/view/container.go index b51bd55a..33023d46 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -182,7 +182,7 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { if !ok { return nil } - ShowPortForwards(c, c.GetTable().Path, ports, startFwdCB) + ShowPortForwards(c, c.GetTable().Path, ports, "", startFwdCB) return nil } @@ -193,8 +193,8 @@ func (c *Container) isForwardable(path string) ([]string, bool) { return nil, false } - cc := po.Spec.Containers var co *v1.Container + cc := po.Spec.Containers for i := range cc { if cc[i].Name == path { co = &cc[i] @@ -230,6 +230,14 @@ func (c *Container) isForwardable(path string) ([]string, bool) { } pp := make([]string, 0, len(ports)) + container, port, ok := parsePFAnn(po.Annotations[AnnDefaultPF]) + if ok && container == path { + if index := indexOfPort(ports, port); index != -1 { + pp = append(pp, path+"/"+port) + ports = append(ports[:index], ports[index+1:]...) + } + } + for _, p := range ports { if !isTCPPort(p) { continue @@ -243,3 +251,16 @@ func (c *Container) isForwardable(path string) ([]string, bool) { return pp, true } + +func indexOfPort(pp []string, port string) int { + for i, p := range pp { + tokens := strings.Split(p, ":") + if len(tokens) == 2 { + if tokens[0] == port || tokens[1] == port { + return i + } + } + } + + return -1 +} diff --git a/internal/view/helpers.go b/internal/view/helpers.go index fcd5c7c1..42a972ad 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -17,6 +17,15 @@ import ( "github.com/rs/zerolog/log" ) +func parsePFAnn(s string) (string, string, bool) { + tokens := strings.Split(s, ":") + if len(tokens) != 2 { + return "", "", false + } + + return tokens[0], tokens[1], true +} + func k8sEnv(c *client.Config) Env { ctx, err := c.CurrentContextName() if err != nil { diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 26aa6aa1..4827e418 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -19,6 +19,41 @@ func init() { zerolog.SetGlobalLevel(zerolog.Disabled) } +func TestParsePFAnn(t *testing.T) { + uu := map[string]struct { + ann, co, port string + ok bool + }{ + "named-port": { + ann: "fred:blee", + co: "fred", + port: "blee", + ok: true, + }, + "port-num": { + ann: "fred:1234", + co: "fred", + port: "1234", + ok: true, + }, + "toast": { + ann: "zorg", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + co, port, ok := parsePFAnn(u.ann) + if u.ok { + assert.Equal(t, u.co, co) + assert.Equal(t, u.port, port) + assert.Equal(t, u.ok, ok) + } + }) + } +} + func TestExtractApp(t *testing.T) { app := NewApp(config.NewConfig(nil)) diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index ae00dc9a..e84e0aee 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -2,6 +2,7 @@ package view import ( "fmt" + "math" "regexp" "strings" @@ -17,7 +18,7 @@ const portForwardKey = "portforward" type PortForwardCB func(v ResourceViewer, path, co string, mapper []client.PortTunnel) // ShowPortForwards pops a port forwarding configuration dialog. -func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortForwardCB) { +func ShowPortForwards(v ResourceViewer, path string, ports []string, ann string, okFn PortForwardCB) { styles := v.App().Styles.Dialog() f := tview.NewForm() @@ -34,15 +35,35 @@ func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortFo var p1, p2 string if len(ports) > 0 { p1, p2 = ports[0], extractPort(ports[0]) + if len(ann) != 0 { + container, port, ok := parsePFAnn(ann) + if ok { + for _, p := range ports { + co, po, portNum := parsePort(p) + if co == container && port == po || port == portNum { + p1, p2 = p, extractPort(p) + break + } + } + } + } } - - f.AddInputField("Container Port:", p1, 30, nil, func(p string) { + fieldLen := int(math.Max(30, float64(len(p1)))) + f.AddInputField("Container Port:", p1, fieldLen, nil, func(p string) { p1 = p }) - f.AddInputField("Local Port:", p2, 30, nil, func(p string) { + field := f.GetFormItemByLabel("Container Port:").(*tview.InputField) + if field.GetText() == "" { + field.SetPlaceholder("Enter a container name/port") + } + f.AddInputField("Local Port:", p2, fieldLen, nil, func(p string) { p2 = p }) - f.AddInputField("Address:", address, 30, nil, func(h string) { + field = f.GetFormItemByLabel("Local Port:").(*tview.InputField) + if field.GetText() == "" { + field.SetPlaceholder("Enter a local port") + } + f.AddInputField("Address:", address, fieldLen, nil, func(h string) { address = h }) for i := 0; i < 3; i++ { @@ -84,12 +105,12 @@ func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortFo b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color()) } - modal := tview.NewModalForm(fmt.Sprintf("", path), f) - - if len(ports) != 0 { - modal.SetText("Exposed Ports: " + strings.Join(ports, ",")) + modal := tview.NewModalForm("", f) + msg := path + if len(ports) > 1 { + msg += "\n\nExposed Ports:\n" + strings.Join(ports, "\n") } - + modal.SetText(msg) modal.SetTextColor(styles.FgColor.Color()) modal.SetBackgroundColor(styles.BgColor.Color()) modal.SetDoneFunc(func(_ int, b string) { @@ -110,6 +131,16 @@ func DismissPortForwards(v ResourceViewer, p *ui.Pages) { // ---------------------------------------------------------------------------- // Helpers... +func parsePort(p string) (string, string, string) { + rx := regexp.MustCompile(`\A([\w|-]+)/?([\w|-]+)?:?(\d+)?(╱UDP)?\z`) + mm := rx.FindStringSubmatch(p) + if len(mm) != 5 { + return "", "", "" + } + + return mm[1], mm[2], mm[3] +} + func extractPort(p string) string { rx := regexp.MustCompile(`\A([\w|-]+)/?([\w|-]+)?:?(\d+)?(╱UDP)?\z`) mm := rx.FindStringSubmatch(p) diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 111910ac..3740d585 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -19,6 +19,8 @@ import ( "k8s.io/client-go/tools/portforward" ) +const AnnDefaultPF = "k9s.imhotep.io/default-portforward-container" + // PortForwardExtender adds port-forward extensions. type PortForwardExtender struct { ResourceViewer @@ -130,7 +132,7 @@ func startFwdCB(v ResourceViewer, path, co string, tt []client.PortTunnel) { } func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { - mm, err := fetchPodPorts(v.App().factory, path) + mm, coPort, err := fetchPodPorts(v.App().factory, path) if err != nil { return err } @@ -143,22 +145,22 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { ports = append(ports, client.FQN(co, p.Name)+":"+strconv.Itoa(int(p.ContainerPort))) } } - ShowPortForwards(v, path, ports, cb) + ShowPortForwards(v, path, ports, coPort, cb) return nil } -func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort, error) { +func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort, string, error) { log.Debug().Msgf("Fetching ports on pod %q", path) o, err := f.Get("v1/pods", path, true, labels.Everything()) if err != nil { - return nil, err + return nil, "", err } var pod v1.Pod err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod) if err != nil { - return nil, err + return nil, "", err } pp := make(map[string][]v1.ContainerPort) @@ -166,5 +168,5 @@ func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort pp[co.Name] = co.Ports } - return pp, nil + return pp, pod.Annotations[AnnDefaultPF], nil }