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