-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkubectlx.go
237 lines (201 loc) · 4.94 KB
/
kubectlx.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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package main
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
)
var BIN_BASE = "/usr/local/bin"
var KUBECTL_DEFAULT_PATH string = "/usr/local/bin/kubectl"
var KUBECTL_DOWNLOAD_URL_BASE string = "https://storage.googleapis.com/kubernetes-release/release/v"
type Kubectl struct {
Path string
Version string
}
// Checks if k.Path exists
func (k *Kubectl) Exists() bool {
_, err := os.Stat(k.Path)
if err != nil {
return false
}
return true
}
// Moves file from source to destination, essentialy a copy-paste function
func (k *Kubectl) Switch(destination string) (bool, error) {
from, err := os.Open(k.Path)
if err != nil {
return false, err
}
defer from.Close()
to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return false, err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return false, err
}
return true, nil
}
// Returns version number {major}-{minor}-{patch} as string
func (k *Kubectl) GetVersion() string {
// Execute kubectl to get version info
cmd := exec.Command(k.Path, "version", "--client=true", "--short")
out, _ := cmd.CombinedOutput()
// Parse output to extract version
parser := regexp.MustCompile(`(Client Version: v)(.*)`)
result := parser.FindStringSubmatch(string(out))
// Parsing failed if less than 3 elements are returned
if len(result) < 3 {
return ""
}
// Return version as string e.g 1.12.0
return result[2]
}
// Just a simple prompt for user confirmation before Download takes place
func (k *Kubectl) AskConfirmation(question string) (bool, error) {
reader := bufio.NewReader(os.Stdin)
for {
// This is a clean workaround to using log.Printf which adds a new line
fmt.Printf("%s %s [y/n]: ", time.Now().Format("2006/01/02 15:04:05"), question)
response, err := reader.ReadString('\n')
if err != nil {
return false, err
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true, nil
} else if response == "n" || response == "no" {
return false, nil
}
}
}
// Handles kubectl file download and permissions
func (k *Kubectl) Download() (bool, error) {
url := KUBECTL_DOWNLOAD_URL_BASE + k.Version + "/bin/" + runtime.GOOS + "/" + runtime.GOARCH + "/" + "kubectl"
// Get the data
resp, err := http.Get(url)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(k.Path)
if err != nil {
return false, err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
return false, err
}
// Set permissions
err = os.Chmod(k.Path, 0755)
if err != nil {
return false, err
}
return true, nil
}
func FindFiles(filesPath string) []string {
matches, err := filepath.Glob(filesPath)
if err != nil {
log.Fatalln(err)
}
if len(matches) == 0 {
return nil
}
return matches
}
func ListVersions() {
var versions []string
versionPaths := FindFiles(KUBECTL_DEFAULT_PATH + "-*")
if versionPaths == nil {
log.Fatalln("No versions installed with kubectlx")
}
for _, v := range versionPaths {
versions = append(versions, strings.Trim(v, "/usr/local/bin/kubectl-"))
}
for _, v := range versions {
fmt.Println(v)
}
os.Exit(0)
}
func Parse(argv []string) (string, error) {
if len(argv) == 0 {
PrintHelp()
} else if len(argv) > 1 {
return "", errors.New("Too many arguments")
}
return argv[0], nil
}
// This is a handler for some functions returning both bool and err
// it makes main() a bit more readable
func Check(isTrue bool, err error) bool {
if err != nil {
log.Fatal(err)
}
return isTrue
}
func PrintHelp() {
helpMsg := `Usage:
%[1]v --help: Print help menu
%[1]v list: Print list of installed versions
%[1]v {version}: Where version is the desired version, e.g: 1.12.0`
fmt.Printf(helpMsg, os.Args[0])
os.Exit(1)
}
func Handler(arg string, err error) string {
if err != nil {
log.Fatal(err)
}
switch arg {
case "list":
ListVersions()
case "--help":
PrintHelp()
}
return arg
}
func main() {
// Parse arguments
version := Handler(Parse(os.Args[1:]))
// Initialize
var current, desired Kubectl
// Set current
current.Path = KUBECTL_DEFAULT_PATH
current.Version = current.GetVersion()
// Set desired
desired.Version = version
desired.Path = KUBECTL_DEFAULT_PATH + "-" + desired.Version
// Check if kubectl default exists
if current.Exists() {
Check(current.Switch(current.Path + "-" + current.Version))
}
if !desired.Exists() {
if Check(desired.AskConfirmation("You do not have this version. Do you want to download it?")) {
if Check(desired.Download()) {
if desired.Version != desired.GetVersion() {
err := os.Remove(desired.Path)
log.Fatalf("Version %s is invalid. I have removed %s", desired.Version, desired.Path)
if err != nil {
log.Fatal(err)
}
}
}
} else {
os.Exit(0)
}
}
desired.Switch(KUBECTL_DEFAULT_PATH)
}