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