Add a new API to return the list of undeployed xApps
[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"}, ""))
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) GetNames(out string) (names []string, err error) {
277         re := regexp.MustCompile(`Name: .*`)
278         result := re.FindAllStringSubmatch(out, -1)
279         if result == nil {
280                 return
281         }
282
283         for _, v := range result {
284                 xappName := strings.Split(v[0], ": ")[1]
285                 if strings.Contains(xappName, "appmgr") == false {
286                         names = append(names, xappName)
287                 }
288         }
289         return names, nil
290 }
291
292 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
293         ip, port := h.GetAddress(out)
294
295         var tmp string
296         r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
297         result := r.FindStringSubmatch(string(out))
298         if result == nil {
299                 return
300         }
301
302         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
303         resources := re.FindAllStringSubmatch(string(result[0]), -1)
304         if resources != nil {
305                 for _, v := range resources {
306                         var x XappInstance
307                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
308                         x.Status = strings.ToLower(x.Status)
309                         x.Ip = ip
310                         x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
311                         x.TxMessages = msgs.TxMessages
312                         x.RxMessages = msgs.RxMessages
313                         xapp.Instances = append(xapp.Instances, x)
314                 }
315         }
316 }
317
318 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
319         xapp.Name = name
320         xapp.Version = h.GetVersion(name)
321         xapp.Status = h.GetState(out)
322
323         h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
324
325         return
326 }
327
328 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
329         xapps = []Xapp{}
330
331         for _, name := range names {
332                 err := h.cm.ReadSchema(name, &XAppConfig{})
333                 if err != nil {
334                         continue
335                 }
336
337                 x, err := h.Status(name)
338                 if err == nil {
339                         xapps = append(xapps, x)
340                 }
341         }
342
343         return
344 }
345
346 func addTillerEnv() (err error) {
347         service := viper.GetString("helm.tiller-service")
348         namespace := viper.GetString("helm.tiller-namespace")
349         port := viper.GetString("helm.tiller-port")
350
351         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
352                 Logger.Error("Tiller Env Setting Failed: %v", err.Error())
353         }
354
355         return err
356 }
357
358 func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
359         args = args + " --namespace=" + x.Namespace
360
361         if x.ImageRepo != "" {
362                 args = args + " --set global.repository=" + x.ImageRepo
363         }
364
365         if x.ServiceName != "" {
366                 args = args + " --set ricapp.service.name=" + x.ServiceName
367         }
368
369         if x.Hostname != "" {
370                 args = args + " --set ricapp.hostname=" + x.Hostname
371         }
372
373         if cmOverride == true {
374                 args = args + " --set ricapp.appconfig.override=" + x.Name + "-appconfig"
375         }
376
377         rname := viper.GetString("helm.repo-name")
378         return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
379 }