Don't refresh the local repo cache
[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                         Logger.Error("Command '%s' failed with error: %v, 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                 Logger.Info("command success: %s", 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                         Logger.Info("Helm init done successfully!")
86                         break
87                 }
88                 Logger.Error("helm init failed, retyring ...")
89                 time.Sleep(time.Duration(10) * time.Second)
90         }
91
92         for {
93                 if _, err := h.AddRepo(); err == nil {
94                         Logger.Info("Helm repo added successfully")
95                         break
96                 }
97                 Logger.Error("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 --skip-refresh"}, ""))
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                 Logger.Error("helm_repo_username ReadFile failed: %v", 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                 Logger.Error("helm_repo_password ReadFile failed: %v", 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         m.Namespace = h.cm.GetNamespace(m.Namespace)
153
154         if err = h.cm.GetConfigMap(m, &cm); err != nil {
155                 out, err = h.Run(getInstallArgs(m, false))
156                 if err != nil {
157                         return
158                 }
159                 return h.ParseStatus(m.Name, string(out))
160         }
161
162         // ConfigMap exists, try to override
163         out, err = h.Run(getInstallArgs(m, true))
164         if err == nil {
165                 return h.ParseStatus(m.Name, string(out))
166         }
167
168         cm, cmErr := h.cm.PurgeConfigMap(m)
169         out, err = h.Run(getInstallArgs(m, false))
170         if err != nil {
171                 return
172         }
173
174         if cmErr == nil {
175                 cmErr = h.cm.RestoreConfigMap(m, cm)
176         }
177         return h.ParseStatus(m.Name, string(out))
178 }
179
180 func (h *Helm) Status(name string) (xapp Xapp, err error) {
181         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
182         if err != nil {
183                 Logger.Error("Getting xapps status: %v", err.Error())
184                 return
185         }
186
187         return h.ParseStatus(name, string(out))
188 }
189
190 func (h *Helm) StatusAll() (xapps []Xapp, err error) {
191         xappNameList, err := h.List()
192         if err != nil {
193                 Logger.Error("Helm list failed: %v", err.Error())
194                 return
195         }
196
197         return h.parseAllStatus(xappNameList)
198 }
199
200 func (h *Helm) List() (names []string, err error) {
201         ns := h.cm.GetNamespace("")
202         out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
203         if err != nil {
204                 Logger.Error("Listing deployed xapps failed: %v", err.Error())
205                 return
206         }
207
208         return h.GetNames(string(out))
209 }
210
211 func (h *Helm) SearchAll() (names []string) {
212         return h.cm.GetNamesFromHelmRepo()
213 }
214
215 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
216         xapp, err = h.Status(name)
217         if err != nil {
218                 Logger.Error("Fetching xapp status failed: %v", err.Error())
219                 return
220         }
221
222         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
223         return xapp, err
224 }
225
226 func (h *Helm) Fetch(name, tarDir string) error {
227         if strings.HasSuffix(os.Args[0], ".test") {
228                 return nil
229         }
230
231         rname := viper.GetString("helm.repo-name") + "/"
232
233         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
234         return err
235 }
236
237 // Helper functions
238 func (h *Helm) GetVersion(name string) (version string) {
239         ns := h.cm.GetNamespace("")
240         out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
241         if err != nil {
242                 return
243         }
244
245         var re = regexp.MustCompile(`AppVersion: .*`)
246         ver := re.FindStringSubmatch(string(out))
247         if ver != nil {
248                 version = strings.Split(ver[0], ": ")[1]
249                 version, _ = strconv.Unquote(version)
250         }
251
252         return
253 }
254
255 func (h *Helm) GetState(out string) (status string) {
256         re := regexp.MustCompile(`STATUS: .*`)
257         result := re.FindStringSubmatch(string(out))
258         if result != nil {
259                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
260         }
261
262         return
263 }
264
265 func (h *Helm) GetAddress(out string) (ip, port string) {
266         var tmp string
267         re := regexp.MustCompile(`ClusterIP.*`)
268         addr := re.FindStringSubmatch(string(out))
269         if addr != nil {
270                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
271         }
272
273         return
274 }
275
276 func (h *Helm) GetEndpointInfo(name string) (ip string, port int) {
277         args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' %s -n %s", name, h.cm.GetNamespace(""))
278         out, err := KubectlExec(args)
279         if err != nil {
280                 return
281         }
282
283         return string(out), 4560
284 }
285
286 func (h *Helm) GetNames(out string) (names []string, err error) {
287         re := regexp.MustCompile(`Name: .*`)
288         result := re.FindAllStringSubmatch(out, -1)
289         if result == nil {
290                 return
291         }
292
293         for _, v := range result {
294                 xappName := strings.Split(v[0], ": ")[1]
295                 if strings.Contains(xappName, "appmgr") == false {
296                         names = append(names, xappName)
297                 }
298         }
299         return names, nil
300 }
301
302 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
303         ip, port := h.GetEndpointInfo(name)
304         if ip == "" {
305                 Logger.Info("Endpoint IP address not found, using CluserIP")
306                 ip, _ = h.GetAddress(out)
307         }
308
309         var tmp string
310         r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
311         result := r.FindStringSubmatch(string(out))
312         if result == nil {
313                 return
314         }
315
316         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
317         resources := re.FindAllStringSubmatch(string(result[0]), -1)
318         if resources != nil {
319                 for _, v := range resources {
320                         var x XappInstance
321                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
322                         x.Status = strings.ToLower(x.Status)
323                         x.Ip = ip
324                         x.Port = port
325                         x.TxMessages = msgs.TxMessages
326                         x.RxMessages = msgs.RxMessages
327                         xapp.Instances = append(xapp.Instances, x)
328                 }
329         }
330 }
331
332 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
333         xapp.Name = name
334         xapp.Version = h.GetVersion(name)
335         xapp.Status = h.GetState(out)
336
337         h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
338
339         return
340 }
341
342 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
343         xapps = []Xapp{}
344
345         for _, name := range names {
346                 err := h.cm.ReadSchema(name, &XAppConfig{})
347                 if err != nil {
348                         continue
349                 }
350
351                 x, err := h.Status(name)
352                 if err == nil {
353                         xapps = append(xapps, x)
354                 }
355         }
356
357         return
358 }
359
360 func addTillerEnv() (err error) {
361         service := viper.GetString("helm.tiller-service")
362         namespace := viper.GetString("helm.tiller-namespace")
363         port := viper.GetString("helm.tiller-port")
364
365         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
366                 Logger.Error("Tiller Env Setting Failed: %v", err.Error())
367         }
368
369         return err
370 }
371
372 func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
373         args = args + " --namespace=" + x.Namespace
374
375         if x.ImageRepo != "" {
376                 args = args + " --set global.repository=" + x.ImageRepo
377         }
378
379         if x.ServiceName != "" {
380                 args = args + " --set ricapp.service.name=" + x.ServiceName
381         }
382
383         if x.Hostname != "" {
384                 args = args + " --set ricapp.hostname=" + x.Hostname
385         }
386
387         if cmOverride == true {
388                 args = args + " --set ricapp.appconfig.override=" + x.Name + "-appconfig"
389         }
390
391         rname := viper.GetString("helm.repo-name")
392         return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
393 }