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 }