-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal.go
212 lines (176 loc) · 5.93 KB
/
local.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
211
212
package local
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/BurntSushi/toml"
"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"github.com/Azure/draft/pkg/draft/manifest"
"github.com/Azure/draft/pkg/draft/tunnel"
"github.com/Azure/draft/pkg/kube/podutil"
)
const (
// DraftLabelKey is the label selector key on a pod that allows
// us to identify which draft app a pod is associated with
DraftLabelKey = "draft"
// BuildIDKey is the label selector key on a pod that specifies
// the build ID of the application
BuildIDKey = "buildID"
)
// App encapsulates information about an application to connect to
//
// Name is the name of the application
// Namespace is the Kubernetes namespace it is deployed in
// Container is the name the name of the application container to connect to
// OverridePorts contains mappings of which local port to map a remote port to
// and will be in the form local_port:remote_port i.e. 8080:8081
type App struct {
Name string
Namespace string
Container string
OverridePorts []string
}
// Connection encapsulated information to connect to an application
type Connection struct {
ContainerConnections []*ContainerConnection
PodName string
Clientset kubernetes.Interface
}
// ContainerConnection encapsulates a connection to a container in a pod
type ContainerConnection struct {
Tunnels []*tunnel.Tunnel
ContainerName string
}
// DeployedApplication returns deployment information about the deployed instance
// of the source code given a path to your draft.toml file and the name of the
// draft environment
func DeployedApplication(draftTomlPath, draftEnvironment string) (*App, error) {
var draftConfig manifest.Manifest
if _, err := toml.DecodeFile(draftTomlPath, &draftConfig); err != nil {
return nil, err
}
appConfig, found := draftConfig.Environments[draftEnvironment]
if !found {
return nil, fmt.Errorf("Environment %v not found", draftEnvironment)
}
return &App{
Name: appConfig.Name,
Namespace: appConfig.Namespace,
OverridePorts: appConfig.OverridePorts}, nil
}
// Connect tunnels to a Kubernetes pod running the application and returns the connection information
func (a *App) Connect(clientset kubernetes.Interface, clientConfig *restclient.Config, targetContainer string, overridePorts []string, buildID string) (*Connection, error) {
var cc []*ContainerConnection
pod, err := podutil.GetPod(a.Namespace, DraftLabelKey, a.Name, BuildIDKey, buildID, clientset)
if err != nil {
return nil, err
}
m, err := getPortMapping(overridePorts)
if err != nil {
return nil, err
}
// if no container was specified as flag, return tunnels to all containers in pod
if targetContainer == "" {
for _, c := range pod.Spec.Containers {
var tt []*tunnel.Tunnel
// iterate through all ports of the contaier and create tunnels
for _, p := range c.Ports {
remote := int(p.ContainerPort)
local := m[remote]
t := tunnel.NewWithLocalTunnel(clientset.CoreV1().RESTClient(), clientConfig, a.Namespace, pod.Name, remote, local)
tt = append(tt, t)
}
cc = append(cc, &ContainerConnection{
ContainerName: c.Name,
Tunnels: tt,
})
}
return &Connection{
ContainerConnections: cc,
PodName: pod.Name,
Clientset: clientset,
}, nil
}
var tt []*tunnel.Tunnel
// a container was specified - return tunnel to specified container
ports, err := getTargetContainerPorts(pod.Spec.Containers, targetContainer)
if err != nil {
return nil, err
}
// iterate through all ports of the container and create tunnels
for _, p := range ports {
local := m[p]
t := tunnel.NewWithLocalTunnel(clientset.CoreV1().RESTClient(), clientConfig, a.Namespace, pod.Name, p, local)
tt = append(tt, t)
}
cc = append(cc, &ContainerConnection{
ContainerName: targetContainer,
Tunnels: tt,
})
return &Connection{
ContainerConnections: cc,
PodName: pod.Name,
Clientset: clientset,
}, nil
}
func getPortMapping(overridePorts []string) (map[int]int, error) {
var portMapping = make(map[int]int, len(overridePorts))
for _, p := range overridePorts {
m := strings.Split(p, ":")
local, err := strconv.Atoi(m[0])
if err != nil {
return nil, fmt.Errorf("cannot get port mapping: %v", err)
}
remote, err := strconv.Atoi(m[1])
if err != nil {
return nil, fmt.Errorf("cannot get port mapping: %v", err)
}
// check if remote port already exists in port mapping
_, exists := portMapping[remote]
if exists {
return nil, fmt.Errorf("remote port %v already mapped", remote)
}
// check if local port already exists in port mapping
for _, l := range portMapping {
if local == l {
return nil, fmt.Errorf("local port %v already mapped", local)
}
}
portMapping[remote] = local
}
return portMapping, nil
}
// RequestLogStream returns a stream of the application pod's logs
func (c *Connection) RequestLogStream(namespace string, containerName string, logLines int64) (io.ReadCloser, error) {
req := c.Clientset.CoreV1().Pods(namespace).GetLogs(c.PodName,
&v1.PodLogOptions{
Follow: true,
TailLines: &logLines,
Container: containerName,
})
return req.Stream()
}
func getTargetContainerPorts(containers []v1.Container, targetContainer string) ([]int, error) {
var ports []int
containerFound := false
for _, c := range containers {
if c.Name == targetContainer && !containerFound {
containerFound = true
for _, p := range c.Ports {
ports = append(ports, int(p.ContainerPort))
}
}
}
if containerFound == false {
return nil, fmt.Errorf("container '%s' not found", targetContainer)
}
return ports, nil
}
func (a *App) GetPodNames(buildID string, clientset kubernetes.Interface) ([]string, error) {
label := map[string]string{DraftLabelKey: a.Name}
annotations := map[string]string{BuildIDKey: buildID}
return podutil.ListPodNames(a.Namespace, label, annotations, clientset)
}