github.com/azure/draft-classic@v0.16.0/pkg/local/local.go (about) 1 package local 2 3 import ( 4 "fmt" 5 "io" 6 "strconv" 7 "strings" 8 9 "github.com/BurntSushi/toml" 10 "k8s.io/api/core/v1" 11 "k8s.io/client-go/kubernetes" 12 restclient "k8s.io/client-go/rest" 13 14 "github.com/Azure/draft/pkg/draft/manifest" 15 "github.com/Azure/draft/pkg/draft/tunnel" 16 "github.com/Azure/draft/pkg/kube/podutil" 17 ) 18 19 const ( 20 // DraftLabelKey is the label selector key on a pod that allows 21 // us to identify which draft app a pod is associated with 22 DraftLabelKey = "draft" 23 24 // BuildIDKey is the label selector key on a pod that specifies 25 // the build ID of the application 26 BuildIDKey = "buildID" 27 ) 28 29 // App encapsulates information about an application to connect to 30 // 31 // Name is the name of the application 32 // Namespace is the Kubernetes namespace it is deployed in 33 // Container is the name the name of the application container to connect to 34 // OverridePorts contains mappings of which local port to map a remote port to 35 // and will be in the form local_port:remote_port i.e. 8080:8081 36 type App struct { 37 Name string 38 Namespace string 39 Container string 40 OverridePorts []string 41 } 42 43 // Connection encapsulated information to connect to an application 44 type Connection struct { 45 ContainerConnections []*ContainerConnection 46 PodName string 47 Clientset kubernetes.Interface 48 } 49 50 // ContainerConnection encapsulates a connection to a container in a pod 51 type ContainerConnection struct { 52 Tunnels []*tunnel.Tunnel 53 ContainerName string 54 } 55 56 // DeployedApplication returns deployment information about the deployed instance 57 // of the source code given a path to your draft.toml file and the name of the 58 // draft environment 59 func DeployedApplication(draftTomlPath, draftEnvironment string) (*App, error) { 60 var draftConfig manifest.Manifest 61 if _, err := toml.DecodeFile(draftTomlPath, &draftConfig); err != nil { 62 return nil, err 63 } 64 65 appConfig, found := draftConfig.Environments[draftEnvironment] 66 if !found { 67 return nil, fmt.Errorf("Environment %v not found", draftEnvironment) 68 } 69 70 return &App{ 71 Name: appConfig.Name, 72 Namespace: appConfig.Namespace, 73 OverridePorts: appConfig.OverridePorts}, nil 74 } 75 76 // Connect tunnels to a Kubernetes pod running the application and returns the connection information 77 func (a *App) Connect(clientset kubernetes.Interface, clientConfig *restclient.Config, targetContainer string, overridePorts []string, buildID string) (*Connection, error) { 78 var cc []*ContainerConnection 79 80 pod, err := podutil.GetPod(a.Namespace, DraftLabelKey, a.Name, BuildIDKey, buildID, clientset) 81 if err != nil { 82 return nil, err 83 } 84 m, err := getPortMapping(overridePorts) 85 if err != nil { 86 return nil, err 87 } 88 89 // if no container was specified as flag, return tunnels to all containers in pod 90 if targetContainer == "" { 91 for _, c := range pod.Spec.Containers { 92 var tt []*tunnel.Tunnel 93 94 // iterate through all ports of the contaier and create tunnels 95 for _, p := range c.Ports { 96 remote := int(p.ContainerPort) 97 local := m[remote] 98 t := tunnel.NewWithLocalTunnel(clientset.CoreV1().RESTClient(), clientConfig, a.Namespace, pod.Name, remote, local) 99 tt = append(tt, t) 100 } 101 cc = append(cc, &ContainerConnection{ 102 ContainerName: c.Name, 103 Tunnels: tt, 104 }) 105 } 106 107 return &Connection{ 108 ContainerConnections: cc, 109 PodName: pod.Name, 110 Clientset: clientset, 111 }, nil 112 } 113 var tt []*tunnel.Tunnel 114 115 // a container was specified - return tunnel to specified container 116 ports, err := getTargetContainerPorts(pod.Spec.Containers, targetContainer) 117 if err != nil { 118 return nil, err 119 } 120 121 // iterate through all ports of the container and create tunnels 122 for _, p := range ports { 123 local := m[p] 124 t := tunnel.NewWithLocalTunnel(clientset.CoreV1().RESTClient(), clientConfig, a.Namespace, pod.Name, p, local) 125 tt = append(tt, t) 126 } 127 128 cc = append(cc, &ContainerConnection{ 129 ContainerName: targetContainer, 130 Tunnels: tt, 131 }) 132 133 return &Connection{ 134 ContainerConnections: cc, 135 PodName: pod.Name, 136 Clientset: clientset, 137 }, nil 138 } 139 140 func getPortMapping(overridePorts []string) (map[int]int, error) { 141 var portMapping = make(map[int]int, len(overridePorts)) 142 143 for _, p := range overridePorts { 144 m := strings.Split(p, ":") 145 local, err := strconv.Atoi(m[0]) 146 if err != nil { 147 return nil, fmt.Errorf("cannot get port mapping: %v", err) 148 } 149 150 remote, err := strconv.Atoi(m[1]) 151 if err != nil { 152 return nil, fmt.Errorf("cannot get port mapping: %v", err) 153 } 154 155 // check if remote port already exists in port mapping 156 _, exists := portMapping[remote] 157 if exists { 158 return nil, fmt.Errorf("remote port %v already mapped", remote) 159 } 160 161 // check if local port already exists in port mapping 162 for _, l := range portMapping { 163 if local == l { 164 return nil, fmt.Errorf("local port %v already mapped", local) 165 } 166 } 167 168 portMapping[remote] = local 169 } 170 171 return portMapping, nil 172 } 173 174 // RequestLogStream returns a stream of the application pod's logs 175 func (c *Connection) RequestLogStream(namespace string, containerName string, logLines int64) (io.ReadCloser, error) { 176 req := c.Clientset.CoreV1().Pods(namespace).GetLogs(c.PodName, 177 &v1.PodLogOptions{ 178 Follow: true, 179 TailLines: &logLines, 180 Container: containerName, 181 }) 182 183 return req.Stream() 184 185 } 186 187 func getTargetContainerPorts(containers []v1.Container, targetContainer string) ([]int, error) { 188 var ports []int 189 containerFound := false 190 191 for _, c := range containers { 192 193 if c.Name == targetContainer && !containerFound { 194 containerFound = true 195 for _, p := range c.Ports { 196 ports = append(ports, int(p.ContainerPort)) 197 } 198 } 199 } 200 201 if containerFound == false { 202 return nil, fmt.Errorf("container '%s' not found", targetContainer) 203 } 204 205 return ports, nil 206 } 207 208 func (a *App) GetPodNames(buildID string, clientset kubernetes.Interface) ([]string, error) { 209 label := map[string]string{DraftLabelKey: a.Name} 210 annotations := map[string]string{BuildIDKey: buildID} 211 return podutil.ListPodNames(a.Namespace, label, annotations, clientset) 212 }