Enhance config handling
[ric-plt/appmgr.git] / pkg / cm / cm.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 cm
21
22 import (
23         "encoding/json"
24         "errors"
25         "fmt"
26         "io/ioutil"
27         "os"
28         "path"
29         "regexp"
30         "strings"
31         "strconv"
32         "github.com/spf13/viper"
33         "github.com/valyala/fastjson"
34         "github.com/xeipuuv/gojsonschema"
35
36         "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
37         "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
38         "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
39 )
40
41 type CM struct{}
42
43 func NewCM() *CM {
44         return &CM{}
45 }
46
47 func (cm *CM) UploadConfigAll() (configList models.AllXappConfig) {
48         return cm.UploadConfigElement("")
49 }
50
51 func (cm *CM) UploadConfigElement(Element string) (configList models.AllXappConfig) {
52         namespace := cm.GetNamespace("")
53         for _, name := range cm.GetNamesFromHelmRepo() {
54                 var activeConfig interface{}
55                 xAppName := name
56                 if err := cm.GetConfigmap(xAppName, namespace, &activeConfig); err != nil {
57                         appmgr.Logger.Info("No active configMap found for '%s', ignoring ...", xAppName)
58                         continue
59                 }
60
61                 if Element != "" {
62                         m := activeConfig.(map[string]interface{})
63                         if m[Element] == nil {
64                                 appmgr.Logger.Info("xApp '%s' doesn't have requested element '%s' in config", name, Element)
65                                 continue
66                         }
67                         activeConfig = m[Element]
68                 }
69
70                 c := models.XAppConfig{
71                         Metadata: &models.ConfigMetadata{XappName: &xAppName, Namespace: &namespace},
72                         Config: activeConfig,
73                 }
74                 configList = append(configList, &c)
75         }
76         return
77 }
78
79 func (cm *CM) GetConfigmap(name, namespace string, c *interface{}) (err error) {
80         cmJson, err := cm.ReadConfigmap(name, namespace)
81         if err != nil {
82                 return err
83         }
84
85         return json.Unmarshal([]byte(cmJson), &c)
86 }
87
88 func (cm *CM) ReadSchema(name string, desc *interface{}) (err error) {
89         if err = cm.FetchChart(name); err != nil {
90                 return
91         }
92
93         tarDir := viper.GetString("xapp.tarDir")
94         err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), desc)
95         if err != nil {
96                 return
97         }
98
99         if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
100                 appmgr.Logger.Info("RemoveAll failed: %v", err)
101         }
102
103         return
104 }
105
106 func (cm *CM) UpdateConfigMap(r models.XAppConfig) (models.ConfigValidationErrors, error) {
107         fmt.Printf("Configmap update: xappName=%s namespace=%s config: %v", *r.Metadata.XappName, *r.Metadata.Namespace, r.Config)
108         if validationErrors, err := cm.Validate(r); err != nil {
109                 return validationErrors, err
110         }
111
112         cmContent, err := cm.BuildConfigMap(r)
113         if err != nil {
114                 return nil, err
115         }
116
117         if err := cm.GenerateJSONFile(cmContent); err != nil {
118                 return nil, err
119         }
120         err = cm.ReplaceConfigMap(*r.Metadata.XappName, *r.Metadata.Namespace)
121
122         return nil, err
123 }
124
125 func (cm *CM) BuildConfigMap(r models.XAppConfig) (string, error) {
126         configJson, err := json.Marshal(r.Config)
127         if err != nil {
128                 appmgr.Logger.Info("Config marshalling failed: %v", err)
129                 return "", err
130         }
131
132         cmContent, err := cm.ReadConfigmap(*r.Metadata.XappName, *r.Metadata.Namespace)
133         if err != nil {
134                 return "", err
135         }
136
137         v, err := cm.ParseJson(cmContent)
138         if err == nil {
139                 v.Set("controls", fastjson.MustParse(string(configJson)))
140                 fmt.Println(v.String())
141                 return v.String(), nil
142         }
143
144         return "", err
145 }
146
147 func (cm *CM) ParseJson(dsContent string) (*fastjson.Value, error) {
148         var p fastjson.Parser
149         v, err := p.Parse(dsContent)
150         if err != nil {
151                 appmgr.Logger.Info("fastjson.Parser failed: %v", err)
152         }
153         return v, err
154 }
155
156
157 func (cm *CM) GenerateJSONFile(jsonString string) error {
158         cmJson, err := json.RawMessage(jsonString).MarshalJSON()
159         if err != nil {
160                 appmgr.Logger.Error("Config marshalling failed: %v", err)
161                 return err
162         }
163
164         err = ioutil.WriteFile(viper.GetString("xapp.tmpConfig"), cmJson, 0644)
165         if err != nil {
166                 appmgr.Logger.Error("WriteFile failed: %v", err)
167                 return err
168         }
169
170         return nil
171 }
172
173 func (cm *CM) ReadFile(name string, data interface{}) (err error) {
174         f, err := ioutil.ReadFile(name)
175         if err != nil {
176                 appmgr.Logger.Info("Reading '%s' file failed: %v", name, err)
177                 return
178         }
179
180         err = json.Unmarshal(f, &data)
181         if err != nil {
182                 appmgr.Logger.Info("Unmarshalling '%s' file failed: %v", name, err)
183                 return
184         }
185
186         return
187 }
188
189 func (cm *CM) ReadConfigmap(name string, ns string) (string, error) {
190         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
191         out, err := util.KubectlExec(args)
192         return string(out), err
193 }
194
195 func (cm *CM) ReplaceConfigMap(name, ns string) (error) {
196         cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl replace -f -"
197         args := fmt.Sprintf(cmd, ns, cm.GetConfigMapName(name, ns), viper.GetString("xapp.tmpConfig"))
198         _, err := util.KubectlExec(args)
199         return err
200 }
201
202 func (cm *CM) FetchChart(name string) (err error) {
203         tarDir := viper.GetString("xapp.tarDir")
204         repo := viper.GetString("helm.repo-name")
205         fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
206
207         _, err = util.HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
208         return
209 }
210
211 func (cm *CM) GetRtmData(name string) (msgs appmgr.RtmData) {
212         appmgr.Logger.Info("Fetching RT data for xApp=%s", name)
213
214         ns := cm.GetNamespace("")
215         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
216         out, err := util.KubectlExec(args)
217         if err != nil {
218                 return
219         }
220
221         var p fastjson.Parser
222         v, err := p.Parse(string(out))
223         if err != nil {
224                 appmgr.Logger.Info("fastjson.Parser for '%s' failed: %v", name, err)
225                 return
226         }
227
228         for _, m := range v.GetArray("rmr", "txMessages") {
229                 msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
230         }
231
232         for _, m := range v.GetArray("rmr", "rxMessages") {
233                 msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
234         }
235
236         for _, m := range v.GetArray("rmr", "policies") {
237                 if val, err := strconv.Atoi(strings.Trim(m.String(), `"`)); err == nil {
238                         msgs.Policies = append(msgs.Policies, int64(val))
239                 }
240         }
241
242         return
243 }
244
245 func (cm *CM) GetConfigMapName(xappName, namespace string) string {
246         return " configmap-" + namespace + "-" + xappName + "-appconfig"
247 }
248
249 func (cm *CM) GetNamespace(ns string) string {
250         if ns != "" {
251                 return ns
252         }
253
254         ns = viper.GetString("xapp.namespace")
255         if ns == "" {
256                 ns = "ricxapp"
257         }
258         return ns
259 }
260
261 func (cm *CM) GetNamesFromHelmRepo() (names []string) {
262         rname := viper.GetString("helm.repo-name")
263
264         cmdArgs := strings.Join([]string{"search ", rname}, "")
265         out, err := util.HelmExec(cmdArgs)
266         if err != nil {
267                 return
268         }
269
270         re := regexp.MustCompile(rname + `/.*`)
271         result := re.FindAllStringSubmatch(string(out), -1)
272         if result != nil {
273                 var tmp string
274                 for _, v := range result {
275                         fmt.Sscanf(v[0], "%s", &tmp)
276                         names = append(names, strings.Split(tmp, "/")[1])
277                 }
278         }
279         return names
280 }
281
282 func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
283         var desc interface{}
284         err = cm.ReadSchema(*req.Metadata.XappName, &desc)
285         if err != nil {
286                 appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.XappName)
287                 return
288         }
289         return cm.doValidate(desc, req.Config)
290 }
291
292 func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
293         schemaLoader := gojsonschema.NewGoLoader(schema)
294         documentLoader := gojsonschema.NewGoLoader(cfg)
295
296         result, err := gojsonschema.Validate(schemaLoader, documentLoader)
297         if err != nil {
298                 appmgr.Logger.Info("Validation failed: %v", err)
299                 return
300         }
301
302         if result.Valid() == false {
303                 appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
304                 for _, desc := range result.Errors() {
305                         field := desc.Field()
306                         validationError := desc.Description()
307                         errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
308                 }
309                 return errList, errors.New("Validation failed!")
310         }
311         appmgr.Logger.Info("Config validation successful!")
312
313         return
314 }