New standard GO project layout
[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         "gopkg.in/yaml.v2"
28         "io/ioutil"
29         "log"
30         "os"
31         "os/exec"
32         "path"
33         "regexp"
34         "strconv"
35         "strings"
36         "time"
37 )
38
39 var execCommand = exec.Command
40
41 func Exec(args string) (out []byte, err error) {
42         cmd := execCommand("/bin/sh", "-c", strings.Join([]string{"helm", args}, " "))
43
44         if !strings.HasSuffix(os.Args[0], ".test") {
45                 out, err = cmd.CombinedOutput()
46                 if err != nil {
47                         mdclog(MdclogErr, formatLog("Command failed", args, err.Error()))
48                 }
49                 return out, err
50         }
51
52         var stdout bytes.Buffer
53         var stderr bytes.Buffer
54         cmd.Stdout = &stdout
55         cmd.Stderr = &stderr
56
57         log.Printf("Running command: %v", cmd)
58         for i := 0; i < 3; i++ {
59                 err = cmd.Run()
60                 if err != nil {
61                         mdclog(MdclogErr, formatLog("Command failed, retrying", args, err.Error()+stderr.String()))
62                         time.Sleep(time.Duration(5) * time.Second)
63                         continue
64                 }
65                 break
66         }
67
68         if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
69                 mdclog(MdclogDebug, formatLog("command success", stdout.String(), ""))
70                 return stdout.Bytes(), nil
71         }
72
73         return stdout.Bytes(), errors.New(stderr.String())
74 }
75
76 func (h *Helm) Initialize() {
77         if h.initDone == true {
78                 return
79         }
80
81         for {
82                 if _, err := h.Init(); err == nil {
83                         mdclog(MdclogDebug, formatLog("Helm init done successfully!", "", ""))
84                         break
85                 }
86                 mdclog(MdclogErr, formatLog("helm init failed, retyring ...", "", ""))
87                 time.Sleep(time.Duration(10) * time.Second)
88         }
89
90         for {
91                 if _, err := h.AddRepo(); err == nil {
92                         mdclog(MdclogDebug, formatLog("Helm repo added successfully", "", ""))
93                         break
94                 }
95                 mdclog(MdclogErr, formatLog("Helm repo addition failed, retyring ...", "", ""))
96                 time.Sleep(time.Duration(10) * time.Second)
97         }
98         h.initDone = true
99 }
100
101 func (h *Helm) Run(args string) (out []byte, err error) {
102         return Exec(args)
103 }
104
105 // API functions
106 func (h *Helm) Init() (out []byte, err error) {
107
108         // Add Tiller address as environment variable
109         if err := addTillerEnv(); err != nil {
110                 return out, err
111         }
112
113         return Exec(strings.Join([]string{"init -c"}, ""))
114 }
115
116 func (h *Helm) AddRepo() (out []byte, err error) {
117
118         // Get helm repo user name and password from files mounted by secret object
119         credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
120         if err != nil {
121                 mdclog(MdclogErr, formatLog("helm_repo_username ReadFile failed", "", err.Error()))
122                 return
123         }
124
125         username := " --username " + string(credFile)
126
127         credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
128         if err != nil {
129                 mdclog(MdclogErr, formatLog("helm_repo_password ReadFile failed", "", err.Error()))
130                 return
131         }
132
133         pwd := " --password " + string(credFile)
134
135         // Get internal helm repo name
136         rname := viper.GetString("helm.repo-name")
137
138         // Get helm repo address from values.yaml
139         repo := viper.GetString("helm.repo")
140
141         return Exec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
142 }
143
144 func (h *Helm) Install(name string) (xapp Xapp, err error) {
145         out, err := h.Run(strings.Join([]string{"repo update "}, ""))
146         if err != nil {
147                 return
148         }
149
150         rname := viper.GetString("helm.repo-name")
151
152         ns := getNamespaceArgs()
153         out, err = h.Run(strings.Join([]string{"install ", rname, "/", name, " --name ", name, ns}, ""))
154         if err != nil {
155                 return
156         }
157
158         return h.ParseStatus(name, string(out))
159 }
160
161 func (h *Helm) Status(name string) (xapp Xapp, err error) {
162
163         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
164         if err != nil {
165                 mdclog(MdclogErr, formatLog("Getting xapps status", "", err.Error()))
166                 return
167         }
168
169         return h.ParseStatus(name, string(out))
170 }
171
172 func (h *Helm) StatusAll() (xapps []Xapp, err error) {
173         xappNameList, err := h.List()
174         if err != nil {
175                 mdclog(MdclogErr, formatLog("Helm list failed", "", err.Error()))
176                 return
177         }
178
179         return h.parseAllStatus(xappNameList)
180 }
181
182 func (h *Helm) List() (names []string, err error) {
183
184         ns := getNamespaceArgs()
185         out, err := h.Run(strings.Join([]string{"list --all --output yaml ", ns}, ""))
186         if err != nil {
187                 mdclog(MdclogErr, formatLog("Listing deployed xapps failed", "", err.Error()))
188                 return
189         }
190
191         return h.GetNames(string(out))
192 }
193
194 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
195         xapp, err = h.Status(name)
196         if err != nil {
197                 mdclog(MdclogErr, formatLog("Fetching xapp status failed", "", err.Error()))
198                 return
199         }
200
201         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
202         return xapp, err
203 }
204
205 func (h *Helm) Fetch(name, tarDir string) error {
206         if strings.HasSuffix(os.Args[0], ".test") {
207                 return nil
208         }
209
210         rname := viper.GetString("helm.repo-name") + "/"
211
212         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
213         return err
214 }
215
216 // Helper functions
217 func (h *Helm) GetMessages(name string) (msgs MessageTypes, err error) {
218         tarDir := viper.GetString("xapp.tarDir")
219         if tarDir == "" {
220                 tarDir = "/tmp"
221         }
222
223         if h.Fetch(name, tarDir); err != nil {
224                 mdclog(MdclogWarn, formatLog("Fetch chart failed", "", err.Error()))
225                 return
226         }
227
228         return h.ParseMessages(name, tarDir, viper.GetString("xapp.msg_type_file"))
229
230 }
231
232 func (h *Helm) ParseMessages(name string, chartDir, msgFile string) (msgs MessageTypes, err error) {
233         yamlFile, err := ioutil.ReadFile(path.Join(chartDir, name, msgFile))
234         if err != nil {
235                 mdclog(MdclogWarn, formatLog("ReadFile failed", "", err.Error()))
236                 return
237         }
238
239         err = yaml.Unmarshal(yamlFile, &msgs)
240         if err != nil {
241                 mdclog(MdclogWarn, formatLog("Unmarshal failed", "", err.Error()))
242                 return
243         }
244
245         if err = os.RemoveAll(path.Join(chartDir, name)); err != nil {
246                 mdclog(MdclogWarn, formatLog("RemoveAll failed", "", err.Error()))
247         }
248
249         return
250 }
251
252 func (h *Helm) GetVersion(name string) (version string) {
253
254         ns := getNamespaceArgs()
255         out, err := h.Run(strings.Join([]string{"list --output yaml ", name, ns}, ""))
256         if err != nil {
257                 return
258         }
259
260         var re = regexp.MustCompile(`AppVersion: .*`)
261         ver := re.FindStringSubmatch(string(out))
262         if ver != nil {
263                 version = strings.Split(ver[0], ": ")[1]
264                 version, _ = strconv.Unquote(version)
265         }
266
267         return
268 }
269
270 func (h *Helm) GetState(out string) (status string) {
271         re := regexp.MustCompile(`STATUS: .*`)
272         result := re.FindStringSubmatch(string(out))
273         if result != nil {
274                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
275         }
276
277         return
278 }
279
280 func (h *Helm) GetAddress(out string) (ip, port string) {
281         var tmp string
282         re := regexp.MustCompile(`ClusterIP.*`)
283         addr := re.FindStringSubmatch(string(out))
284         if addr != nil {
285                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
286         }
287
288         return
289 }
290
291 func (h *Helm) GetNames(out string) (names []string, err error) {
292         re := regexp.MustCompile(`Name: .*`)
293         result := re.FindAllStringSubmatch(out, -1)
294         if result == nil {
295                 return
296         }
297
298         for _, v := range result {
299                 xappName := strings.Split(v[0], ": ")[1]
300                 if strings.Contains(xappName, "appmgr") == false {
301                         names = append(names, xappName)
302                 }
303         }
304         return names, nil
305 }
306
307 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
308         ip, port := h.GetAddress(out)
309
310         var tmp string
311         r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
312         result := r.FindStringSubmatch(string(out))
313         if result == nil {
314                 return
315         }
316
317         re := regexp.MustCompile(name + "-(\\d+).*")
318         resources := re.FindAllStringSubmatch(string(result[0]), -1)
319         if resources != nil {
320                 for _, v := range resources {
321                         var x XappInstance
322                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
323                         x.Status = strings.ToLower(x.Status)
324                         x.Ip = ip
325                         x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
326                         x.TxMessages = msgs.TxMessages
327                         x.RxMessages = msgs.RxMessages
328                         xapp.Instances = append(xapp.Instances, x)
329                 }
330         }
331 }
332
333 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
334
335         xapp.Name = name
336         xapp.Version = h.GetVersion(name)
337         xapp.Status = h.GetState(out)
338
339         types, err := h.GetMessages(name)
340         if err != nil {
341                 // xAPP can still be deployed if the msg_type file is missing.
342                 mdclog(MdclogWarn, formatLog("method GetMessages Failed....", "", err.Error()))
343
344                 //Set err back to nil, so it does not cause issues in called functions.
345                 err = nil
346         }
347
348         h.FillInstanceData(name, out, &xapp, types)
349
350         return
351 }
352
353 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
354         xapps = []Xapp{}
355
356         for _, name := range names {
357                 x, err := h.Status(name)
358                 if err == nil {
359                         xapps = append(xapps, x)
360                 }
361         }
362
363         return
364 }
365
366 func addTillerEnv() (err error) {
367
368         service := viper.GetString("helm.tiller-service")
369         namespace := viper.GetString("helm.tiller-namespace")
370         port := viper.GetString("helm.tiller-port")
371
372         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
373                 mdclog(MdclogErr, formatLog("Tiller Env Setting Failed", "", err.Error()))
374         }
375
376         return err
377 }
378
379 func getNamespaceArgs() string {
380         ns := viper.GetString("xapp.namespace")
381         if ns == "" {
382                 ns = "ricxapp"
383         }
384         return " --namespace=" + ns
385 }
386
387 func formatLog(text string, args string, err string) string {
388         return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
389 }