github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/testhelpers/kube_test_helpers.go (about) 1 package testhelpers 2 3 import ( 4 "bytes" 5 "fmt" 6 "net" 7 "net/http" 8 "net/url" 9 "strings" 10 "time" 11 12 "github.com/jenkins-x/jx-logging/pkg/log" 13 "github.com/olli-ai/jx/v2/pkg/cmd/clients" 14 "github.com/pkg/errors" 15 core_v1 "k8s.io/api/core/v1" 16 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/client-go/kubernetes" 18 "k8s.io/client-go/tools/portforward" 19 "k8s.io/client-go/transport/spdy" 20 ) 21 22 // GetFreePort asks the kernel for a free open port that is ready to use. 23 func GetFreePort() (int, error) { 24 addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") 25 if err != nil { 26 return 0, err 27 } 28 29 l, err := net.ListenTCP("tcp", addr) 30 if err != nil { 31 return 0, err 32 } 33 defer func() { 34 _ = l.Close() 35 }() 36 return l.Addr().(*net.TCPAddr).Port, nil 37 } 38 39 // WaitForPod waits for the specified duration for the given Pod to get into the 'Running' status. 40 func WaitForPod(pod *core_v1.Pod, namespace string, labels map[string]string, timeout time.Duration, kubeClient kubernetes.Interface) error { 41 status := pod.Status 42 watch, err := kubeClient.CoreV1().Pods(namespace).Watch(meta_v1.ListOptions{ 43 Watch: true, 44 ResourceVersion: pod.ResourceVersion, 45 LabelSelector: LabelSelector(labels), 46 }) 47 if err != nil { 48 return errors.Wrapf(err, "unable to create watch for pod '%s'", pod.Name) 49 } 50 51 func() { 52 for { 53 select { 54 case events, ok := <-watch.ResultChan(): 55 if !ok { 56 return 57 } 58 pod := events.Object.(*core_v1.Pod) 59 log.Logger().Debugf("Pod status: %v", pod.Status.Phase) 60 status = pod.Status 61 if pod.Status.Phase != core_v1.PodPending { 62 watch.Stop() 63 } 64 case <-time.After(timeout): 65 log.Logger().Debugf("timeout to wait for pod active") 66 watch.Stop() 67 } 68 } 69 }() 70 if status.Phase != core_v1.PodRunning { 71 return errors.Errorf("Pod '%s' should be running", pod.Name) 72 } 73 return nil 74 } 75 76 // PortForward port forwards the container port of the specified pod in the given namespace to the specified local forwarding port. 77 // The functions returns a stop channel to stop port forwarding. 78 func PortForward(namespace string, podName string, containerPort string, forwardPort string, factory clients.Factory) (chan struct{}, error) { 79 config, err := factory.CreateKubeConfig() 80 if err != nil { 81 return nil, err 82 } 83 84 roundTripper, upgrader, err := spdy.RoundTripperFor(config) 85 if err != nil { 86 return nil, err 87 } 88 89 path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, podName) 90 hostIP := strings.TrimLeft(config.Host, "https:/") 91 serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP} 92 93 dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL) 94 95 stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1) 96 out, errOut := new(bytes.Buffer), new(bytes.Buffer) 97 98 forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%s:%s", forwardPort, containerPort)}, stopChan, readyChan, out, errOut) 99 if err != nil { 100 return nil, err 101 } 102 103 go func() { 104 for range readyChan { // Kubernetes will close this channel when it has something to tell us. 105 } 106 if len(errOut.String()) != 0 { 107 log.Logger().Error(errOut.String()) 108 } else if len(out.String()) != 0 { 109 log.Logger().Info(out.String()) 110 } 111 }() 112 113 go func() { 114 err := forwarder.ForwardPorts() 115 if err != nil { 116 log.Logger().Errorf("error during port forwarding: %s", errOut.String()) 117 } 118 }() 119 120 return stopChan, err 121 } 122 123 // LabelSelector builds a Kubernetes label selector from the specified map. 124 func LabelSelector(labels map[string]string) string { 125 selector := "" 126 for k, v := range labels { 127 selector = selector + fmt.Sprintf("%s=%s,", k, v) 128 } 129 selector = strings.TrimRight(selector, ",") 130 return selector 131 }