Parse message names from xApp configMap
[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         m.Namespace = 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                 mdclog(MdclogErr, formatLog("Getting xapps status", "", 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                 mdclog(MdclogErr, formatLog("Helm list failed", "", err.Error()))
194                 return
195         }
196
197         return h.parseAllStatus(xappNameList)
198 }
199
200 func (h *Helm) List() (names []string, err error) {
201         ns := getNamespace("")
202         out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
203         if err != nil {
204                 mdclog(MdclogErr, formatLog("Listing deployed xapps failed", "", err.Error()))
205                 return
206         }
207
208         return h.GetNames(string(out))
209 }
210
211 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
212         xapp, err = h.Status(name)
213         if err != nil {
214                 mdclog(MdclogErr, formatLog("Fetching xapp status failed", "", err.Error()))
215                 return
216         }
217
218         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
219         return xapp, err
220 }
221
222 func (h *Helm) Fetch(name, tarDir string) error {
223         if strings.HasSuffix(os.Args[0], ".test") {
224                 return nil
225         }
226
227         rname := viper.GetString("helm.repo-name") + "/"
228
229         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
230         return err
231 }
232
233 // Helper functions
234 func (h *Helm) GetVersion(name string) (version string) {
235         ns := getNamespace("")
236         out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
237         if err != nil {
238                 return
239         }
240
241         var re = regexp.MustCompile(`AppVersion: .*`)
242         ver := re.FindStringSubmatch(string(out))
243         if ver != nil {
244                 version = strings.Split(ver[0], ": ")[1]
245                 version, _ = strconv.Unquote(version)
246         }
247
248         return
249 }
250
251 func (h *Helm) GetState(out string) (status string) {
252         re := regexp.MustCompile(`STATUS: .*`)
253         result := re.FindStringSubmatch(string(out))
254         if result != nil {
255                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
256         }
257
258         return
259 }
260
261 func (h *Helm) GetAddress(out string) (ip, port string) {
262         var tmp string
263         re := regexp.MustCompile(`ClusterIP.*`)
264         addr := re.FindStringSubmatch(string(out))
265         if addr != nil {
266                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
267         }
268
269         return
270 }
271
272 func (h *Helm) GetNames(out string) (names []string, err error) {
273         re := regexp.MustCompile(`Name: .*`)
274         result := re.FindAllStringSubmatch(out, -1)
275         if result == nil {
276                 return
277         }
278
279         for _, v := range result {
280                 xappName := strings.Split(v[0], ": ")[1]
281                 if strings.Contains(xappName, "appmgr") == false {
282                         names = append(names, xappName)
283                 }
284         }
285         return names, nil
286 }
287
288 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
289         ip, port := h.GetAddress(out)
290
291         var tmp string
292         r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
293         result := r.FindStringSubmatch(string(out))
294         if result == nil {
295                 return
296         }
297
298         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
299         resources := re.FindAllStringSubmatch(string(result[0]), -1)
300         if resources != nil {
301                 for _, v := range resources {
302                         var x XappInstance
303                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
304                         x.Status = strings.ToLower(x.Status)
305                         x.Ip = ip
306                         x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
307                         x.TxMessages = msgs.TxMessages
308                         x.RxMessages = msgs.RxMessages
309                         xapp.Instances = append(xapp.Instances, x)
310                 }
311         }
312 }
313
314 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
315         xapp.Name = name
316         xapp.Version = h.GetVersion(name)
317         xapp.Status = h.GetState(out)
318
319         h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
320
321         return
322 }
323
324 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
325         xapps = []Xapp{}
326
327         for _, name := range names {
328                 x, err := h.Status(name)
329                 if err == nil {
330                         xapps = append(xapps, x)
331                 }
332         }
333
334         return
335 }
336
337 func addTillerEnv() (err error) {
338         service := viper.GetString("helm.tiller-service")
339         namespace := viper.GetString("helm.tiller-namespace")
340         port := viper.GetString("helm.tiller-port")
341
342         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
343                 mdclog(MdclogErr, formatLog("Tiller Env Setting Failed", "", err.Error()))
344         }
345
346         return err
347 }
348
349 func getNamespace(namespace string) string {
350         if namespace != "" {
351                 return namespace
352         }
353
354         ns := viper.GetString("xapp.namespace")
355         if ns == "" {
356                 ns = "ricxapp"
357         }
358         return ns
359 }
360
361 func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
362         args = args + " --namespace=" + x.Namespace
363
364         if x.ImageRepo != "" {
365                 args = args + " --set global.repository=" + x.ImageRepo
366         }
367
368         if x.ServiceName != "" {
369                 args = args + " --set ricapp.service.name=" + x.ServiceName
370         }
371
372         if x.Hostname != "" {
373                 args = args + " --set ricapp.hostname=" + x.Hostname
374         }
375
376         if cmOverride == true {
377                 args = args + " --set ricapp.appconfig.override=" + x.Name + "-appconfig"
378         }
379
380         rname := viper.GetString("helm.repo-name")
381         return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
382 }
383
384 func formatLog(text string, args string, err string) string {
385         return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
386 }