Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/dev/kubecdl/main.go
2492 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package main
6
7
import (
8
"bytes"
9
"encoding/json"
10
"errors"
11
"fmt"
12
"io/ioutil"
13
"os"
14
"os/exec"
15
"path/filepath"
16
"strings"
17
18
"github.com/sirupsen/logrus"
19
"github.com/spf13/pflag"
20
)
21
22
var (
23
project = pflag.StringP("project", "p", "workspace-clusters", "name of the Google project - defaults to what's configured in gcloud")
24
kubeconfig = pflag.StringP("kubeconfig", "k", os.Getenv("KUBECONFIG"), "kubeconfig filepath")
25
)
26
27
func main() {
28
pflag.Parse()
29
30
clusterName := pflag.Arg(0)
31
if clusterName == "" {
32
logrus.Fatalf("usage: %s [--project|-p GoogleProject] [--kubeconfig|-k ~/.kube/config] <clusterName>", os.Args[0])
33
}
34
35
_, err := exec.LookPath("gcloud")
36
if err != nil {
37
logrus.WithError(err).Fatal("gcloud is not available")
38
}
39
40
prj := *project
41
if prj == "" {
42
out, err := exec.Command("gcloud", "config", "get-value", "project").CombinedOutput()
43
if err != nil {
44
logrus.WithError(err).Fatal("cannot get configured project. Use --project to explicitly set one.")
45
}
46
prj = strings.TrimSpace(string(out))
47
}
48
kubecfgfn := *kubeconfig
49
if kubecfgfn == "" {
50
home, err := os.UserHomeDir()
51
if err != nil {
52
logrus.Fatal(err)
53
}
54
55
if _, err := os.Stat(filepath.Join(home, ".kube")); errors.Is(err, os.ErrNotExist) {
56
err := os.Mkdir(filepath.Join(home, ".kube"), os.ModePerm)
57
if err != nil {
58
logrus.Fatal(err)
59
}
60
}
61
62
kubecfgfn = filepath.Join(home, ".kube", "config")
63
}
64
65
serverIP, err := getServerIP(clusterName, prj)
66
if err != nil {
67
logrus.WithError(err).Fatal("cannot get cluster IP")
68
}
69
nodeName, zone, err := getNodeName(clusterName, prj)
70
if err != nil {
71
logrus.WithError(err).Fatal("cannot get node name")
72
}
73
kubecfg, err := getK3sKubeconfig(nodeName, zone, prj)
74
if err != nil {
75
logrus.WithError(err).Fatal("cannot get kubeconfig")
76
}
77
78
kubecfg = bytes.ReplaceAll(kubecfg, []byte("127.0.0.1"), []byte(serverIP))
79
kubecfg = bytes.ReplaceAll(kubecfg, []byte("default"), []byte(clusterName))
80
81
tmpfile, err := os.CreateTemp("", "kubecfg-*.yaml")
82
if err != nil {
83
logrus.Fatal(err)
84
}
85
_, err = tmpfile.Write(kubecfg)
86
if err != nil {
87
logrus.WithError(err).Fatal("cannot write temporary kubeconfig")
88
}
89
tmpfile.Close()
90
defer os.Remove(tmpfile.Name())
91
92
cmd := exec.Command("kubectl", "config", "view", "--flatten", "--merge")
93
cmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s:%s", kubecfgfn, tmpfile.Name()))
94
res, err := cmd.CombinedOutput()
95
if err != nil {
96
logrus.WithError(err).Error("cannot merge kubeconfig")
97
return
98
}
99
100
err = ioutil.WriteFile(kubecfgfn, res, 0644)
101
if err != nil {
102
logrus.WithError(err).WithField("path", kubecfgfn).Error("cannot write kubeconfig. Dumping combined result:")
103
fmt.Println(string(res))
104
return
105
}
106
}
107
108
func getServerIP(clusterName, project string) (sererIP string, err error) {
109
var nfo []struct {
110
IPAddress string
111
Name string `json:"name"`
112
}
113
114
out, err := exec.Command("gcloud", "compute", "forwarding-rules", "list", "--format=json", "--project", project).CombinedOutput()
115
if err != nil {
116
return "", fmt.Errorf("failed to describe loadbalance: %s: %w", string(out), err)
117
}
118
119
err = json.Unmarshal(out, &nfo)
120
if err != nil {
121
return "", fmt.Errorf("failed to unmarshal loadbalance description: %s: %w", string(out), err)
122
}
123
expectName := "server-ws-" + clusterName
124
for _, lb := range nfo {
125
if lb.Name == expectName {
126
return lb.IPAddress, nil
127
}
128
}
129
130
return "", fmt.Errorf("did not find public IP for cluster")
131
}
132
133
func getNodeName(clusterName, project string) (nodeName, zone string, err error) {
134
var nfo []struct {
135
Name string `json:"name"`
136
Zone string `json:"zone"`
137
}
138
139
out, err := exec.Command("gcloud", "compute", "instances", "list", "--format=json", "--quiet", "--project", project, "--filter", "labels.cluster-name>=ws-"+clusterName+" AND labels.cluster-name<=ws-"+clusterName+" AND labels.instance-type>=control-plane AND labels.instance-type<=control-plane").CombinedOutput()
140
if err != nil {
141
return "", "", fmt.Errorf("failed to describe node instances: %s: %w", string(out), err)
142
}
143
144
err = json.Unmarshal(out, &nfo)
145
if err != nil {
146
return "", "", fmt.Errorf("failed to unmarshal node instances: %s: %w", string(out), err)
147
}
148
if len(nfo) > 0 {
149
z := strings.Split(nfo[0].Zone, "/")
150
151
return nfo[0].Name, z[len(z)-1], nil
152
}
153
return "", "", fmt.Errorf("did not find node for cluster")
154
}
155
156
func getK3sKubeconfig(nodeName, zone, project string) ([]byte, error) {
157
res, err := exec.Command("gcloud", "compute", "ssh", "--project", project, "--zone", zone, "--command", "sudo cat /etc/rancher/k3s/k3s.yaml", nodeName, "--ssh-flag=-p 2222").CombinedOutput()
158
if err != nil {
159
return nil, fmt.Errorf("cannot ssh into node: %s: %w", string(res), err)
160
}
161
162
// we may have had to login first, i.e. the output might not be the pure kubeconfig file
163
idx := bytes.Index(res, []byte("apiVersion:"))
164
if idx == -1 {
165
return nil, fmt.Errorf("did not find kubeconfig")
166
}
167
168
return res[idx:], nil
169
}
170
171