Skip to content

Commit

Permalink
Implement Go port-forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
45132765 committed Jan 7, 2024
1 parent 60acf40 commit e617637
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 3 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ go 1.21.3

require (
github.com/go-logr/logr v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.17.0
github.com/robfig/cron/v3 v3.0.1
github.com/stretchr/testify v1.8.4
google.golang.org/api v0.149.0
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5
k8s.io/api v0.29.0
k8s.io/apimachinery v0.29.0
k8s.io/client-go v0.29.0
Expand Down Expand Up @@ -67,6 +67,7 @@ require (
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down Expand Up @@ -438,8 +442,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
79 changes: 79 additions & 0 deletions pkg/kubernetes/port_forward.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package kubernetes

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"

"github.com/hashicorp/go-multierror"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)

// PortForward port forwards to the specified Pod in the background. The forwarded port is a random
// available local port which is returned as well as a function to close the listener when finished
func PortForward(ctx context.Context, restConfig *rest.Config, podNamespace, podName string, port int) (uint16, func() error, error) {
stopChan, readyChan, errChan := make(chan struct{}, 1), make(chan struct{}, 1), make(chan error, 1)
forwarder, err := createForwarder(ctx, restConfig, stopChan, readyChan, podNamespace, podName, port)
if err != nil {
return 0, nil, err
}
go func() {
errChan <- forwarder.ForwardPorts()
}()
// Wait for port forward to be ready or fail
select {
case <-readyChan:
case err := <-errChan:
if err != nil {
return 0, nil, err
}
return 0, nil, errors.New("port forward finished")
}
// Create function for the caller to finish port forwarding
close := func() error {
// Make sure any started listeners are stopped...
close(stopChan)
// ...and wait for the port forward to finish
return <-errChan
}
forwardedPorts, err := forwarder.GetPorts()
if err != nil {
return 0, nil, multierror.Append(err, close())
}
if len(forwardedPorts) != 1 {
err := fmt.Errorf("unexpected number of forwarded ports: %d", len(forwardedPorts))
return 0, nil, multierror.Append(err, close())
}
return forwardedPorts[0].Local, close, nil
}

func createForwarder(ctx context.Context, restConfig *rest.Config, stopChan, readyChan chan struct{}, podNamespace, podName string, port int) (*portforward.PortForwarder, error) {
// Discard output to avoid race conditions
out, errOut := io.Discard, io.Discard

roundTripper, upgrader, err := spdy.RoundTripperFor(restConfig)
if err != nil {
return nil, err
}

path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", podNamespace, podName)
hostIP := strings.TrimLeft(restConfig.Host, "htps:/")
serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP}

dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL)
// Listen on a random available local port to avoid collisions:
// https://github.com/kubernetes/client-go/blob/86d49e7265f07676cb39f342595a858b032112de/tools/portforward/portforward.go#L75
forwarderPort := fmt.Sprintf(":%d", port)
forwarder, err := portforward.New(dialer, []string{forwarderPort}, stopChan, readyChan, out, errOut)
if err != nil {
return nil, err
}

return forwarder, nil
}

0 comments on commit e617637

Please sign in to comment.