github.com/Azure/draft@v0.16.0/pkg/draft/tunnel/tunnel.go (about) 1 // Initial license from Helm 2 /* 3 Copyright 2016 The Kubernetes Authors All rights reserved. 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 18 package tunnel 19 20 import ( 21 "fmt" 22 "io" 23 "io/ioutil" 24 "net" 25 "net/http" 26 "strconv" 27 28 "k8s.io/client-go/rest" 29 "k8s.io/client-go/tools/portforward" 30 "k8s.io/client-go/transport/spdy" 31 ) 32 33 // Tunnel describes a ssh-like tunnel to a kubernetes pod 34 type Tunnel struct { 35 Local int 36 Remote int 37 Namespace string 38 PodName string 39 Out io.Writer 40 stopChan chan struct{} 41 readyChan chan struct{} 42 config *rest.Config 43 client rest.Interface 44 } 45 46 // NewTunnel creates a new tunnel 47 func NewTunnel(client rest.Interface, config *rest.Config, namespace, podName string, remote int) *Tunnel { 48 return &Tunnel{ 49 config: config, 50 client: client, 51 Namespace: namespace, 52 PodName: podName, 53 Remote: remote, 54 stopChan: make(chan struct{}, 1), 55 readyChan: make(chan struct{}, 1), 56 Out: ioutil.Discard, 57 } 58 } 59 60 // NewWithLocalTunnel creates a new tunnel on a specified local port 61 func NewWithLocalTunnel(client rest.Interface, config *rest.Config, namespace, podName string, remote, local int) *Tunnel { 62 return &Tunnel{ 63 config: config, 64 client: client, 65 Namespace: namespace, 66 PodName: podName, 67 Remote: remote, 68 Local: local, 69 stopChan: make(chan struct{}, 1), 70 readyChan: make(chan struct{}, 1), 71 Out: ioutil.Discard, 72 } 73 } 74 75 // Close disconnects a tunnel connection 76 func (t *Tunnel) Close() { 77 close(t.stopChan) 78 close(t.readyChan) 79 } 80 81 // ForwardPort opens a tunnel to a kubernetes pod 82 func (t *Tunnel) ForwardPort() error { 83 // Build a url to the portforward endpoint 84 // example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward 85 u := t.client.Post(). 86 Resource("pods"). 87 Namespace(t.Namespace). 88 Name(t.PodName). 89 SubResource("portforward").URL() 90 91 transport, upgrader, err := spdy.RoundTripperFor(t.config) 92 if err != nil { 93 return err 94 } 95 dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", u) 96 97 local, err := getAvailablePort(t.Local) 98 if err != nil { 99 return fmt.Errorf("could not find an available port: %s", err) 100 } 101 t.Local = local 102 103 ports := []string{fmt.Sprintf("%d:%d", t.Local, t.Remote)} 104 105 pf, err := portforward.New(dialer, ports, t.stopChan, t.readyChan, t.Out, t.Out) 106 if err != nil { 107 return err 108 } 109 110 errChan := make(chan error) 111 go func() { 112 errChan <- pf.ForwardPorts() 113 }() 114 115 select { 116 case err = <-errChan: 117 return fmt.Errorf("forwarding ports: %v", err) 118 case <-pf.Ready: 119 return nil 120 } 121 } 122 123 func getAvailablePort(localPort int) (int, error) { 124 l, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort)) 125 if err != nil { 126 return 0, err 127 } 128 defer l.Close() 129 130 _, p, err := net.SplitHostPort(l.Addr().String()) 131 if err != nil { 132 return 0, err 133 } 134 port, err := strconv.Atoi(p) 135 if err != nil { 136 return 0, err 137 } 138 return port, err 139 }