ac1d0c832c65d7af976d93072b1181735285cc52
[ric-plt/appmgr.git] / cmd / appmgr / desc.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         "encoding/json"
24         "errors"
25         "fmt"
26         "github.com/spf13/viper"
27         "github.com/valyala/fastjson"
28         "github.com/xeipuuv/gojsonschema"
29         "io/ioutil"
30         "log"
31         "os"
32         "path"
33         "regexp"
34         "strings"
35         "time"
36 )
37
38 type ConfigMetadata struct {
39         Name       string `json:"name"`
40         ConfigName string `json:"configName, omitempty"`
41         Namespace  string `json:"namespace, omitempty"`
42 }
43
44 type XAppConfig struct {
45         Metadata      ConfigMetadata `json:"metadata"`
46         Descriptor    interface{}    `json:"descriptor, omitempty"`
47         Configuration interface{}    `json:"config, omitempty"`
48 }
49
50 type ConfigMap struct {
51         Kind       string      `json:"kind"`
52         ApiVersion string      `json:"apiVersion"`
53         Data       interface{} `json:"data"`
54         Metadata   CMMetadata  `json:"metadata"`
55 }
56
57 type CMMetadata struct {
58         Name      string `json:"name"`
59         Namespace string `json:"namespace"`
60 }
61
62 type CMError struct {
63         Field       string `json:"field"`
64         Description string `json:"description"`
65 }
66
67 func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) {
68         for _, name := range cm.GetNamesFromHelmRepo() {
69                 if name == "appmgr" {
70                         continue
71                 }
72
73                 c := XAppConfig{
74                         Metadata: ConfigMetadata{Name: name, Namespace: "ricxapp", ConfigName: name + "-appconfig"},
75                 }
76
77                 err := cm.ReadSchema(name, &c)
78                 if err != nil {
79                         continue
80                 }
81
82                 err = cm.ReadConfigMap(c.Metadata.ConfigName, "ricxapp", &c.Configuration)
83                 if err != nil {
84                         log.Println("No active configMap found, using default!")
85                 }
86
87                 cfg = append(cfg, c)
88         }
89         return
90 }
91
92 func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) {
93         if err = cm.FetchChart(name); err != nil {
94                 return
95         }
96
97         tarDir := viper.GetString("xapp.tarDir")
98         err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
99         if err != nil {
100                 return
101         }
102
103         err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration)
104         if err != nil {
105                 return
106         }
107
108         if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
109                 log.Println("RemoveAll failed", err)
110         }
111
112         return
113 }
114
115 func (cm *ConfigMap) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
116         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
117         configMapJson, err := KubectlExec(args)
118         if err != nil {
119                 return
120         }
121
122         err = json.Unmarshal([]byte(configMapJson), &c)
123         if err != nil {
124                 return
125         }
126
127         return
128 }
129
130 func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) {
131         c := ConfigMap{
132                 Kind:       "ConfigMap",
133                 ApiVersion: "v1",
134                 Metadata:   CMMetadata{Name: r.Metadata.Name, Namespace: r.Metadata.Namespace},
135                 Data:       r.Configuration,
136         }
137
138         cmJson, err := json.Marshal(c.Data)
139         if err != nil {
140                 log.Println("Config marshalling failed: ", err)
141                 return
142         }
143
144         cmFile := viper.GetString("xapp.tmpConfig")
145         err = ioutil.WriteFile(cmFile, cmJson, 0644)
146         if err != nil {
147                 log.Println("WriteFile failed: ", err)
148                 return
149         }
150
151         cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
152         args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
153         _, err = KubectlExec(args)
154         if err != nil {
155                 return
156         }
157         log.Println("Configmap changes done!")
158
159         return
160 }
161
162 func (cm *ConfigMap) GetConfigMap(m XappDeploy, c *interface{}) (err error) {
163         if m.ConfigName == "" {
164                 m.ConfigName = m.Name + "-appconfig"
165         }
166         return cm.ReadConfigMap(m.ConfigName, m.Namespace, c)
167 }
168
169 func (cm *ConfigMap) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
170         if errList, err = cm.Validate(r); err != nil {
171                 return
172         }
173         err = cm.ApplyConfigMap(r, "create")
174         return
175 }
176
177 func (cm *ConfigMap) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
178         if errList, err = cm.Validate(r); err != nil {
179                 return
180         }
181
182         // Re-create the configmap with the new parameters
183         err = cm.ApplyConfigMap(r, "apply")
184         return
185 }
186
187 func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
188         err = cm.ReadConfigMap(r.Metadata.ConfigName, r.Metadata.Namespace, &c)
189         if err == nil {
190                 args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Metadata.Namespace, r.Metadata.ConfigName)
191                 _, err = KubectlExec(args)
192         }
193         return
194 }
195
196 func (cm *ConfigMap) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
197         if m.ConfigName == "" {
198                 m.ConfigName = m.Name + "-appconfig"
199         }
200         md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
201
202         return cm.DeleteConfigMap(XAppConfig{Metadata: md})
203 }
204
205 func (cm *ConfigMap) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
206         if m.ConfigName == "" {
207                 m.ConfigName = m.Name + "-appconfig"
208         }
209         md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
210         time.Sleep(time.Duration(10 * time.Second))
211
212         return cm.ApplyConfigMap(XAppConfig{Metadata: md, Configuration: c}, "create")
213 }
214
215 func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) {
216         rname := viper.GetString("helm.repo-name")
217
218         cmdArgs := strings.Join([]string{"search ", rname}, "")
219         out, err := HelmExec(cmdArgs)
220         if err != nil {
221                 return
222         }
223
224         re := regexp.MustCompile(rname + `/.*`)
225         result := re.FindAllStringSubmatch(string(out), -1)
226         if result != nil {
227                 var tmp string
228                 for _, v := range result {
229                         fmt.Sscanf(v[0], "%s", &tmp)
230                         names = append(names, strings.Split(tmp, "/")[1])
231                 }
232         }
233         return names
234 }
235
236 func (cm *ConfigMap) Validate(req XAppConfig) (errList []CMError, err error) {
237         c := XAppConfig{}
238         err = cm.ReadSchema(req.Metadata.Name, &c)
239         if err != nil {
240                 log.Printf("No schema file found for '%s', aborting ...", req.Metadata.Name)
241                 return
242         }
243         return cm.doValidate(c.Descriptor, req.Configuration)
244 }
245
246 func (cm *ConfigMap) doValidate(schema, cfg interface{}) (errList []CMError, err error) {
247         schemaLoader := gojsonschema.NewGoLoader(schema)
248         documentLoader := gojsonschema.NewGoLoader(cfg)
249
250         result, err := gojsonschema.Validate(schemaLoader, documentLoader)
251         if err != nil {
252                 log.Println("Validation failed: ", err)
253                 return
254         }
255
256         if result.Valid() == false {
257                 log.Println("The document is not valid, Errors: ", result.Errors())
258                 for _, desc := range result.Errors() {
259                         errList = append(errList, CMError{Field: desc.Field(), Description: desc.Description()})
260                 }
261                 return errList, errors.New("Validation failed!")
262         }
263         return
264 }
265
266 func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) {
267         f, err := ioutil.ReadFile(name)
268         if err != nil {
269                 log.Printf("Reading '%s' file failed: %v", name, err)
270                 return
271         }
272
273         err = json.Unmarshal(f, &data)
274         if err != nil {
275                 log.Printf("Unmarshalling '%s' file failed: %v", name, err)
276                 return
277         }
278
279         return
280 }
281
282 func (cm *ConfigMap) FetchChart(name string) (err error) {
283         tarDir := viper.GetString("xapp.tarDir")
284         repo := viper.GetString("helm.repo-name")
285         fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
286
287         _, err = HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
288         return
289 }
290
291 func (cm *ConfigMap) GetMessages(name string) (msgs MessageTypes) {
292         log.Println("Fetching tx/rx messages for: ", name)
293
294         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n ricxapp %s-appconfig", name)
295         out, err := KubectlExec(args)
296         if err != nil {
297                 return
298         }
299
300         var p fastjson.Parser
301         v, err := p.Parse(string(out))
302         if err != nil {
303                 log.Printf("fastjson.Parser for '%s' failed: %v", name, err)
304                 return
305         }
306
307         for _, m := range v.GetArray("rmr", "txMessages") {
308                 msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
309         }
310         for _, m := range v.GetArray("rmr", "rxMessages") {
311                 msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
312         }
313
314         return
315 }