Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/test/pkg/integration/common/port_forward.go
2499 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package common
6
7
import (
8
"bytes"
9
"context"
10
"errors"
11
"fmt"
12
"io"
13
"net"
14
"os/exec"
15
"strings"
16
"time"
17
18
"k8s.io/apimachinery/pkg/util/wait"
19
)
20
21
const (
22
errorDialingBackend = "error: error upgrading connection: error dialing backend: EOF"
23
)
24
25
// ForwardPortOfPod establishes a TCP port forwarding to a Kubernetes pod
26
func ForwardPortOfPod(ctx context.Context, kubeconfig string, namespace, name, port string) (readychan chan struct{}, errchan chan error) {
27
return forwardPort(ctx, kubeconfig, namespace, "pod", name, port)
28
}
29
30
// ForwardPortOfSvc establishes a TCP port forwarding to a Kubernetes service
31
func ForwardPortOfSvc(ctx context.Context, kubeconfig string, namespace, name, port string) (readychan chan struct{}, errchan chan error) {
32
return forwardPort(ctx, kubeconfig, namespace, "service", name, port)
33
}
34
35
// forwardPort establishes a TCP port forwarding to a Kubernetes resource - pod or service
36
// Uses kubectl instead of Go to use a local process that can reproduce the same behavior outside the tests
37
// Since we are using kubectl directly we need to pass kubeconfig explicitly
38
func forwardPort(ctx context.Context, kubeconfig string, namespace, resourceType, name, port string) (readychan chan struct{}, errchan chan error) {
39
errchan = make(chan error, 1)
40
readychan = make(chan struct{}, 1)
41
42
go func() {
43
args := []string{
44
"port-forward",
45
"--address=0.0.0.0",
46
fmt.Sprintf("%s/%v", resourceType, name),
47
fmt.Sprintf("--namespace=%v", namespace),
48
fmt.Sprintf("--kubeconfig=%v", kubeconfig),
49
port,
50
}
51
52
command := exec.CommandContext(ctx, "kubectl", args...)
53
var serr, sout bytes.Buffer
54
command.Stdout = &sout
55
command.Stderr = &serr
56
err := command.Start()
57
defer func() {
58
if command.Process != nil {
59
_ = command.Process.Kill()
60
}
61
}()
62
if err != nil {
63
if strings.TrimSuffix(serr.String(), "\n") == errorDialingBackend {
64
errchan <- io.EOF
65
} else {
66
errchan <- fmt.Errorf("unexpected error string port-forward: %w", errors.New(serr.String()))
67
}
68
}
69
70
err = command.Wait()
71
if err != nil {
72
if strings.TrimSuffix(serr.String(), "\n") == errorDialingBackend {
73
errchan <- io.EOF
74
} else {
75
errchan <- fmt.Errorf("unexpected error running port-forward: %w", errors.New(serr.String()))
76
}
77
}
78
}()
79
80
// wait until we can reach the local port before signaling we are ready
81
go func() {
82
localPort := strings.Split(port, ":")[0]
83
waitErr := wait.PollImmediate(500*time.Millisecond, 1*time.Minute, func() (bool, error) {
84
conn, err := net.DialTimeout("tcp", net.JoinHostPort("localhost", localPort), 1*time.Second)
85
if err != nil {
86
return false, nil
87
}
88
if conn == nil {
89
return false, nil
90
}
91
92
conn.Close()
93
return true, nil
94
})
95
96
if waitErr == wait.ErrWaitTimeout {
97
errchan <- fmt.Errorf("timeout waiting for port-foward: %w", waitErr)
98
return
99
} else if waitErr != nil {
100
errchan <- waitErr
101
return
102
} else {
103
readychan <- struct{}{}
104
105
}
106
}()
107
108
return readychan, errchan
109
}
110
111