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