github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/cluster/tunnel.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package cluster contains Jackal-specific cluster management functions.
     5  package cluster
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/Racer159/jackal/src/types"
    12  
    13  	"github.com/Racer159/jackal/src/config"
    14  	"github.com/Racer159/jackal/src/pkg/k8s"
    15  	"github.com/Racer159/jackal/src/pkg/message"
    16  	v1 "k8s.io/api/core/v1"
    17  )
    18  
    19  // Jackal specific connect strings
    20  const (
    21  	JackalRegistry = "REGISTRY"
    22  	JackalLogging  = "LOGGING"
    23  	JackalGit      = "GIT"
    24  	JackalInjector = "INJECTOR"
    25  
    26  	JackalInjectorName  = "jackal-injector"
    27  	JackalInjectorPort  = 5000
    28  	JackalRegistryName  = "jackal-docker-registry"
    29  	JackalRegistryPort  = 5000
    30  	JackalGitServerName = "jackal-gitea-http"
    31  	JackalGitServerPort = 3000
    32  )
    33  
    34  // TunnelInfo is a struct that contains the necessary info to create a new k8s.Tunnel
    35  type TunnelInfo struct {
    36  	localPort    int
    37  	remotePort   int
    38  	namespace    string
    39  	resourceType string
    40  	resourceName string
    41  	urlSuffix    string
    42  }
    43  
    44  // NewTunnelInfo returns a new TunnelInfo object for connecting to a cluster
    45  func NewTunnelInfo(namespace, resourceType, resourceName, urlSuffix string, localPort, remotePort int) TunnelInfo {
    46  	return TunnelInfo{
    47  		namespace:    namespace,
    48  		resourceType: resourceType,
    49  		resourceName: resourceName,
    50  		urlSuffix:    urlSuffix,
    51  		localPort:    localPort,
    52  		remotePort:   remotePort,
    53  	}
    54  }
    55  
    56  // PrintConnectTable will print a table of all Jackal connect matches found in the cluster.
    57  func (c *Cluster) PrintConnectTable() error {
    58  	list, err := c.GetServicesByLabelExists(v1.NamespaceAll, config.JackalConnectLabelName)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	connections := make(types.ConnectStrings)
    64  
    65  	for _, svc := range list.Items {
    66  		name := svc.Labels[config.JackalConnectLabelName]
    67  
    68  		// Add the connectString for processing later in the deployment.
    69  		connections[name] = types.ConnectString{
    70  			Description: svc.Annotations[config.JackalConnectAnnotationDescription],
    71  			URL:         svc.Annotations[config.JackalConnectAnnotationURL],
    72  		}
    73  	}
    74  
    75  	message.PrintConnectStringTable(connections)
    76  
    77  	return nil
    78  }
    79  
    80  // Connect will establish a tunnel to the specified target.
    81  func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) {
    82  	var err error
    83  	zt := TunnelInfo{
    84  		namespace:    JackalNamespaceName,
    85  		resourceType: k8s.SvcResource,
    86  	}
    87  
    88  	switch strings.ToUpper(target) {
    89  	case JackalRegistry:
    90  		zt.resourceName = JackalRegistryName
    91  		zt.remotePort = JackalRegistryPort
    92  		zt.urlSuffix = `/v2/_catalog`
    93  
    94  	case JackalLogging:
    95  		zt.resourceName = "jackal-loki-stack-grafana"
    96  		zt.remotePort = 3000
    97  		// Start the logs with something useful.
    98  		zt.urlSuffix = `/monitor/explore?orgId=1&left=%5B"now-12h","now","Loki",%7B"refId":"Jackal%20Logs","expr":"%7Bnamespace%3D%5C"jackal%5C"%7D"%7D%5D`
    99  
   100  	case JackalGit:
   101  		zt.resourceName = JackalGitServerName
   102  		zt.remotePort = JackalGitServerPort
   103  
   104  	case JackalInjector:
   105  		zt.resourceName = JackalInjectorName
   106  		zt.remotePort = JackalInjectorPort
   107  
   108  	default:
   109  		if target != "" {
   110  			if zt, err = c.checkForJackalConnectLabel(target); err != nil {
   111  				return nil, fmt.Errorf("problem looking for a jackal connect label in the cluster: %s", err.Error())
   112  			}
   113  		}
   114  
   115  		if zt.resourceName == "" {
   116  			return nil, fmt.Errorf("missing resource name")
   117  		}
   118  		if zt.remotePort < 1 {
   119  			return nil, fmt.Errorf("missing remote port")
   120  		}
   121  	}
   122  
   123  	return c.ConnectTunnelInfo(zt)
   124  }
   125  
   126  // ConnectTunnelInfo connects to the cluster with the provided TunnelInfo
   127  func (c *Cluster) ConnectTunnelInfo(zt TunnelInfo) (*k8s.Tunnel, error) {
   128  	tunnel, err := c.NewTunnel(zt.namespace, zt.resourceType, zt.resourceName, zt.urlSuffix, zt.localPort, zt.remotePort)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	_, err = tunnel.Connect()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return tunnel, nil
   139  }
   140  
   141  // ConnectToJackalRegistryEndpoint determines if a registry endpoint is in cluster, and if so opens a tunnel to connect to it
   142  func (c *Cluster) ConnectToJackalRegistryEndpoint(registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) {
   143  	registryEndpoint := registryInfo.Address
   144  
   145  	var err error
   146  	var tunnel *k8s.Tunnel
   147  	if registryInfo.InternalRegistry {
   148  		// Establish a registry tunnel to send the images to the jackal registry
   149  		if tunnel, err = c.NewTunnel(JackalNamespaceName, k8s.SvcResource, JackalRegistryName, "", 0, JackalRegistryPort); err != nil {
   150  			return "", tunnel, err
   151  		}
   152  	} else {
   153  		svcInfo, err := c.ServiceInfoFromNodePortURL(registryInfo.Address)
   154  
   155  		// If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource
   156  		if err == nil {
   157  			if tunnel, err = c.NewTunnel(svcInfo.Namespace, k8s.SvcResource, svcInfo.Name, "", 0, svcInfo.Port); err != nil {
   158  				return "", tunnel, err
   159  			}
   160  		}
   161  	}
   162  
   163  	if tunnel != nil {
   164  		_, err = tunnel.Connect()
   165  		if err != nil {
   166  			return "", tunnel, err
   167  		}
   168  		registryEndpoint = tunnel.Endpoint()
   169  	}
   170  
   171  	return registryEndpoint, tunnel, nil
   172  }
   173  
   174  // checkForJackalConnectLabel looks in the cluster for a connect name that matches the target
   175  func (c *Cluster) checkForJackalConnectLabel(name string) (TunnelInfo, error) {
   176  	var err error
   177  	var zt TunnelInfo
   178  
   179  	message.Debugf("Looking for a Jackal Connect Label in the cluster")
   180  
   181  	matches, err := c.GetServicesByLabel("", config.JackalConnectLabelName, name)
   182  	if err != nil {
   183  		return zt, fmt.Errorf("unable to lookup the service: %w", err)
   184  	}
   185  
   186  	if len(matches.Items) > 0 {
   187  		// If there is a match, use the first one as these are supposed to be unique.
   188  		svc := matches.Items[0]
   189  
   190  		// Reset based on the matched params.
   191  		zt.resourceType = k8s.SvcResource
   192  		zt.resourceName = svc.Name
   193  		zt.namespace = svc.Namespace
   194  		// Only support a service with a single port.
   195  		zt.remotePort = svc.Spec.Ports[0].TargetPort.IntValue()
   196  		// if targetPort == 0, look for Port (which is required)
   197  		if zt.remotePort == 0 {
   198  			zt.remotePort = c.FindPodContainerPort(svc)
   199  		}
   200  
   201  		// Add the url suffix too.
   202  		zt.urlSuffix = svc.Annotations[config.JackalConnectAnnotationURL]
   203  
   204  		message.Debugf("tunnel connection match: %s/%s on port %d", svc.Namespace, svc.Name, zt.remotePort)
   205  	} else {
   206  		return zt, fmt.Errorf("no matching services found for %s", name)
   207  	}
   208  
   209  	return zt, nil
   210  }