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  }