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