cfa1f18dd55d3134fb230e4ab8d6c22bd896175b
[ric-plt/appmgr.git] / src / 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 main
21
22 import (
23     "fmt"
24     "log"
25     "os"
26     "os/exec"
27     "regexp"
28     "strconv"
29     "strings"
30     "github.com/spf13/viper"
31     "gopkg.in/yaml.v2"
32     "io/ioutil"
33     "path"
34 )
35
36 var execCommand = exec.Command
37
38 func Exec(args string) (out []byte, err error) {
39     cmd := execCommand("/bin/sh", "-c", strings.Join([]string{"helm", args}, " "))
40
41     // In testing environment, don't print command traces ...
42     if !strings.HasSuffix(os.Args[0], ".test") {
43         log.Printf("Running command: %v", cmd)
44     }
45
46     out, err = cmd.CombinedOutput()
47     if err != nil {
48         mdclog(Mdclog_err, formatLog("Command failed", args, err.Error()))
49         return out, err
50     }
51
52     if !strings.HasSuffix(os.Args[0], ".test") {
53         mdclog(Mdclog_debug, formatLog("command success", string(out), ""))
54     }
55
56     return out, err
57 }
58
59 func (h *Helm) Run(args string) (out []byte, err error) {
60     if h.initDone == false {
61         if _, err := h.Init(); err != nil {
62             mdclog(Mdclog_err, formatLog("helm init failed", args, err.Error()))
63             return out, err
64         }
65         mdclog(Mdclog_debug, formatLog("Helm init done successfully!", args, ""))
66
67         // Add helm repo
68         if _, err := h.AddRepo(); err != nil {
69             mdclog(Mdclog_err, formatLog("Helm repo addition failed", args, err.Error()))
70             return out, err
71         }
72
73         mdclog(Mdclog_debug, formatLog("Helm repo added successfully", string(out), ""))
74         h.initDone = true
75     }
76
77     return Exec(args)
78 }
79
80 // API functions
81 func (h *Helm) Init() (out []byte, err error) {
82
83     // Add Tiller address as environment variable
84     if err := addTillerEnv(); err != nil {
85         return out, err
86     }
87
88     return Exec(strings.Join([]string{"init -c"}, ""))
89 }
90
91 func (h *Helm) AddRepo() (out []byte, err error) {
92
93     // Get helm repo user name and password from files mounted by secret object
94     credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
95     if err != nil {
96         mdclog(Mdclog_err, formatLog("helm_repo_username ReadFile failed", "", err.Error()))
97         return
98     }
99
100     username := " --username " + string(credFile)
101
102     credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
103     if err != nil {
104         mdclog(Mdclog_err, formatLog("helm_repo_password ReadFile failed", "", err.Error()))
105         return
106     }
107
108     pwd := " --password " + string(credFile)
109
110     // Get internal helm repo name
111     rname := viper.GetString("helm.repo-name")
112
113     // Get helm repo address from values.yaml
114     repo := viper.GetString("helm.repo")
115
116     return Exec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
117 }
118
119 func (h *Helm) Install(name string) (xapp Xapp, err error) {
120     out, err := h.Run(strings.Join([]string{"repo update "}, ""))
121     if err != nil {
122         return
123     }
124
125     rname := viper.GetString("helm.repo-name")
126
127     ns := getNamespaceArgs()
128     out, err = h.Run(strings.Join([]string{"install ", rname, "/", name, " --name ", name, ns}, ""))
129     if err != nil {
130         return
131     }
132
133     return h.ParseStatus(name, string(out))
134 }
135
136 func (h *Helm) Status(name string) (xapp Xapp, err error) {
137
138     out, err := h.Run(strings.Join([]string{"status ", name}, ""))
139     if err != nil {
140         mdclog(Mdclog_err, formatLog("Getting xapps status", "", err.Error()))
141         return
142     }
143
144     return h.ParseStatus(name, string(out))
145 }
146
147 func (h *Helm) StatusAll() (xapps []Xapp, err error) {
148     xappNameList, err := h.List()
149     if err != nil {
150         mdclog(Mdclog_err, formatLog("Helm list failed", "", err.Error()))
151         return
152     }
153
154     return h.parseAllStatus(xappNameList)
155 }
156
157 func (h *Helm) List() (names []string, err error) {
158
159     ns := getNamespaceArgs()
160     out, err := h.Run(strings.Join([]string{"list --all --output yaml ", ns}, ""))
161     if err != nil {
162         mdclog(Mdclog_err, formatLog("Listing deployed xapps failed", "", err.Error()))
163         return
164     }
165
166     return h.GetNames(string(out))
167 }
168
169 func (h *Helm) Delete(name string) (xapp Xapp, err error) {
170     xapp, err = h.Status(name)
171     if err != nil {
172         mdclog(Mdclog_err, formatLog("Fetching xapp status failed", "", err.Error()))
173         return
174     }
175
176     _, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
177     return xapp, err
178 }
179
180 func (h *Helm) Fetch(name , tarDir string) (error) {
181     if strings.HasSuffix(os.Args[0], ".test") {
182         return nil
183     }
184
185     rname := viper.GetString("helm.repo-name") + "/"
186
187     _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
188     return err
189 }
190
191 // Helper functions
192 func (h *Helm) GetMessages(name string) (msgs MessageTypes, err error) {
193     tarDir := viper.GetString("xapp.tarDir")
194     if tarDir == "" {
195         tarDir = "/tmp"
196     }
197
198     if h.Fetch(name, tarDir); err != nil {
199         mdclog(Mdclog_err, formatLog("Fetch chart failed", "", err.Error()))
200         return
201     }
202
203     return h.ParseMessages(name, tarDir, "msg_type.yaml")
204
205 }
206
207 func (h *Helm) ParseMessages(name string, chartDir, msgFile string) (msgs MessageTypes, err error) {
208     yamlFile, err := ioutil.ReadFile(path.Join(chartDir, name, msgFile))
209     if err != nil {
210         mdclog(Mdclog_err, formatLog("ReadFile failed", "", err.Error()))
211         return
212     }
213
214     err = yaml.Unmarshal(yamlFile, &msgs)
215     if err != nil {
216         mdclog(Mdclog_err, formatLog("Unmarshal failed", "", err.Error()))
217         return
218     }
219
220     if err = os.RemoveAll(path.Join(chartDir, name)); err != nil {
221         mdclog(Mdclog_err, formatLog("RemoveAll failed", "", err.Error()))
222     }
223
224     return
225 }
226
227 func (h *Helm) GetVersion(name string) (version string) {
228
229     ns := getNamespaceArgs()
230     out, err := h.Run(strings.Join([]string{"list --output yaml ", name, ns}, ""))
231     if err != nil {
232         return
233     }
234
235     var re = regexp.MustCompile(`AppVersion: .*`)
236     ver := re.FindStringSubmatch(string(out))
237     if ver != nil {
238         version = strings.Split(ver[0], ": ")[1]
239         version, _ = strconv.Unquote(version)
240     }
241
242     return
243 }
244
245 func (h *Helm) GetState(out string) (status string) {
246     re := regexp.MustCompile(`STATUS: .*`)
247     result := re.FindStringSubmatch(string(out))
248     if result != nil {
249         status = strings.ToLower(strings.Split(result[0], ": ")[1])
250     }
251
252     return
253 }
254
255 func (h *Helm) GetAddress(out string) (ip, port string) {
256     var tmp string
257     re := regexp.MustCompile(`ClusterIP.*`)
258     addr := re.FindStringSubmatch(string(out))
259     if addr != nil {
260         fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
261     }
262
263     return
264 }
265
266 func (h *Helm) GetNames(out string) (names []string, err error) {
267     re := regexp.MustCompile(`Name: .*`)
268     result := re.FindAllStringSubmatch(out, -1)
269     if result == nil {
270         return
271     }
272
273     for _, v := range result {
274         xappName := strings.Split(v[0], ": ")[1]
275         if strings.Contains(xappName, "appmgr") == false {
276             names = append(names, xappName)
277         }
278     }
279     return names, nil
280 }
281
282 func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
283     ip, port := h.GetAddress(out)
284
285     var tmp string
286     r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`)
287     result := r.FindStringSubmatch(string(out))
288     if result == nil {
289         return
290     }
291
292     re := regexp.MustCompile(name + "-(\\d+).*")
293     resources := re.FindAllStringSubmatch(string(result[0]), -1)
294     if resources != nil {
295         for _, v := range resources {
296             var x XappInstance
297             fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
298             x.Status = strings.ToLower(x.Status)
299             x.Ip = ip
300             x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0])
301             x.TxMessages = msgs.TxMessages
302             x.RxMessages = msgs.RxMessages
303             xapp.Instances = append(xapp.Instances, x)
304         }
305     }
306 }
307
308 func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
309     types, err := h.GetMessages(name)
310     if (err != nil) {
311         return
312     }
313
314     xapp.Name = name
315     xapp.Version = h.GetVersion(name)
316     xapp.Status = h.GetState(out)
317     h.FillInstanceData(name, out, &xapp, types)
318
319     return
320 }
321
322 func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
323     xapps = []Xapp{}
324
325     for _, name := range names {
326         x, err := h.Status(name)
327         if err == nil {
328             xapps = append(xapps, x)
329         }
330     }
331
332     return
333 }
334
335 func addTillerEnv() (err error) {
336
337     service := viper.GetString("helm.tiller-service")
338     namespace := viper.GetString("helm.tiller-namespace")
339     port := viper.GetString("helm.tiller-port")
340
341     if err = os.Setenv("HELM_HOST", service + "." + namespace + ":" + port); err != nil {
342         mdclog(Mdclog_err, formatLog("Tiller Env Setting Failed", "", err.Error()))
343     }
344
345     return err
346 }
347
348 func getNamespaceArgs() (string) {
349     ns := viper.GetString("xapp.namespace")
350     if ns == "" {
351         ns = "ricxapp"
352     }
353     return " --namespace=" + ns
354 }
355
356 func formatLog(text string, args string, err string) (string) {
357     return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
358 }
359