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