mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
119 lines
2.8 KiB
Go
119 lines
2.8 KiB
Go
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package dart
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
toxiproxy "github.com/Shopify/toxiproxy/client"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/xerrors"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/portforward"
|
|
"k8s.io/client-go/transport/spdy"
|
|
)
|
|
|
|
const (
|
|
// port to toxiproxy's control plane API
|
|
toxiproxyPort = 8474
|
|
)
|
|
|
|
// ProxiedToxiproxy provides a connection to a toxiproxy pod
|
|
type ProxiedToxiproxy struct {
|
|
*toxiproxy.Client
|
|
closer func()
|
|
}
|
|
|
|
// NewProxiedToxiproxy connects to a Toxiproxy pod
|
|
func NewProxiedToxiproxy(cfg *rest.Config, namespace, pod string) (*ProxiedToxiproxy, error) {
|
|
localPort := rand.Intn(2000) + 31000
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
readychan, errchan := forwardPort(ctx, cfg, namespace, pod, fmt.Sprintf("%d:%d", localPort, toxiproxyPort))
|
|
select {
|
|
case <-readychan:
|
|
case err := <-errchan:
|
|
return nil, err
|
|
}
|
|
log.WithField("port", localPort).Info("forwarding control API of toxiproxy")
|
|
|
|
tpc := toxiproxy.NewClient(fmt.Sprintf("localhost:%d", localPort))
|
|
return &ProxiedToxiproxy{
|
|
Client: tpc,
|
|
closer: cancel,
|
|
}, nil
|
|
}
|
|
|
|
// Close shuts down the connection to the toxiproxy pod
|
|
func (p *ProxiedToxiproxy) Close() error {
|
|
p.closer()
|
|
return nil
|
|
}
|
|
|
|
// ForwardPort establishes a TCP port forwarding to a Kubernetes pod
|
|
func forwardPort(ctx context.Context, config *rest.Config, namespace, pod, port string) (readychan chan struct{}, errchan chan error) {
|
|
errchan = make(chan error, 1)
|
|
readychan = make(chan struct{}, 1)
|
|
|
|
roundTripper, upgrader, err := spdy.RoundTripperFor(config)
|
|
if err != nil {
|
|
errchan <- err
|
|
return
|
|
}
|
|
|
|
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, pod)
|
|
hostIP := strings.TrimPrefix(config.Host, "https://")
|
|
serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP}
|
|
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL)
|
|
|
|
stopChan := make(chan struct{}, 1)
|
|
fwdReadyChan := make(chan struct{}, 1)
|
|
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
|
|
forwarder, err := portforward.New(dialer, []string{port}, stopChan, fwdReadyChan, out, errOut)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var once sync.Once
|
|
go func() {
|
|
err := forwarder.ForwardPorts()
|
|
if err != nil {
|
|
errchan <- err
|
|
}
|
|
once.Do(func() { close(readychan) })
|
|
}()
|
|
|
|
go func() {
|
|
select {
|
|
case <-readychan:
|
|
// we're out of here
|
|
case <-ctx.Done():
|
|
close(stopChan)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for range fwdReadyChan {
|
|
}
|
|
|
|
if errOut.Len() != 0 {
|
|
errchan <- xerrors.Errorf(errOut.String())
|
|
return
|
|
}
|
|
|
|
once.Do(func() { close(readychan) })
|
|
}()
|
|
|
|
return
|
|
}
|