appmgr namespace changes
[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 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         username, 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
94         password, err := ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
95         if err != nil {
96                 appmgr.Logger.Info("helm_repo_password ReadFile failed: %v", err.Error())
97                 return
98         }
99
100         repoArgs := fmt.Sprintf(" %s %s ", viper.GetString("helm.repo-name"), viper.GetString("helm.repo"))
101         credentials := fmt.Sprintf(" --username %s --password %s", string(username), string(password))
102
103         return util.HelmExec(strings.Join([]string{"repo add ", repoArgs, credentials}, ""))
104 }
105
106 func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
107         m.Namespace = h.cm.GetNamespace(m.Namespace)
108
109         out, err := h.Run(strings.Join([]string{"repo update "}, ""))
110         if err != nil {
111                 return
112         }
113
114         out, err = h.Run(h.GetInstallArgs(m, false))
115         if err != nil {
116                 return
117         }
118         return h.ParseStatus(*m.XappName, string(out))
119 }
120
121 func (h *Helm) Status(name string) (xapp models.Xapp, err error) {
122         out, err := h.Run(strings.Join([]string{"status ", name}, ""))
123         if err != nil {
124                 appmgr.Logger.Info("Getting xapps status: %v", err.Error())
125                 return
126         }
127
128         return h.ParseStatus(name, string(out))
129 }
130
131 func (h *Helm) StatusAll() (xapps models.AllDeployedXapps, err error) {
132         xappNameList, err := h.List()
133         if err != nil {
134                 appmgr.Logger.Info("Helm list failed: %v", err.Error())
135                 return
136         }
137
138         return h.parseAllStatus(xappNameList)
139 }
140
141 func (h *Helm) List() (names []string, err error) {
142         ns := h.cm.GetNamespace("")
143         out, err := h.Run(strings.Join([]string{"list --all --deployed --output yaml --namespace=", ns}, ""))
144         if err != nil {
145                 appmgr.Logger.Info("Listing deployed xapps failed: %v", err.Error())
146                 return
147         }
148
149         return h.GetNames(string(out))
150 }
151
152 func (h *Helm) SearchAll() models.AllDeployableXapps {
153         return h.cm.GetNamesFromHelmRepo()
154 }
155
156 func (h *Helm) Delete(name string) (xapp models.Xapp, err error) {
157         xapp, err = h.Status(name)
158         if err != nil {
159                 appmgr.Logger.Info("Fetching xapp status failed: %v", err.Error())
160                 return
161         }
162
163         _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
164         return xapp, err
165 }
166
167 func (h *Helm) Fetch(name, tarDir string) error {
168         if strings.HasSuffix(os.Args[0], ".test") {
169                 return nil
170         }
171
172         rname := viper.GetString("helm.repo-name") + "/"
173
174         _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
175         return err
176 }
177
178 // Helper functions
179 func (h *Helm) GetVersion(name string) (version string) {
180         ns := h.cm.GetNamespace("")
181         out, err := h.Run(strings.Join([]string{"list --deployed --output yaml --namespace=", ns, " ", name}, ""))
182         if err != nil {
183                 return
184         }
185
186         var re = regexp.MustCompile(`AppVersion: .*`)
187         ver := re.FindStringSubmatch(string(out))
188         if ver != nil {
189                 version = strings.Split(ver[0], ": ")[1]
190                 version, _ = strconv.Unquote(version)
191         }
192
193         return
194 }
195
196 func (h *Helm) GetState(out string) (status string) {
197         re := regexp.MustCompile(`STATUS: .*`)
198         result := re.FindStringSubmatch(string(out))
199         if result != nil {
200                 status = strings.ToLower(strings.Split(result[0], ": ")[1])
201         }
202
203         return
204 }
205
206 func (h *Helm) GetAddress(out string) (ip, port string) {
207         var tmp string
208         re := regexp.MustCompile(`ClusterIP.*`)
209         addr := re.FindStringSubmatch(string(out))
210         if addr != nil {
211                 fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
212         }
213
214         return
215 }
216
217 func (h *Helm) GetEndpointInfo(name string) (svc string, port int) {
218         port = 4560 // Default
219         ns := h.cm.GetNamespace("")
220         args := fmt.Sprintf(" get service -n %s service-%s-%s-rmr -o json", ns, ns, name)
221         out, err := util.KubectlExec(args)
222         if err != nil {
223                 return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
224         }
225         appmgr.Logger.Debug("Endpoint IP address of %s: %s", name, string(out))
226
227         v, err := h.cm.ParseJson(string(out))
228         if err != nil {
229                 return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
230         }
231
232         for _, p := range v.GetArray("spec", "ports") {
233                 if string(p.GetStringBytes("name")) == "rmrdata" {
234                         port = int(p.GetInt("port"))
235                         break
236                 }
237         }
238         appmgr.Logger.Info("service-%s-%s-rmr.%s %d", ns, name, ns, port)
239
240         return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), port
241 }
242
243 func (h *Helm) GetNames(out string) (names []string, err error) {
244         re := regexp.MustCompile(`Name: .*`)
245         result := re.FindAllStringSubmatch(out, -1)
246         if result == nil {
247                 return
248         }
249
250         for _, v := range result {
251                 xappName := strings.Split(v[0], ": ")[1]
252                 if strings.Contains(xappName, "appmgr") == false {
253                         names = append(names, xappName)
254                 }
255         }
256         return names, nil
257 }
258
259 func (h *Helm) FillInstanceData(name string, out string, xapp *models.Xapp, rtData appmgr.RtmData) {
260         ip, port := h.GetEndpointInfo(name)
261         if ip == "" {
262                 appmgr.Logger.Info("Endpoint IP address not found, using CluserIP")
263                 ip, _ = h.GetAddress(out)
264         }
265
266         var tmp string
267         r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
268         result := r.FindStringSubmatch(string(out))
269         if result == nil {
270                 return
271         }
272
273         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
274         resources := re.FindAllStringSubmatch(string(result[0]), -1)
275         if resources != nil {
276                 for _, v := range resources {
277                         var x models.XappInstance
278                         var name string
279                         fmt.Sscanf(v[0], "%s %s %s", &name, &tmp, &x.Status)
280                         x.Name = &name
281                         x.Status = strings.ToLower(x.Status)
282                         x.IP = ip
283                         x.Port = int64(port)
284                         x.TxMessages = rtData.TxMessages
285                         x.RxMessages = rtData.RxMessages
286                         x.Policies = rtData.Policies
287                         xapp.Instances = append(xapp.Instances, &x)
288                 }
289         }
290 }
291
292 func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error) {
293         xapp.Name = &name
294         xapp.Version = h.GetVersion(name)
295         xapp.Status = h.GetState(out)
296
297         h.FillInstanceData(name, out, &xapp, h.cm.GetRtmData(name))
298         return
299 }
300
301 func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
302         xapps = models.AllDeployedXapps{}
303         for _, name := range names {
304                 var desc interface{}
305                 err := h.cm.ReadSchema(name, &desc)
306                 if err != nil {
307                         continue
308                 }
309
310                 x, err := h.Status(name)
311                 if err == nil {
312                         xapps = append(xapps, &x)
313                 }
314         }
315         return
316 }
317
318 func (h *Helm) AddTillerEnv() (err error) {
319         service := viper.GetString("helm.tiller-service")
320         namespace := viper.GetString("helm.tiller-namespace")
321         port := viper.GetString("helm.tiller-port")
322
323         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
324                 appmgr.Logger.Info("Tiller Env Setting Failed: %v", err.Error())
325         }
326         return err
327 }
328
329 func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
330         args = fmt.Sprintf("%s --namespace=%s", args, x.Namespace)
331         if x.HelmVersion != "" {
332                 args = fmt.Sprintf("%s --version=%s", args, x.HelmVersion)
333         }
334
335         if x.ReleaseName != "" {
336                 args = fmt.Sprintf("%s --name=%s", args, x.ReleaseName)
337         } else {
338                 args = fmt.Sprintf("%s --name=%s", args, *x.XappName)
339         }
340
341         if cmOverride == true {
342                 args = fmt.Sprintf("%s ---set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
343         }
344
345         if x.OverrideFile != nil {
346                 if overrideYaml, err := yaml.JSONToYAML([]byte(x.OverrideFile.(string))); err == nil {
347                         err = ioutil.WriteFile("/tmp/appmgr_override.yaml", overrideYaml, 0644)
348                         if err != nil {
349                                 appmgr.Logger.Info("ioutil.WriteFile(/tmp/appmgr_override.yaml) failed: %v", err)
350                         } else {
351                                 args = args + " -f=/tmp/appmgr_override.yaml"
352                         }
353                 } else {
354                         appmgr.Logger.Info("yaml.JSONToYAML failed: %v", err)
355                 }
356         }
357
358         repoName := viper.GetString("helm.repo-name")
359         if repoName == "" {
360                 repoName = "helm-repo"
361         }
362         return fmt.Sprintf("install %s/%s %s", repoName, *x.XappName, args)
363 }