388fb5c1e7c45cf1291eea1c8645bb76531a8c93
[ric-plt/appmgr.git] / cmd / appmgr / helm.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package main
21
22 import (
23         "bytes"
24         "errors"
25         "fmt"
26         "github.com/spf13/viper"
27         "io/ioutil"
28         "log"
29         "os"
30         "os/exec"
31         "regexp"
32         "strconv"
33         "strings"
34         "time"
35 )
36
37 var execCommand = exec.Command
38
39 func Exec(args string) (out []byte, err error) {
40         cmd := execCommand("/bin/sh", "-c", args)
41
42         var stdout bytes.Buffer
43         var stderr bytes.Buffer
44         cmd.Stdout = &stdout
45         cmd.Stderr = &stderr
46
47         log.Println("Running command: ", cmd)
48         for i := 0; i < viper.GetInt("helm.retry"); i++ {
49                 err = cmd.Run()
50                 if err != nil {
51                         mdclog(MdclogErr, formatLog("Command failed, retrying", args, err.Error()+stderr.String()))
52                         time.Sleep(time.Duration(2) * time.Second)
53                         continue
54                 }
55                 break
56         }
57
58         if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
59                 mdclog(MdclogDebug, formatLog("command success", stdout.String(), ""))
60                 return stdout.Bytes(), nil
61         }
62
63         return stdout.Bytes(), errors.New(stderr.String())
64 }
65
66 var HelmExec = func(args string) (out []byte, err error) {
67         return Exec(strings.Join([]string{"helm", args}, " "))
68 }
69
70 var KubectlExec = func(args string) (out []byte, err error) {
71         return Exec(strings.Join([]string{"kubectl", args}, " "))
72 }
73
74 func (h *Helm) SetCM(cm ConfigMapper) {
75         h.cm = cm
76 }
77
78 func (h *Helm) Initialize() {
79         if h.initDone == true {
80                 return
81         }
82
83         for {
84                 if _, err := h.Init(); err == nil {
85                         mdclog(MdclogDebug, formatLog("Helm init done successfully!", "", ""))
86                         break
87                 }
88                 mdclog(MdclogErr, formatLog("helm init failed, retyring ...", "", ""))
89                 time.Sleep(time.Duration(10) * time.Second)
90         }
91
92         for {
93                 if _, err := h.AddRepo(); err == nil {
94                         mdclog(MdclogDebug, formatLog("Helm repo added successfully", "", ""))
95                         break
96                 }
97                 mdclog(MdclogErr, formatLog("Helm repo addition failed, retyring ...", "", ""))
98                 time.Sleep(time.Duration(10) * time.Second)
99         }
100
101         h.initDone = true
102 }
103
104 func (h *Helm) Run(args string) (out []byte, err error) {
105         return HelmExec(args)
106 }
107
108 // API functions
109 func (h *Helm) Init() (out []byte, err error) {
110         // Add Tiller address as environment variable
111         if err := addTillerEnv(); err != nil {
112                 return out, err
113         }
114
115         return HelmExec(strings.Join([]string{"init -c"}, ""))
116 }
117
118 func (h *Helm) AddRepo() (out []byte, err error) {
119         // Get helm repo user name and password from files mounted by secret object
120         credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
121         if err != nil {
122                 mdclog(MdclogErr, formatLog("helm_repo_username ReadFile failed", "", err.Error()))
123                 return
124         }
125
126         username := " --username " + string(credFile)
127
128         credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
129         if err != nil {
130                 mdclog(MdclogErr, formatLog("helm_repo_password ReadFile failed", "", err.Error()))
131                 return
132         }
133
134         pwd := " --password " + string(credFile)
135
136         // Get internal helm repo name
137         rname := viper.GetString("helm.repo-name")
138
139         // Get helm repo address from values.yaml
140         repo := viper.GetString("helm.repo")
141
142         return HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
143 }
144
145 func (h *Helm) Install(m XappDeploy) (xapp Xapp, err error) {
146         out, err := h.Run(strings.Join([]string{"repo update "}, ""))
147         if err != nil {
148                 return
149         }
150
151         var cm interface{}
152         if err = h.cm.ReadConfigMap(m.Name, m.Namespace, &cm); err != nil {
153                 out, err = h.Run(getInstallArgs(m, false))
154                 if err != nil {
155                         return
156                 }
157                 return h.ParseStatus(m.Name, string(out))
158         }
159
160         // ConfigMap exists, try to override
161         out, err = h.Run(getInstallArgs(m, true))
162         if err == nil {
163                 return h.ParseStatus(m.Name, string(out))
164         }
165
166         cm, cmErr := h.cm.PurgeConfigMap(m)
167         out, err = h.Run(getInstallArgs(m, false))
168         if err != nil {
169                 return
170         }
171
172         if cmErr == nil {
173                 cmErr = h.cm.RestoreConfigMap(m, cm)
174         }
175         return h.ParseStatus(m.Name, string(out))
176 }
177
178 func (h *Helm) Status(name string) (xapp Xapp, err error) {
179         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
180         if err != nil {
181                 mdclog(MdclogErr, formatLog("Getting xapps status", "", err.Error()))
182                 return
183         }
184
185         return h.ParseStatus(name, string(out))
186 }
187
188 func (h *Helm) StatusAll() (xapps []Xapp, err error) {
189         xappNameList, err := h.List()
190         if err != nil {
191                 mdclog(MdclogErr, formatLog("Helm list failed", "", err.Error()))
192                 return
193         }
194
195         return h.parseAllStatus(xappNameList)
196 }
197
198 func (h *Helm) List() (names []string, err error) {
199         ns := getNamespace("")
200         out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
201         if err != nil {
202                 mdclog(MdclogErr, formatLog("Listing deployed xapps failed", "", err.Error()))
203                 return
204         }
205
206         return h.GetNames(string(out))
207 }
208
209 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
210         xapp, err = h.Status(name)
211         if err != nil {
212                 mdclog(MdclogErr, formatLog("Fetching xapp status failed", "", err.Error()))
213                 return
214         }
215
216         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
217         return xapp, err
218 }
219
220 func (h *Helm) Fetch(name, tarDir string) error {
221         if strings.HasSuffix(os.Args[0], ".test") {
222                 return nil
223         }
224
225         rname := viper.GetString("helm.repo-name") + "/"
226
227         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
228         return err
229 }
230
231 // Helper functions
232 func (h *Helm) GetVersion(name string) (version string) {
233         ns := getNamespace("")
234         out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
235         if err != nil {
236                 return
237         }
238
239         var re = regexp.MustCompile(`AppVersion: .*`)
240         ver := re.FindStringSubmatch(string(out))
241         if ver != nil {
242                 version = strings.Split(ver[0], ": ")[1]
243                 version, _ = strconv.Unquote(version)
244         }
245
246         return
247 }
248
249 func (h *Helm) GetState(out string) (status string) {
250         re := regexp.MustCompile(`STATUS: .*`)
251         result := re.FindStringSubmatch(string(out))
252         if result != nil {
253                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
254         }
255
256         return
257 }
258
259 func (h *Helm) GetAddress(out string) (ip, port string) {
260         var tmp string
261         re := regexp.MustCompile(`ClusterIP.*`)
262         addr := re.FindStringSubmatch(string(out))
263         if addr != nil {
264                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
265         }
266
267         return
268 }
269
270 func (h *Helm) GetNames(out string) (names []string, err error) {
271         re := regexp.MustCompile(`Name: .*`)
272         result := re.FindAllStringSubmatch(out, -1)
273         if result == nil {
274                 return
275         }
276
277         for _, v := range result {
278                 xappName := strings.Split(v[0], ": ")[1]
279                 if strings.Contains(xappName, "appmgr") == false {
280                         names = append(names, xappName)
281                 }
282         }
283         return names, nil
284 }
285
286 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
287         ip, port := h.GetAddress(out)
288
289         var tmp string
290         r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
291         result := r.FindStringSubmatch(string(out))
292         if result == nil {
293                 return
294         }
295
296         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
297         resources := re.FindAllStringSubmatch(string(result[0]), -1)
298         if resources != nil {
299                 for _, v := range resources {
300                         var x XappInstance
301                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
302                         x.Status = strings.ToLower(x.Status)
303                         x.Ip = ip
304                         x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
305                         x.TxMessages = msgs.TxMessages
306                         x.RxMessages = msgs.RxMessages
307                         xapp.Instances = append(xapp.Instances, x)
308                 }
309         }
310 }
311
312 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
313         xapp.Name = name
314         xapp.Version = h.GetVersion(name)
315         xapp.Status = h.GetState(out)
316
317         h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
318
319         return
320 }
321
322 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
323         xapps = []Xapp{}
324
325         for _, name := range names {
326                 x, err := h.Status(name)
327                 if err == nil {
328                         xapps = append(xapps, x)
329                 }
330         }
331
332         return
333 }
334
335 func addTillerEnv() (err error) {
336         service := viper.GetString("helm.tiller-service")
337         namespace := viper.GetString("helm.tiller-namespace")
338         port := viper.GetString("helm.tiller-port")
339
340         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
341                 mdclog(MdclogErr, formatLog("Tiller Env Setting Failed", "", err.Error()))
342         }
343
344         return err
345 }
346
347 func getNamespace(namespace string) string {
348         if namespace != "" {
349                 return namespace
350         }
351
352         ns := viper.GetString("xapp.namespace")
353         if ns == "" {
354                 ns = "ricxapp"
355         }
356         return ns
357 }
358
359 func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
360         x.Namespace = getNamespace(x.Namespace)
361         args = args + " --namespace=" + x.Namespace
362
363         if x.ImageRepo != "" {
364                 args = args + " --set image.repository=" + x.ImageRepo
365         }
366
367         if x.ServiceName != "" {
368                 args = args + " --set service.name=" + x.ServiceName
369         }
370
371         if x.Hostname != "" {
372                 args = args + " --set hostname=" + x.Hostname
373         }
374
375         if cmOverride == true {
376                 args = args + " --set appconfig.override=true"
377         }
378
379         rname := viper.GetString("helm.repo-name")
380         return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
381 }
382
383 func formatLog(text string, args string, err string) string {
384         return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
385 }