Update Kubectl and sdlgo versions
[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) (ip string, port int) {
218         ns := h.cm.GetNamespace("")
219         args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' service-%s-%s-rmr -n %s", ns, name, ns)
220         out, err := util.KubectlExec(args)
221         if err != nil {
222                 return
223         }
224         appmgr.Logger.Info("Endpoint IP address of %s: %s", name, string(out))
225         return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
226 }
227
228 func (h *Helm) GetNames(out string) (names []string, err error) {
229         re := regexp.MustCompile(`Name: .*`)
230         result := re.FindAllStringSubmatch(out, -1)
231         if result == nil {
232                 return
233         }
234
235         for _, v := range result {
236                 xappName := strings.Split(v[0], ": ")[1]
237                 if strings.Contains(xappName, "appmgr") == false {
238                         names = append(names, xappName)
239                 }
240         }
241         return names, nil
242 }
243
244 func (h *Helm) FillInstanceData(name string, out string, xapp *models.Xapp, rtData appmgr.RtmData) {
245         ip, port := h.GetEndpointInfo(name)
246         if ip == "" {
247                 appmgr.Logger.Info("Endpoint IP address not found, using CluserIP")
248                 ip, _ = h.GetAddress(out)
249         }
250
251         var tmp string
252         r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
253         result := r.FindStringSubmatch(string(out))
254         if result == nil {
255                 return
256         }
257
258         re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
259         resources := re.FindAllStringSubmatch(string(result[0]), -1)
260         if resources != nil {
261                 for _, v := range resources {
262                         var x models.XappInstance
263                         var name string
264                         fmt.Sscanf(v[0], "%s %s %s", &name, &tmp, &x.Status)
265                         x.Name = &name
266                         x.Status = strings.ToLower(x.Status)
267                         x.IP = ip
268                         x.Port = int64(port)
269                         x.TxMessages = rtData.TxMessages
270                         x.RxMessages = rtData.RxMessages
271                         x.Policies = rtData.Policies
272                         xapp.Instances = append(xapp.Instances, &x)
273                 }
274         }
275 }
276
277 func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error) {
278         xapp.Name = &name
279         xapp.Version = h.GetVersion(name)
280         xapp.Status = h.GetState(out)
281
282         h.FillInstanceData(name, out, &xapp, h.cm.GetRtmData(name))
283         return
284 }
285
286 func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
287         xapps = models.AllDeployedXapps{}
288         for _, name := range names {
289                 var desc interface{}
290                 err := h.cm.ReadSchema(name, &desc)
291                 if err != nil {
292                         continue
293                 }
294
295                 x, err := h.Status(name)
296                 if err == nil {
297                         xapps = append(xapps, &x)
298                 }
299         }
300         return
301 }
302
303 func (h *Helm) AddTillerEnv() (err error) {
304         service := viper.GetString("helm.tiller-service")
305         namespace := viper.GetString("helm.tiller-namespace")
306         port := viper.GetString("helm.tiller-port")
307
308         if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
309                 appmgr.Logger.Info("Tiller Env Setting Failed: %v", err.Error())
310         }
311         return err
312 }
313
314 func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
315         args = fmt.Sprintf("%s --namespace=%s", args, x.Namespace)
316         if x.HelmVersion != "" {
317                 args = fmt.Sprintf("%s --version=%s", args, x.HelmVersion)
318         }
319
320         if x.ReleaseName != "" {
321                 args = fmt.Sprintf("%s --name=%s", args, x.ReleaseName)
322         } else {
323                 args = fmt.Sprintf("%s --name=%s", args, *x.XappName)
324         }
325
326         if cmOverride == true {
327                 args = fmt.Sprintf("%s ---set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
328         }
329
330         if x.OverrideFile != nil {
331                 if overrideYaml, err := yaml.JSONToYAML([]byte(x.OverrideFile.(string))); err == nil {
332                         err = ioutil.WriteFile("/tmp/appmgr_override.yaml", overrideYaml, 0644)
333                         if err != nil {
334                                 appmgr.Logger.Info("ioutil.WriteFile(/tmp/appmgr_override.yaml) failed: %v", err)
335                         } else {
336                                 args = args + " -f=/tmp/appmgr_override.yaml"
337                         }
338                 } else {
339                         appmgr.Logger.Info("yaml.JSONToYAML failed: %v", err)
340                 }
341         }
342
343         repoName := viper.GetString("helm.repo-name")
344         if repoName == "" {
345                 repoName = "helm-repo"
346         }
347         return fmt.Sprintf("install %s/%s %s", repoName, *x.XappName, args)
348 }