Improve appmgr UT coverage
[ric-plt/appmgr.git] / pkg / helm / 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 helm
21
22 import (
23         "fmt"
24         "github.com/ghodss/yaml"
25         "github.com/spf13/viper"
26         "io/ioutil"
27         "os"
28         "regexp"
29         "strconv"
30         "strings"
31         "time"
32
33         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
34         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/cm"
35         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/models"
36         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
37 )
38
39 var kubeExec = util.KubectlExec
40 var helmExec = util.HelmExec
41
42 type Helm struct {
43         initDone bool
44         cm       *cm.CM
45 }
46
47 func NewHelm() *Helm {
48         return &Helm{initDone: false, cm: cm.NewCM()}
49 }
50
51 func (h *Helm) Initialize() {
52         if h.initDone == true {
53                 return
54         }
55
56         for {
57                 if _, err := h.Init(); err == nil {
58                         appmgr.Logger.Info("Helm init done successfully!")
59                         break
60                 }
61                 appmgr.Logger.Info("helm init failed, retyring ...")
62                 time.Sleep(time.Duration(10) * time.Second)
63         }
64
65         for {
66                 if _, err := h.AddRepo(); err == nil {
67                         appmgr.Logger.Info("Helm repo added successfully")
68                         break
69                 }
70                 appmgr.Logger.Info("Helm repo addition failed, retyring ...")
71                 time.Sleep(time.Duration(10) * time.Second)
72         }
73         h.initDone = true
74 }
75
76 func (h *Helm) Run(args string) (out []byte, err error) {
77         return helmExec(args)
78 }
79
80 // API functions
81 func (h *Helm) Init() (out []byte, err error) {
82         if err := h.AddTillerEnv(); err != nil {
83                 return out, err
84         }
85
86         return helmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
87 }
88
89 func (h *Helm) AddRepo() (out []byte, err error) {
90         // Get helm repo user name and password from files mounted by secret object
91         username, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
92         if err != nil {
93                 appmgr.Logger.Info("helm_repo_username ReadFile failed: %v", err.Error())
94                 return
95         }
96
97         password, err := ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
98         if err != nil {
99                 appmgr.Logger.Info("helm_repo_password ReadFile failed: %v", err.Error())
100                 return
101         }
102
103         repoArgs := fmt.Sprintf(" %s %s ", viper.GetString("helm.repo-name"), viper.GetString("helm.repo"))
104         credentials := fmt.Sprintf(" --username %s --password %s", string(username), string(password))
105
106         return helmExec(strings.Join([]string{"repo add ", repoArgs, credentials}, ""))
107 }
108
109 func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
110         m.Namespace = h.cm.GetNamespace(m.Namespace)
111
112         out, err := h.Run(strings.Join([]string{"repo update "}, ""))
113         if err != nil {
114                 return
115         }
116
117         out, err = h.Run(h.GetInstallArgs(m, false))
118         if err != nil {
119                 return
120         }
121         return h.ParseStatus(*m.XappName, string(out))
122 }
123
124 func (h *Helm) Status(name string) (xapp models.Xapp, err error) {
125         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
126         if err != nil {
127                 appmgr.Logger.Info("Getting xapps status: %v", err.Error())
128                 return
129         }
130         return h.ParseStatus(name, string(out))
131 }
132
133 func (h *Helm) StatusAll() (xapps models.AllDeployedXapps, err error) {
134         xappNameList, err := h.List()
135         if err != nil {
136                 appmgr.Logger.Info("Helm list failed: %v", err.Error())
137                 return
138         }
139
140         return h.parseAllStatus(xappNameList)
141 }
142
143 func (h *Helm) List() (names []string, err error) {
144         ns := h.cm.GetNamespace("")
145         out, err := h.Run(strings.Join([]string{"list --all --deployed --output yaml --namespace=", ns}, ""))
146         if err != nil {
147                 appmgr.Logger.Info("Listing deployed xapps failed: %v", err.Error())
148                 return
149         }
150
151         return h.GetNames(string(out))
152 }
153
154 func (h *Helm) SearchAll() models.AllDeployableXapps {
155         return h.cm.GetNamesFromHelmRepo()
156 }
157
158 func (h *Helm) Delete(name string) (xapp models.Xapp, err error) {
159         xapp, err = h.Status(name)
160         if err != nil {
161                 appmgr.Logger.Info("Fetching xapp status failed: %v", err.Error())
162                 return
163         }
164
165         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
166         return xapp, err
167 }
168
169 func (h *Helm) Fetch(name, tarDir string) error {
170         if strings.HasSuffix(os.Args[0], ".test") {
171                 return nil
172         }
173
174         rname := viper.GetString("helm.repo-name") + "/"
175
176         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
177         return err
178 }
179
180 // Helper functions
181 func (h *Helm) GetVersion(name string) (version string) {
182         ns := h.cm.GetNamespace("")
183         out, err := h.Run(strings.Join([]string{"list --deployed --output yaml --namespace=", ns, " ", name}, ""))
184         if err != nil {
185                 return
186         }
187
188         var re = regexp.MustCompile(`AppVersion: .*`)
189         ver := re.FindStringSubmatch(string(out))
190         if ver != nil {
191                 version = strings.Split(ver[0], ": ")[1]
192                 version, _ = strconv.Unquote(version)
193         }
194
195         return
196 }
197
198 func (h *Helm) GetState(out string) (status string) {
199         re := regexp.MustCompile(`STATUS: .*`)
200         result := re.FindStringSubmatch(string(out))
201         if result != nil {
202                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
203         }
204
205         return
206 }
207
208 func (h *Helm) GetAddress(out string) (ip, port string) {
209         var tmp string
210         re := regexp.MustCompile(`ClusterIP.*`)
211         addr := re.FindStringSubmatch(string(out))
212         if addr != nil {
213                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
214         }
215
216         return
217 }
218
219 func (h *Helm) GetEndpointInfo(name string) (svc string, port int) {
220         port = 4560 // Default
221         ns := h.cm.GetNamespace("")
222         args := fmt.Sprintf(" get service -n %s service-%s-%s-rmr -o json", ns, ns, name)
223         out, err := kubeExec(args)
224         if err != nil {
225                 return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
226         }
227         appmgr.Logger.Debug("Endpoint IP address of %s: %s", name, string(out))
228
229         v, err := h.cm.ParseJson(string(out))
230         if err != nil {
231                 return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
232         }
233
234         for _, p := range v.GetArray("spec", "ports") {
235                 if string(p.GetStringBytes("name")) == "rmrdata" {
236                         port = int(p.GetInt("port"))
237                         break
238                 }
239         }
240         appmgr.Logger.Info("service-%s-%s-rmr.%s %d", ns, name, ns, port)
241
242         return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), port
243 }
244
245 func (h *Helm) GetNames(out string) (names []string, err error) {
246         re := regexp.MustCompile(`Name: .*`)
247         result := re.FindAllStringSubmatch(out, -1)
248         if result == nil {
249                 return
250         }
251
252         for _, v := range result {
253                 xappName := strings.Split(v[0], ": ")[1]
254                 if strings.Contains(xappName, "appmgr") == false {
255                         names = append(names, xappName)
256                 }
257         }
258         return names, nil
259 }
260
261 func (h *Helm) FillInstanceData(name string, out string, xapp *models.Xapp, rtData appmgr.RtmData) {
262         ip, port := h.GetEndpointInfo(name)
263         if ip == "" {
264                 appmgr.Logger.Info("Endpoint IP address not found, using CluserIP")
265                 ip, _ = h.GetAddress(out)
266         }
267
268         var tmp string
269         r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
270         result := r.FindStringSubmatch(string(out))
271         if result == nil {
272                 return
273         }
274
275         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
276         resources := re.FindAllStringSubmatch(string(result[0]), -1)
277         if resources != nil {
278                 for _, v := range resources {
279                         var x models.XappInstance
280                         var name string
281                         fmt.Sscanf(v[0], "%s %s %s", &name, &tmp, &x.Status)
282                         x.Name = &name
283                         x.Status = strings.ToLower(x.Status)
284                         x.IP = ip
285                         x.Port = int64(port)
286                         x.TxMessages = rtData.TxMessages
287                         x.RxMessages = rtData.RxMessages
288                         x.Policies = rtData.Policies
289                         xapp.Instances = append(xapp.Instances, &x)
290                 }
291         }
292 }
293
294 func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error) {
295         xapp.Name = &name
296         xapp.Version = h.GetVersion(name)
297         xapp.Status = h.GetState(out)
298
299         h.FillInstanceData(name, out, &xapp, h.cm.GetRtmData(name))
300         return
301 }
302
303 func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
304         xapps = models.AllDeployedXapps{}
305         for _, name := range names {
306                 var desc interface{}
307                 err := h.cm.ReadSchema(name, &desc)
308                 if err != nil {
309                         continue
310                 }
311
312                 x, err := h.Status(name)
313                 if err == nil {
314                         xapps = append(xapps, &x)
315                 }
316         }
317         return
318 }
319
320 func (h *Helm) AddTillerEnv() (err error) {
321         service := viper.GetString("helm.tiller-service")
322         namespace := viper.GetString("helm.tiller-namespace")
323         port := viper.GetString("helm.tiller-port")
324
325         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
326                 appmgr.Logger.Info("Tiller Env Setting Failed: %v", err.Error())
327         }
328         return err
329 }
330
331 func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
332         args = fmt.Sprintf("%s --namespace=%s", args, x.Namespace)
333         if x.HelmVersion != "" {
334                 args = fmt.Sprintf("%s --version=%s", args, x.HelmVersion)
335         }
336
337         if x.ReleaseName != "" {
338                 args = fmt.Sprintf("%s --name=%s", args, x.ReleaseName)
339         } else {
340                 args = fmt.Sprintf("%s --name=%s", args, *x.XappName)
341         }
342
343         if cmOverride == true {
344                 args = fmt.Sprintf("%s --set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
345         }
346
347         if x.OverrideFile != nil {
348                 if overrideYaml, err := yaml.JSONToYAML([]byte(x.OverrideFile.(string))); err == nil {
349                         err = ioutil.WriteFile("/tmp/appmgr_override.yaml", overrideYaml, 0644)
350                         if err != nil {
351                                 appmgr.Logger.Info("ioutil.WriteFile(/tmp/appmgr_override.yaml) failed: %v", err)
352                         } else {
353                                 args = args + " -f=/tmp/appmgr_override.yaml"
354                         }
355                 } else {
356                         appmgr.Logger.Info("yaml.JSONToYAML failed: %v", err)
357                 }
358         }
359
360         repoName := viper.GetString("helm.repo-name")
361         if repoName == "" {
362                 repoName = "helm-repo"
363         }
364         return fmt.Sprintf("install %s/%s %s", repoName, *x.XappName, args)
365 }