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