Skip to content

Commit bbddd27

Browse files
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.
1 parent cdfb912 commit bbddd27

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

staging/src/k8s.io/client-go/tools/portforward/portforward.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,9 @@ func (pf *PortForwarder) forward() error {
197197
var err error
198198

199199
listenSuccess := false
200-
for _, port := range pf.ports {
201-
err = pf.listenOnPort(&port)
200+
for i := range pf.ports {
201+
port := &pf.ports[i]
202+
err = pf.listenOnPort(port)
202203
switch {
203204
case err == nil:
204205
listenSuccess = true

staging/src/k8s.io/client-go/tools/portforward/portforward_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ package portforward
1818

1919
import (
2020
"net"
21+
"net/http"
2122
"os"
2223
"reflect"
2324
"sort"
2425
"strings"
2526
"testing"
27+
"time"
2628

2729
"k8s.io/apimachinery/pkg/util/httpstream"
2830
)
@@ -39,6 +41,37 @@ func (d *fakeDialer) Dial(protocols ...string) (httpstream.Connection, string, e
3941
return d.conn, d.negotiatedProtocol, d.err
4042
}
4143

44+
type fakeConnection struct {
45+
closed bool
46+
closeChan chan bool
47+
}
48+
49+
func newFakeConnection() httpstream.Connection {
50+
return &fakeConnection{
51+
closeChan: make(chan bool),
52+
}
53+
}
54+
55+
func (c *fakeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) {
56+
return nil, nil
57+
}
58+
59+
func (c *fakeConnection) Close() error {
60+
if !c.closed {
61+
c.closed = true
62+
close(c.closeChan)
63+
}
64+
return nil
65+
}
66+
67+
func (c *fakeConnection) CloseChan() <-chan bool {
68+
return c.closeChan
69+
}
70+
71+
func (c *fakeConnection) SetIdleTimeout(timeout time.Duration) {
72+
// no-op
73+
}
74+
4275
func TestParsePortsAndNew(t *testing.T) {
4376
tests := []struct {
4477
input []string
@@ -254,3 +287,46 @@ func TestGetListener(t *testing.T) {
254287

255288
}
256289
}
290+
291+
func TestGetPortsReturnsDynamicallyAssignedLocalPort(t *testing.T) {
292+
dialer := &fakeDialer{
293+
conn: newFakeConnection(),
294+
}
295+
296+
stopChan := make(chan struct{})
297+
readyChan := make(chan struct{})
298+
errChan := make(chan error)
299+
300+
defer func() {
301+
close(stopChan)
302+
303+
forwardErr := <-errChan
304+
if forwardErr != nil {
305+
t.Fatalf("ForwardPorts returned error: %s", forwardErr)
306+
}
307+
}()
308+
309+
pf, err := New(dialer, []string{":5000"}, stopChan, readyChan, os.Stdout, os.Stderr)
310+
311+
if err != nil {
312+
t.Fatalf("error while calling New: %s", err)
313+
}
314+
315+
go func() {
316+
errChan <- pf.ForwardPorts()
317+
close(errChan)
318+
}()
319+
320+
<-pf.Ready
321+
322+
ports, err := pf.GetPorts()
323+
324+
if len(ports) != 1 {
325+
t.Fatalf("expected 1 port, got %d", len(ports))
326+
}
327+
328+
port := ports[0]
329+
if port.Local == 0 {
330+
t.Fatalf("local port is 0, expected != 0")
331+
}
332+
}

0 commit comments

Comments
 (0)