client-go: Dynamic local port not accessible when port-forwarding

When setting up a port forwarding with the client-go library (using the
`k8s.io/client-go/tools/portforward.PortForwarder`) with a non-defined local
port (i.e. passing `:80` as `ports` parameter to `portforward.New(...)`), a
local port will be assigned dynamically.

Currently, the local port will be _always_ 0 if it was not specified initially.
This is because the assigned local port is only set on a _copy_ of the actual
`ForwardedPort` type that is obtained in a `range` loop. This PR changes this
behaviour to set the local port at the correct instance by passing a pointer
instead of a copy to the relevant functions.
pull/564/head
Martin Helmich 2019-02-03 19:01:19 +01:00 committed by Martin Helmich
parent cdfb9126d3
commit bbddd27f0d
2 changed files with 79 additions and 2 deletions

View File

@ -197,8 +197,9 @@ func (pf *PortForwarder) forward() error {
var err error
listenSuccess := false
for _, port := range pf.ports {
err = pf.listenOnPort(&port)
for i := range pf.ports {
port := &pf.ports[i]
err = pf.listenOnPort(port)
switch {
case err == nil:
listenSuccess = true

View File

@ -18,11 +18,13 @@ package portforward
import (
"net"
"net/http"
"os"
"reflect"
"sort"
"strings"
"testing"
"time"
"k8s.io/apimachinery/pkg/util/httpstream"
)
@ -39,6 +41,37 @@ func (d *fakeDialer) Dial(protocols ...string) (httpstream.Connection, string, e
return d.conn, d.negotiatedProtocol, d.err
}
type fakeConnection struct {
closed bool
closeChan chan bool
}
func newFakeConnection() httpstream.Connection {
return &fakeConnection{
closeChan: make(chan bool),
}
}
func (c *fakeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) {
return nil, nil
}
func (c *fakeConnection) Close() error {
if !c.closed {
c.closed = true
close(c.closeChan)
}
return nil
}
func (c *fakeConnection) CloseChan() <-chan bool {
return c.closeChan
}
func (c *fakeConnection) SetIdleTimeout(timeout time.Duration) {
// no-op
}
func TestParsePortsAndNew(t *testing.T) {
tests := []struct {
input []string
@ -254,3 +287,46 @@ func TestGetListener(t *testing.T) {
}
}
func TestGetPortsReturnsDynamicallyAssignedLocalPort(t *testing.T) {
dialer := &fakeDialer{
conn: newFakeConnection(),
}
stopChan := make(chan struct{})
readyChan := make(chan struct{})
errChan := make(chan error)
defer func() {
close(stopChan)
forwardErr := <-errChan
if forwardErr != nil {
t.Fatalf("ForwardPorts returned error: %s", forwardErr)
}
}()
pf, err := New(dialer, []string{":5000"}, stopChan, readyChan, os.Stdout, os.Stderr)
if err != nil {
t.Fatalf("error while calling New: %s", err)
}
go func() {
errChan <- pf.ForwardPorts()
close(errChan)
}()
<-pf.Ready
ports, err := pf.GetPorts()
if len(ports) != 1 {
t.Fatalf("expected 1 port, got %d", len(ports))
}
port := ports[0]
if port.Local == 0 {
t.Fatalf("local port is 0, expected != 0")
}
}