github.com/argoproj/argo-cd/v3@v3.2.1/util/kube/portforwarder.go (about) 1 package kube 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "net" 8 "net/http" 9 "os" 10 11 corev1 "k8s.io/api/core/v1" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/util/httpstream" 14 "k8s.io/client-go/kubernetes" 15 "k8s.io/client-go/tools/clientcmd" 16 "k8s.io/client-go/tools/portforward" 17 "k8s.io/client-go/transport/spdy" 18 cmdutil "k8s.io/kubectl/pkg/cmd/util" 19 "k8s.io/kubectl/pkg/util/podutils" 20 21 utilio "github.com/argoproj/argo-cd/v3/util/io" 22 ) 23 24 func selectPodForPortForward(clientSet kubernetes.Interface, namespace string, podSelectors ...string) (*corev1.Pod, error) { 25 for _, podSelector := range podSelectors { 26 pods, err := clientSet.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ 27 LabelSelector: podSelector, 28 }) 29 if err != nil { 30 return nil, err 31 } 32 33 for _, po := range pods.Items { 34 if po.Status.Phase == corev1.PodRunning && podutils.IsPodReady(&po) { 35 return &po, nil 36 } 37 } 38 } 39 return nil, fmt.Errorf("cannot find ready pod with selector: %v - use the --{component}-name flag in this command or set the environmental variable (Refer to https://argo-cd.readthedocs.io/en/stable/user-guide/environment-variables), to change the Argo CD component name in the CLI", podSelectors) 40 } 41 42 func PortForward(targetPort int, namespace string, overrides *clientcmd.ConfigOverrides, podSelectors ...string) (int, error) { 43 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 44 loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig 45 clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) 46 config, err := clientConfig.ClientConfig() 47 if err != nil { 48 return -1, err 49 } 50 51 if namespace == "" { 52 namespace, _, err = clientConfig.Namespace() 53 if err != nil { 54 return -1, err 55 } 56 } 57 58 clientSet, err := kubernetes.NewForConfig(config) 59 if err != nil { 60 return -1, err 61 } 62 63 pod, err := selectPodForPortForward(clientSet, namespace, podSelectors...) 64 if err != nil { 65 return -1, err 66 } 67 68 url := clientSet.CoreV1().RESTClient().Post(). 69 Resource("pods"). 70 Namespace(pod.Namespace). 71 Name(pod.Name). 72 SubResource("portforward").URL() 73 74 transport, upgrader, err := spdy.RoundTripperFor(config) 75 if err != nil { 76 return -1, fmt.Errorf("could not create round tripper: %w", err) 77 } 78 dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url) 79 80 // Reuse environment variable for kubectl to disable the feature flag, default is enabled. 81 if !cmdutil.PortForwardWebsockets.IsDisabled() { 82 tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(url, config) 83 if err != nil { 84 return -1, fmt.Errorf("could not create tunneling dialer: %w", err) 85 } 86 // First attempt tunneling (websocket) dialer, then fallback to spdy dialer. 87 dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool { 88 return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err) 89 }) 90 } 91 92 readyChan := make(chan struct{}, 1) 93 failedChan := make(chan error, 1) 94 out := new(bytes.Buffer) 95 errOut := new(bytes.Buffer) 96 97 ln, err := net.Listen("tcp", "localhost:0") 98 if err != nil { 99 return -1, err 100 } 101 port := ln.Addr().(*net.TCPAddr).Port 102 utilio.Close(ln) 103 forwarder, err := portforward.NewOnAddresses(dialer, []string{"localhost"}, []string{fmt.Sprintf("%d:%d", port, targetPort)}, context.Background().Done(), readyChan, out, errOut) 104 if err != nil { 105 return -1, err 106 } 107 108 go func() { 109 err = forwarder.ForwardPorts() 110 if err != nil { 111 failedChan <- err 112 } 113 }() 114 select { 115 case err = <-failedChan: 116 return -1, err 117 case <-readyChan: 118 } 119 if errOut.String() != "" { 120 return -1, fmt.Errorf("%s", errOut.String()) 121 } 122 return port, nil 123 }