-
Notifications
You must be signed in to change notification settings - Fork 9
/
persistence.go
202 lines (172 loc) · 4.64 KB
/
persistence.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
package config
import (
"encoding/json"
"io/ioutil"
"path"
"strings"
"github.com/safing/portbase/log"
)
var (
configFilePath string
)
func loadConfig() error {
// check if persistence is configured
if configFilePath == "" {
return nil
}
// read config file
data, err := ioutil.ReadFile(configFilePath)
if err != nil {
return err
}
// convert to map
newValues, err := JSONToMap(data)
if err != nil {
return err
}
// apply
return setConfig(newValues)
}
func saveConfig() error {
// check if persistence is configured
if configFilePath == "" {
return nil
}
// extract values
activeValues := make(map[string]interface{})
optionsLock.RLock()
for key, option := range options {
option.Lock()
if option.activeValue != nil {
activeValues[key] = option.activeValue.getData(option)
}
option.Unlock()
}
optionsLock.RUnlock()
// convert to JSON
data, err := MapToJSON(activeValues)
if err != nil {
log.Errorf("config: failed to save config: %s", err)
return err
}
// write file
return ioutil.WriteFile(configFilePath, data, 0600)
}
// JSONToMap parses and flattens a hierarchical json object.
func JSONToMap(jsonData []byte) (map[string]interface{}, error) {
loaded := make(map[string]interface{})
err := json.Unmarshal(jsonData, &loaded)
if err != nil {
return nil, err
}
return Flatten(loaded), nil
}
// Flatten returns a flattened copy of the given hierarchical config.
func Flatten(config map[string]interface{}) (flattenedConfig map[string]interface{}) {
flattenedConfig = make(map[string]interface{})
flattenMap(flattenedConfig, config, "")
return flattenedConfig
}
func flattenMap(rootMap, subMap map[string]interface{}, subKey string) {
for key, entry := range subMap {
// get next level key
subbedKey := path.Join(subKey, key)
// check for next subMap
nextSub, ok := entry.(map[string]interface{})
if ok {
flattenMap(rootMap, nextSub, subbedKey)
} else {
// only set if not on root level
rootMap[subbedKey] = entry
}
}
}
// MapToJSON expands a flattened map and returns it as json.
func MapToJSON(config map[string]interface{}) ([]byte, error) {
return json.MarshalIndent(Expand(config), "", " ")
}
// Expand returns a hierarchical copy of the given flattened config.
func Expand(flattenedConfig map[string]interface{}) (config map[string]interface{}) {
config = make(map[string]interface{})
for key, entry := range flattenedConfig {
PutValueIntoHierarchicalConfig(config, key, entry)
}
return config
}
// PutValueIntoHierarchicalConfig injects a configuration entry into an hierarchical config map. Conflicting entries will be replaced.
func PutValueIntoHierarchicalConfig(config map[string]interface{}, key string, value interface{}) {
parts := strings.Split(key, "/")
// create/check maps for all parts except the last one
subMap := config
for i, part := range parts {
if i == len(parts)-1 {
// do not process the last part,
// which is not a map, but the value key itself
break
}
var nextSubMap map[string]interface{}
// get value
value, ok := subMap[part]
if !ok {
// create new map and assign it
nextSubMap = make(map[string]interface{})
subMap[part] = nextSubMap
} else {
nextSubMap, ok = value.(map[string]interface{})
if !ok {
// create new map and assign it
nextSubMap = make(map[string]interface{})
subMap[part] = nextSubMap
}
}
// assign for next parts loop
subMap = nextSubMap
}
// assign value to last submap
subMap[parts[len(parts)-1]] = value
}
// CleanFlattenedConfig removes all inexistent configuration options from the given flattened config map.
func CleanFlattenedConfig(flattenedConfig map[string]interface{}) {
optionsLock.RLock()
defer optionsLock.RUnlock()
for key := range flattenedConfig {
_, ok := options[key]
if !ok {
delete(flattenedConfig, key)
}
}
}
// CleanHierarchicalConfig removes all inexistent configuration options from the given hierarchical config map.
func CleanHierarchicalConfig(config map[string]interface{}) {
optionsLock.RLock()
defer optionsLock.RUnlock()
cleanSubMap(config, "")
}
func cleanSubMap(subMap map[string]interface{}, subKey string) (empty bool) {
var foundValid int
for key, value := range subMap {
value, ok := value.(map[string]interface{})
if ok {
// we found another section
isEmpty := cleanSubMap(value, path.Join(subKey, key))
if isEmpty {
delete(subMap, key)
} else {
foundValid++
}
continue
}
// we found an option value
if strings.Contains(key, "/") {
delete(subMap, key)
} else {
_, ok := options[path.Join(subKey, key)]
if ok {
foundValid++
} else {
delete(subMap, key)
}
}
}
return foundValid == 0
}