checkpoint
parent
9d23488ff5
commit
c885495ef4
|
|
@ -0,0 +1,53 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sHotKeys manages K9s hotKeys.
|
||||
var K9sHotKeys = filepath.Join(K9sHome, "hotkey.yml")
|
||||
|
||||
// HotKeys represents a collection of plugins.
|
||||
type HotKeys struct {
|
||||
HotKey map[string]HotKey `yaml:"hotKey"`
|
||||
}
|
||||
|
||||
// HotKey describes a K9s hotkey.
|
||||
type HotKey struct {
|
||||
ShortCut string `yaml:"shortCut"`
|
||||
Description string `yaml:"description"`
|
||||
Command string `yaml:"command"`
|
||||
}
|
||||
|
||||
// NewHotKeys returns a new plugin.
|
||||
func NewHotKeys() HotKeys {
|
||||
return HotKeys{
|
||||
HotKey: make(map[string]HotKey),
|
||||
}
|
||||
}
|
||||
|
||||
// Load K9s plugins.
|
||||
func (h HotKeys) Load() error {
|
||||
return h.LoadHotKeys(K9sHotKeys)
|
||||
}
|
||||
|
||||
// LoadHotKeys loads plugins from a given file.
|
||||
func (h HotKeys) LoadHotKeys(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hh HotKeys
|
||||
if err := yaml.Unmarshal(f, &hh); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range hh.HotKey {
|
||||
h.HotKey[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHotKeyLoad(t *testing.T) {
|
||||
h := config.NewHotKeys()
|
||||
assert.Nil(t, h.LoadHotKeys("test_assets/hot_key.yml"))
|
||||
|
||||
assert.Equal(t, 1, len(h.HotKey))
|
||||
|
||||
k, ok := h.HotKey["pods"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "shift-0", k.ShortCut)
|
||||
assert.Equal(t, "Launch pod view", k.Description)
|
||||
assert.Equal(t, "pods", k.Command)
|
||||
}
|
||||
|
|
@ -12,4 +12,11 @@ func TestPluginLoad(t *testing.T) {
|
|||
assert.Nil(t, p.LoadPlugins("test_assets/plugin.yml"))
|
||||
|
||||
assert.Equal(t, 1, len(p.Plugin))
|
||||
k, ok := p.Plugin["blah"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "shift-s", k.ShortCut)
|
||||
assert.Equal(t, "blee", k.Description)
|
||||
assert.Equal(t, []string{"po", "dp"}, k.Scopes)
|
||||
assert.Equal(t, "duh", k.Command)
|
||||
assert.Equal(t, []string{"-n", "$NAMESPACE", "-boolean"}, k.Args)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
hotKey:
|
||||
pods:
|
||||
shortCut: shift-0
|
||||
description: Launch pod view
|
||||
command: pods
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -87,7 +88,7 @@ func (p *PortForwarder) FQN() string {
|
|||
}
|
||||
|
||||
// Start initiates a port forward session for a given pod and ports.
|
||||
func (p *PortForwarder) Start(path, co string, ports []string) (*portforward.PortForwarder, error) {
|
||||
func (p *PortForwarder) Start(path, co, address string, ports []string) (*portforward.PortForwarder, error) {
|
||||
p.path, p.container, p.ports, p.age = path, co, ports, time.Now()
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
|
|
@ -115,10 +116,10 @@ func (p *PortForwarder) Start(path, co string, ports []string) (*portforward.Por
|
|||
Name(n).
|
||||
SubResource("portforward")
|
||||
|
||||
return p.forwardPorts("POST", req.URL(), ports)
|
||||
return p.forwardPorts("POST", req.URL(), address, ports)
|
||||
}
|
||||
|
||||
func (p *PortForwarder) forwardPorts(method string, url *url.URL, ports []string) (*portforward.PortForwarder, error) {
|
||||
func (p *PortForwarder) forwardPorts(method string, url *url.URL, address string, ports []string) (*portforward.PortForwarder, error) {
|
||||
cfg, err := p.Config().RESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -129,7 +130,10 @@ func (p *PortForwarder) forwardPorts(method string, url *url.URL, ports []string
|
|||
}
|
||||
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
|
||||
addrs := []string{localhost}
|
||||
if address == "" {
|
||||
address = localhost
|
||||
}
|
||||
addrs := strings.Split(address, ",")
|
||||
return portforward.NewOnAddresses(dialer, addrs, ports, p.stopChan, p.readyChan, p.Out, p.ErrOut)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
const portForwardKey = "portforward"
|
||||
|
||||
// ShowPortForward pops a port forwarding configuration dialog.
|
||||
func ShowPortForward(p *ui.Pages, port string, okFn func(lport, cport string)) {
|
||||
func ShowPortForward(p *ui.Pages, port string, okFn func(address, lport, cport string)) {
|
||||
f := tview.NewForm()
|
||||
f.SetItemPadding(0)
|
||||
f.SetButtonsAlign(tview.AlignCenter).
|
||||
|
|
@ -20,16 +20,19 @@ func ShowPortForward(p *ui.Pages, port string, okFn func(lport, cport string)) {
|
|||
SetLabelColor(tcell.ColorAqua).
|
||||
SetFieldTextColor(tcell.ColorOrange)
|
||||
|
||||
p1, p2 := port, port
|
||||
f.AddInputField("Pod Port:", p1, 20, nil, func(port string) {
|
||||
p1 = port
|
||||
p1, p2, address := port, port, "localhost"
|
||||
f.AddInputField("Pod Port:", p1, 20, nil, func(p string) {
|
||||
p1 = p
|
||||
})
|
||||
f.AddInputField("Local Port:", p2, 20, nil, func(port string) {
|
||||
p2 = port
|
||||
f.AddInputField("Local Port:", p2, 20, nil, func(p string) {
|
||||
p2 = p
|
||||
})
|
||||
f.AddInputField("Address:", address, 20, nil, func(h string) {
|
||||
address = h
|
||||
})
|
||||
|
||||
f.AddButton("OK", func() {
|
||||
okFn(stripPort(p2), stripPort(p1))
|
||||
okFn(address, stripPort(p2), stripPort(p1))
|
||||
})
|
||||
f.AddButton("Cancel", func() {
|
||||
DismissPortForward(p)
|
||||
|
|
@ -48,6 +51,7 @@ func DismissPortForward(p *ui.Pages) {
|
|||
p.RemovePage(portForwardKey)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// StripPort removes the named port id if present.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
func TestPortForwardDialog(t *testing.T) {
|
||||
p := ui.NewPages()
|
||||
|
||||
okFunc := func(lport, cport string) {
|
||||
okFunc := func(address, lport, cport string) {
|
||||
}
|
||||
ShowPortForward(p, "8080", okFunc)
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ func TestStripPort(t *testing.T) {
|
|||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, stripPort(u.port))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@ const (
|
|||
Key9
|
||||
)
|
||||
|
||||
// Defines numeric keys for container actions
|
||||
const (
|
||||
KeyShift0 int32 = 41
|
||||
KeyShift1 int32 = 33
|
||||
KeyShift2 int32 = 64
|
||||
KeyShift3 int32 = 35
|
||||
KeyShift4 int32 = 36
|
||||
KeyShift5 int32 = 37
|
||||
KeyShift6 int32 = 94
|
||||
KeyShift7 int32 = 38
|
||||
KeyShift8 int32 = 42
|
||||
KeyShift9 int32 = 40
|
||||
)
|
||||
|
||||
// Defines char keystrokes
|
||||
const (
|
||||
KeyA tcell.Key = iota + 97
|
||||
|
|
@ -151,6 +165,17 @@ func initStdKeys() {
|
|||
}
|
||||
|
||||
func initShiftKeys() {
|
||||
tcell.KeyNames[tcell.Key(KeyShift0)] = "Shift-0"
|
||||
tcell.KeyNames[tcell.Key(KeyShift1)] = "Shift-1"
|
||||
tcell.KeyNames[tcell.Key(KeyShift2)] = "Shift-2"
|
||||
tcell.KeyNames[tcell.Key(KeyShift3)] = "Shift-3"
|
||||
tcell.KeyNames[tcell.Key(KeyShift4)] = "Shift-4"
|
||||
tcell.KeyNames[tcell.Key(KeyShift5)] = "Shift-5"
|
||||
tcell.KeyNames[tcell.Key(KeyShift6)] = "Shift-6"
|
||||
tcell.KeyNames[tcell.Key(KeyShift7)] = "Shift-7"
|
||||
tcell.KeyNames[tcell.Key(KeyShift8)] = "Shift-8"
|
||||
tcell.KeyNames[tcell.Key(KeyShift9)] = "Shift-9"
|
||||
|
||||
tcell.KeyNames[tcell.Key(KeyShiftA)] = "Shift-A"
|
||||
tcell.KeyNames[tcell.Key(KeyShiftB)] = "Shift-B"
|
||||
tcell.KeyNames[tcell.Key(KeyShiftC)] = "Shift-C"
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ func (t *Table) SendKey(evt *tcell.EventKey) {
|
|||
}
|
||||
|
||||
func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msgf("KEY PRESS %#v", evt)
|
||||
key := evt.Key()
|
||||
if key == tcell.KeyUp || key == tcell.KeyDown {
|
||||
return evt
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Runner interface {
|
||||
App() *App
|
||||
GetSelectedItem() string
|
||||
Aliases() []string
|
||||
EnvFn() EnvFunc
|
||||
}
|
||||
|
||||
func hasAll(scopes []string) bool {
|
||||
for _, s := range scopes {
|
||||
if s == "all" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func includes(aliases []string, s string) bool {
|
||||
for _, a := range aliases {
|
||||
if a == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inScope(scopes, aliases []string) bool {
|
||||
if hasAll(scopes) {
|
||||
return true
|
||||
}
|
||||
for _, s := range scopes {
|
||||
if includes(aliases, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func hotKeyActions(r Runner, aa ui.KeyActions) {
|
||||
hh := config.NewHotKeys()
|
||||
if err := hh.Load(); err != nil {
|
||||
log.Warn().Msgf("No HotKey configuration found")
|
||||
return
|
||||
}
|
||||
|
||||
for k, hk := range hh.HotKey {
|
||||
key, err := asKey(hk.ShortCut)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Unable to map hotkey shortcut to a key")
|
||||
continue
|
||||
}
|
||||
_, ok := aa[key]
|
||||
if ok {
|
||||
log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut")
|
||||
continue
|
||||
}
|
||||
aa[key] = ui.NewKeyAction(
|
||||
hk.Description,
|
||||
gotoCmd(r, hk.Command),
|
||||
true)
|
||||
}
|
||||
}
|
||||
|
||||
func gotoCmd(r Runner, cmd string) ui.ActionHandler {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if err := r.App().gotoResource(cmd); err != nil {
|
||||
r.App().Flash().Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func pluginActions(r Runner, aa ui.KeyActions) {
|
||||
pp := config.NewPlugins()
|
||||
if err := pp.Load(); err != nil {
|
||||
log.Warn().Msgf("No plugin configuration found")
|
||||
return
|
||||
}
|
||||
|
||||
for k, plugin := range pp.Plugin {
|
||||
if !inScope(plugin.Scopes, r.Aliases()) {
|
||||
continue
|
||||
}
|
||||
key, err := asKey(plugin.ShortCut)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Unable to map plugin shortcut to a key")
|
||||
continue
|
||||
}
|
||||
_, ok := aa[key]
|
||||
if ok {
|
||||
log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut")
|
||||
continue
|
||||
}
|
||||
aa[key] = ui.NewKeyAction(
|
||||
plugin.Description,
|
||||
execCmd(r, plugin.Command, plugin.Background, plugin.Args...),
|
||||
true)
|
||||
}
|
||||
}
|
||||
|
||||
func execCmd(r Runner, bin string, bg bool, args ...string) ui.ActionHandler {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := r.GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
var (
|
||||
env = r.EnvFn()()
|
||||
aa = make([]string, len(args))
|
||||
err error
|
||||
)
|
||||
for i, a := range args {
|
||||
aa[i], err = env.envFor(a)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Args match failed")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if run(true, r.App(), bin, bg, aa...) {
|
||||
r.App().Flash().Info("Custom CMD launched!")
|
||||
} else {
|
||||
r.App().Flash().Info("Custom CMD failed!")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -447,7 +447,8 @@ func (b *Browser) refreshActions() {
|
|||
if client.Can(b.meta.Verbs, "describe") {
|
||||
aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true)
|
||||
}
|
||||
b.customActions(aa)
|
||||
pluginActions(b, aa)
|
||||
hotKeyActions(b, aa)
|
||||
b.Actions().Add(aa)
|
||||
|
||||
if b.bindKeysFn != nil {
|
||||
|
|
@ -456,61 +457,12 @@ func (b *Browser) refreshActions() {
|
|||
b.app.Menu().HydrateMenu(b.Hints())
|
||||
}
|
||||
|
||||
func (b *Browser) customActions(aa ui.KeyActions) {
|
||||
pp := config.NewPlugins()
|
||||
if err := pp.Load(); err != nil {
|
||||
log.Warn().Msgf("No plugin configuration found")
|
||||
return
|
||||
}
|
||||
|
||||
for k, plugin := range pp.Plugin {
|
||||
if !in(plugin.Scopes, b.meta.Name) {
|
||||
continue
|
||||
}
|
||||
key, err := asKey(plugin.ShortCut)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Unable to map shortcut to a key")
|
||||
continue
|
||||
}
|
||||
_, ok := aa[key]
|
||||
if ok {
|
||||
log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut")
|
||||
continue
|
||||
}
|
||||
aa[key] = ui.NewKeyAction(
|
||||
plugin.Description,
|
||||
b.execCmd(plugin.Command, plugin.Background, plugin.Args...),
|
||||
true)
|
||||
}
|
||||
func (b *Browser) Aliases() []string {
|
||||
return append(b.meta.ShortNames, b.meta.SingularName, b.meta.Name)
|
||||
}
|
||||
|
||||
func (b *Browser) execCmd(bin string, bg bool, args ...string) ui.ActionHandler {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := b.GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
var (
|
||||
env = b.envFn()
|
||||
aa = make([]string, len(args))
|
||||
err error
|
||||
)
|
||||
for i, a := range args {
|
||||
aa[i], err = env.envFor(a)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Args match failed")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if run(true, b.app, bin, bg, aa...) {
|
||||
b.app.Flash().Info("Custom CMD launched!")
|
||||
} else {
|
||||
b.app.Flash().Info("Custom CMD failed!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (b *Browser) EnvFn() EnvFunc {
|
||||
return b.envFn
|
||||
}
|
||||
|
||||
func (b *Browser) defaultK9sEnv() K9sEnv {
|
||||
|
|
|
|||
|
|
@ -85,9 +85,6 @@ func (c *command) viewMetaFor(cmd string) (string, *MetaViewer, error) {
|
|||
if !ok {
|
||||
return "", nil, fmt.Errorf("Huh? `%s` command not found", cmd)
|
||||
}
|
||||
if _, err := c.app.factory.CanForResource(c.app.Config.ActiveNamespace(), gvr); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
v, ok := customViewers[client.GVR(gvr)]
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ func (c *Container) selectedContainer() string {
|
|||
}
|
||||
|
||||
func (c *Container) viewLogs(app *App, ns, res, path string) {
|
||||
log.Debug().Msgf(">>>>>>>> ViewLOgs %q -- %q -- %q", ns, res, path)
|
||||
status := c.GetTable().GetSelectedCell(3)
|
||||
if status != "Running" && status != "Completed" {
|
||||
app.Flash().Err(errors.New("No logs available"))
|
||||
|
|
@ -134,11 +133,11 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) portForward(lport, cport string) {
|
||||
func (c *Container) portForward(address, lport, cport string) {
|
||||
co := c.GetTable().GetSelectedCell(0)
|
||||
pf := dao.NewPortForwarder(c.App().Conn())
|
||||
ports := []string{lport + ":" + cport}
|
||||
fw, err := pf.Start(c.GetTable().Path, co, ports)
|
||||
fw, err := pf.Start(c.GetTable().Path, co, address, ports)
|
||||
if err != nil {
|
||||
c.App().Flash().Err(err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
|
|
@ -104,6 +105,22 @@ func (v *Help) showNav() model.MenuHints {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *Help) showHotKeys() (model.MenuHints, error) {
|
||||
hh := config.NewHotKeys()
|
||||
if err := hh.Load(); err != nil {
|
||||
return nil, fmt.Errorf("no hotkey configuration found")
|
||||
}
|
||||
m := make(model.MenuHints, 0, len(hh.HotKey))
|
||||
for _, hk := range hh.HotKey {
|
||||
m = append(m, model.MenuHint{
|
||||
Mnemonic: hk.ShortCut,
|
||||
Description: hk.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (v *Help) showGeneral() model.MenuHints {
|
||||
return model.MenuHints{
|
||||
{
|
||||
|
|
@ -160,10 +177,18 @@ func (v *Help) resetTitle() {
|
|||
func (v *Help) build(hh model.MenuHints) {
|
||||
v.Clear()
|
||||
sort.Sort(hh)
|
||||
v.addSection(0, "RESOURCE", hh)
|
||||
v.addSection(4, "GENERAL", v.showGeneral())
|
||||
v.addSection(6, "NAVIGATION", v.showNav())
|
||||
v.addSection(8, "HELP", v.showHelp())
|
||||
var col int
|
||||
v.addSection(col, "RESOURCE", hh)
|
||||
col += 2
|
||||
v.addSection(col, "GENERAL", v.showGeneral())
|
||||
col += 2
|
||||
v.addSection(col, "NAVIGATION", v.showNav())
|
||||
col += 2
|
||||
if h, err := v.showHotKeys(); err == nil {
|
||||
v.addSection(col, "HOTKEYS", h)
|
||||
col += 2
|
||||
}
|
||||
v.addSection(col, "HELP", v.showHelp())
|
||||
}
|
||||
|
||||
func (v *Help) addSection(c int, title string, hh model.MenuHints) {
|
||||
|
|
|
|||
|
|
@ -58,17 +58,6 @@ func extractApp(ctx context.Context) (*App, error) {
|
|||
return app, nil
|
||||
}
|
||||
|
||||
// In check if a string belongs to a set.
|
||||
func in(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AsKey maps a string representation of a key to a tcell key.
|
||||
func asKey(key string) (tcell.Key, error) {
|
||||
for k, v := range tcell.KeyNames {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory {
|
|||
}
|
||||
|
||||
func toGVR(gvr string) schema.GroupVersionResource {
|
||||
log.Debug().Msgf(">>> Convert GVR %q", gvr)
|
||||
tokens := strings.Split(gvr, "/")
|
||||
if len(tokens) < 3 {
|
||||
tokens = append([]string{""}, tokens...)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
// Forwarder represents a port forwarder.
|
||||
type Forwarder interface {
|
||||
// Start initializes a port forward.
|
||||
Start(path, co string, ports []string) (*portforward.PortForwarder, error)
|
||||
Start(path, co, address string, ports []string) (*portforward.PortForwarder, error)
|
||||
|
||||
// Stop terminates a port forward.
|
||||
Stop()
|
||||
|
|
|
|||
Loading…
Reference in New Issue