Initial RIC Configuration Management
[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 func HelmExec(args string) (out []byte, err error) {
67         return Exec(strings.Join([]string{"helm", args}, " "))
68 }
69
70 func KubectlExec(args string) (out []byte, err error) {
71         return Exec(strings.Join([]string{"kubectl", args}, " "))
72 }
73
74 func (h *Helm) Initialize() {
75         if h.initDone == true {
76                 return
77         }
78
79         for {
80                 if _, err := h.Init(); err == nil {
81                         mdclog(MdclogDebug, formatLog("Helm init done successfully!", "", ""))
82                         break
83                 }
84                 mdclog(MdclogErr, formatLog("helm init failed, retyring ...", "", ""))
85                 time.Sleep(time.Duration(10) * time.Second)
86         }
87
88         for {
89                 if _, err := h.AddRepo(); err == nil {
90                         mdclog(MdclogDebug, formatLog("Helm repo added successfully", "", ""))
91                         break
92                 }
93                 mdclog(MdclogErr, formatLog("Helm repo addition failed, retyring ...", "", ""))
94                 time.Sleep(time.Duration(10) * time.Second)
95         }
96
97         h.initDone = true
98 }
99
100 func (h *Helm) Run(args string) (out []byte, err error) {
101         return HelmExec(args)
102 }
103
104 // API functions
105 func (h *Helm) Init() (out []byte, err error) {
106
107         // Add Tiller address as environment variable
108         if err := addTillerEnv(); err != nil {
109                 return out, err
110         }
111
112         return HelmExec(strings.Join([]string{"init -c"}, ""))
113 }
114
115 func (h *Helm) AddRepo() (out []byte, err error) {
116
117         // Get helm repo user name and password from files mounted by secret object
118         credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
119         if err != nil {
120                 mdclog(MdclogErr, formatLog("helm_repo_username ReadFile failed", "", err.Error()))
121                 return
122         }
123
124         username := " --username " + string(credFile)
125
126         credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
127         if err != nil {
128                 mdclog(MdclogErr, formatLog("helm_repo_password ReadFile failed", "", err.Error()))
129                 return
130         }
131
132         pwd := " --password " + string(credFile)
133
134         // Get internal helm repo name
135         rname := viper.GetString("helm.repo-name")
136
137         // Get helm repo address from values.yaml
138         repo := viper.GetString("helm.repo")
139
140         return HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
141 }
142
143 func (h *Helm) Install(m ConfigMetadata) (xapp Xapp, err error) {
144         out, err := h.Run(strings.Join([]string{"repo update "}, ""))
145         if err != nil {
146                 return
147         }
148
149         m.Namespace = getNamespace(m.Namespace)
150         cm, cmErr := PurgeConfigMap(m)
151
152         ns := " --namespace=" + m.Namespace
153         rname := viper.GetString("helm.repo-name")
154         out, err = h.Run(strings.Join([]string{"install ", rname, "/", m.Name, " --name ", m.Name, ns}, ""))
155         if err != nil {
156                 return
157         }
158
159         if cmErr == nil {
160                 cmErr = RestoreConfigMap(m, cm)
161         }
162         return h.ParseStatus(m.Name, string(out))
163 }
164
165 func (h *Helm) Status(name string) (xapp Xapp, err error) {
166
167         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
168         if err != nil {
169                 mdclog(MdclogErr, formatLog("Getting xapps status", "", err.Error()))
170                 return
171         }
172
173         return h.ParseStatus(name, string(out))
174 }
175
176 func (h *Helm) StatusAll() (xapps []Xapp, err error) {
177         xappNameList, err := h.List()
178         if err != nil {
179                 mdclog(MdclogErr, formatLog("Helm list failed", "", err.Error()))
180                 return
181         }
182
183         return h.parseAllStatus(xappNameList)
184 }
185
186 func (h *Helm) List() (names []string, err error) {
187
188         ns := getNamespace("")
189         out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
190         if err != nil {
191                 mdclog(MdclogErr, formatLog("Listing deployed xapps failed", "", err.Error()))
192                 return
193         }
194
195         return h.GetNames(string(out))
196 }
197
198 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
199         xapp, err = h.Status(name)
200         if err != nil {
201                 mdclog(MdclogErr, formatLog("Fetching xapp status failed", "", err.Error()))
202                 return
203         }
204
205         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
206         return xapp, err
207 }
208
209 func (h *Helm) Fetch(name, tarDir string) error {
210         if strings.HasSuffix(os.Args[0], ".test") {
211                 return nil
212         }
213
214         rname := viper.GetString("helm.repo-name") + "/"
215
216         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
217         return err
218 }
219
220 // Helper functions
221 func (h *Helm) GetVersion(name string) (version string) {
222
223         ns := getNamespace("")
224         out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
225         if err != nil {
226                 return
227         }
228
229         var re = regexp.MustCompile(`AppVersion: .*`)
230         ver := re.FindStringSubmatch(string(out))
231         if ver != nil {
232                 version = strings.Split(ver[0], ": ")[1]
233                 version, _ = strconv.Unquote(version)
234         }
235
236         return
237 }
238
239 func (h *Helm) GetState(out string) (status string) {
240         re := regexp.MustCompile(`STATUS: .*`)
241         result := re.FindStringSubmatch(string(out))
242         if result != nil {
243                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
244         }
245
246         return
247 }
248
249 func (h *Helm) GetAddress(out string) (ip, port string) {
250         var tmp string
251         re := regexp.MustCompile(`ClusterIP.*`)
252         addr := re.FindStringSubmatch(string(out))
253         if addr != nil {
254                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
255         }
256
257         return
258 }
259
260 func (h *Helm) GetNames(out string) (names []string, err error) {
261         re := regexp.MustCompile(`Name: .*`)
262         result := re.FindAllStringSubmatch(out, -1)
263         if result == nil {
264                 return
265         }
266
267         for _, v := range result {
268                 xappName := strings.Split(v[0], ": ")[1]
269                 if strings.Contains(xappName, "appmgr") == false {
270                         names = append(names, xappName)
271                 }
272         }
273         return names, nil
274 }
275
276 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
277         ip, port := h.GetAddress(out)
278
279         var tmp string
280         r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
281         result := r.FindStringSubmatch(string(out))
282         if result == nil {
283                 return
284         }
285
286         re := regexp.MustCompile(name + "-(\\d+).*")
287         resources := re.FindAllStringSubmatch(string(result[0]), -1)
288         if resources != nil {
289                 for _, v := range resources {
290                         var x XappInstance
291                         fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
292                         x.Status = strings.ToLower(x.Status)
293                         x.Ip = ip
294                         x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
295                         x.TxMessages = msgs.TxMessages
296                         x.RxMessages = msgs.RxMessages
297                         xapp.Instances = append(xapp.Instances, x)
298                 }
299         }
300 }
301
302 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
303
304         xapp.Name = name
305         xapp.Version = h.GetVersion(name)
306         xapp.Status = h.GetState(out)
307
308         types, err := GetMessages(name)
309         if err != nil {
310                 // xAPP can still be deployed if the msg_type file is missing.
311                 mdclog(MdclogWarn, formatLog("GetMessages Failed....", "", err.Error()))
312
313                 //Set err back to nil, so it does not cause issues in called functions.
314                 err = nil
315         }
316
317         h.FillInstanceData(name, out, &xapp, types)
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
337         service := viper.GetString("helm.tiller-service")
338         namespace := viper.GetString("helm.tiller-namespace")
339         port := viper.GetString("helm.tiller-port")
340
341         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
342                 mdclog(MdclogErr, formatLog("Tiller Env Setting Failed", "", err.Error()))
343         }
344
345         return err
346 }
347
348 func getNamespace(namespace string) string {
349         if namespace != "" {
350                 return namespace
351         }
352
353         ns := viper.GetString("xapp.namespace")
354         if ns == "" {
355                 ns = "ricxapp"
356         }
357         return ns
358 }
359
360 func formatLog(text string, args string, err string) string {
361         return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
362 }