/
import.go
232 lines (213 loc) · 7.48 KB
/
import.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// +build linux darwin windows
// +build amd64 386
// Copyright © 2018 Antoine GIRARD <antoine.girard@sapk.fr>
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"reflect"
"strings"
"github.com/rs/zerolog/log"
"github.com/sapk/genesys-tools/tool/check"
"github.com/sapk/genesys-tools/tool/format"
"github.com/sapk/genesys-tools/tool/loader"
"github.com/sapk/go-genesys/api/client"
"github.com/spf13/cobra"
)
var (
importUsername string
importPassword string
importDefaultAnswers []string
importForceYes bool
)
//TODO add help message for what is not imported
var allowedImportTypes = map[string]bool{
//"CfgApplication": true,
"CfgPlace": true, //lost link to contactdbid capacityruledbid dndbids sitedbid
"CfgDN": true,
"CfgAppPrototype": true,
"CfgField": true,
"CfgScript": true,
"CfgAgentLogin": true,
"CfgPerson": true,
"CfgAgentGroup": true,
}
//TODO importe template and metadata first
//TODO afficher les connection et lien manquant , host, ...
func init() {
//These default login is not a secret since on public genesys website: https://docs.genesys.com/Documentation/FR/Current/Dep/StdLogin
importCmd.Flags().StringVarP(&importUsername, "user", "u", "default", "GAX user name")
importCmd.Flags().StringVarP(&importPassword, "pass", "p", "password", "GAX user password")
importCmd.Flags().BoolVarP(&importForceYes, "force", "f", false, "Implies yes to each questions")
importCmd.Flags().StringSliceVarP(&importDefaultAnswers, "default", "d", []string{}, "Default value to answer by object type. (Ex: -d 'CfgTenant=102,CfgTenant=224')")
RootCmd.AddCommand(importCmd)
}
var importCmd = &cobra.Command{
Use: "import",
Short: "[WIP] Connect to a GAX server to import object from dump",
Long: `[WIP] Use GAX APIs to load objects from dump of previous configuration.
Ex: genesys-tools import hostb:8080 Application/*.md`,
//TODO list allowedImportTypes
Args: func(cmd *cobra.Command, args []string) error {
log.Debug().Msgf("Checking args for import cmd: %s", args)
if len(args) < 2 {
return fmt.Errorf("requires at least one GAX server and one file to import")
}
if !check.IsValidClientArg(args[0]) {
return fmt.Errorf("invalid gax host argument specified (ex: gax_host:8080): %s", args[0])
}
for _, arg := range args[1:] {
if !check.IsValidFileArg(arg) {
return fmt.Errorf("invalid file argument specified: %s", arg)
}
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
gax := args[0]
if !strings.Contains(gax, ":") {
//By default use port 8080
gax += ":8080"
}
defaults := make(map[string]string)
for _, def := range importDefaultAnswers {
tmp := strings.Split(def, "=")
if len(tmp) != 2 {
log.Warn().Interface("default", def).Msg("Invalid default ignored")
continue
}
log.Debug().Interface("type", tmp[0]).Interface("value", tmp[1]).Msg("Registering default value")
defaults[tmp[0]] = tmp[1]
}
//Login
c := client.NewClient(gax, false)
user, err := c.Login(importUsername, importPassword)
if err != nil {
log.Panic().Msgf("Login failed : %v", err)
}
log.Debug().Interface("User", user).Msgf("Logged as: %s", user.Username)
for _, file := range args[1:] {
obj := getObj(file)
log.Info().Msgf("Parsing %s: %s", obj["type"], format.Name(obj))
log.Debug().Interface("Object", obj).Msg("Parsing object")
t, ok := obj["type"].(string)
if !ok {
log.Fatal().Msgf("Fail to find type of object %s : %v", file, obj)
}
if !allowedImportTypes[t] {
log.Warn().Msgf("Skipping file %s since type %s is not importable yet.", file, t)
continue
}
l := loader.ListObject(c, t)
log.Debug().Msgf("List response : %v", l)
if len(l) == 0 { //no same object so we create
log.Debug().Msgf("Found no object with type : %v", t)
createObj(c, obj, defaults)
} else {
//Try to find if a app is matching
list := loader.FilterBy(obj, l, loader.MatchIdName)
if len(list) == 0 {
log.Debug().Msgf("Found no object with same DBID and Name")
list = loader.FilterBy(obj, l, loader.MatchName)
if len(list) == 0 {
log.Debug().Msgf("Found no object with same Name")
/* Temporary disable as it doesn't match change in name for exemple (detected on place)
list = loader.FilterBy(obj, l, loader.MatchId)
if len(list) == 0 {
log.Debug().Msgf("Found no object with same DBID")
}
*/
}
}
//TODO less ugly
//TODO manage errors
var err error
switch len(list) {
case 0: //no same object so we create
err = createObj(c, obj, defaults)
case 1:
err = updateObj(c, list[0], obj, defaults)
default:
log.Warn().Msgf("Multiple object matching : %s", file)
for _, src := range list {
updateObj(c, src, obj, defaults)
}
}
if err != nil {
log.Error().Interface("object", obj).Msgf("Failed to import object: %v", err)
} else {
log.Info().Interface("object", obj).Msgf("Import object success !")
}
}
}
},
}
func updateObj(c *client.Client, src map[string]interface{}, obj map[string]interface{}, defaults map[string]string) error {
log.Info().Interface("Source", src).Interface("Object", obj).Msg("Update object")
eq := reflect.DeepEqual(obj, src)
if eq {
log.Info().Interface("Source", src).Interface("Object", obj).Msg("Skipping update of object because of equality")
return nil
}
if f, ok := loader.LoaderList[obj["type"].(string)]; ok {
obj = f.FormatUpdate(c, src, obj, defaults)
} else {
obj = loader.LoaderList["default"].FormatUpdate(c, src, obj, defaults)
}
//TODO check eq after cleaning
eq = reflect.DeepEqual(obj, src)
if eq {
log.Info().Interface("Source", src).Interface("Object", obj).Msg("Skipping update of object because of equality after loading format")
return nil
}
log.Info().Interface("Object", obj).Msg("Sending updated object")
//TODO ask for ovveride
//TODO get dbid for older one ?
//TODO check possible deps
//TODO check if no change
if importForceYes || check.AskFor(fmt.Sprintf("Update %s", format.FormatShortObj(obj))) { // ask for confirmation
_, err := c.UpdateObject(src["type"].(string), src["dbid"].(string), obj) //TODO check up
return err
}
return nil
}
func createObj(c *client.Client, obj map[string]interface{}, defaults map[string]string) error {
log.Debug().Interface("Object", obj).Msg("Create object init")
if f, ok := loader.LoaderList[obj["type"].(string)]; ok {
obj = f.FormatCreate(c, obj, defaults)
} else {
obj = loader.LoaderList["default"].FormatCreate(c, obj, defaults)
}
log.Info().Interface("Object", obj).Msg("Create object")
if importForceYes || check.AskFor(fmt.Sprintf("Create %s", format.FormatShortObj(obj))) { // ask for confirmation
_, err := c.PostObject(obj) //TODO check up
return err
}
return nil
}
func getObj(file string) map[string]interface{} {
b, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal().Msgf("Read file %s failed : %v", file, err)
}
fileStr := string(b)
pos := strings.LastIndex(fileStr, "[//]: # ({")
if pos == -1 {
log.Fatal().Msgf("Fail to found raw dump in file %s : %v", file, err)
}
jsonStr := fileStr[pos+9:]
//TODO regex
pos = strings.Index(jsonStr, "})\n")
if pos == -1 {
log.Fatal().Msgf("Fail to found raw dump in file %s : %v", file, err)
}
jsonStr = jsonStr[:pos+1]
log.Debug().Msgf("Parsing JSON : %s", jsonStr)
var data map[string]interface{}
err = json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
log.Fatal().Msgf("Fail failed to parse %s : %v", jsonStr, err)
}
return data
}