-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
242 lines (216 loc) · 5.82 KB
/
config.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
233
234
235
236
237
238
239
240
241
242
package models
import (
"encoding/json"
"fmt"
"io"
"strings"
conf "github.com/mistifyio/mistify-operator-admin/config"
"github.com/mistifyio/mistify-operator-admin/db"
)
type (
// Config persists and retrieves arbitrary namespaced key/value pairs with
// support for default values.
Config struct {
// Since set values overlay defaults, prevent direct access to the data
// and force use of appropriate getters/setters
data map[string]map[string]string
}
)
// Validate checks that the properties of Config are valid
func (config *Config) Validate() error {
if config.data == nil {
return ErrNilData
}
return nil
}
// Get retrieves the full config with set values taking precedence over defaults
func (config *Config) Get() map[string]map[string]string {
data := make(map[string]map[string]string)
defaultConfig := conf.Get().Mistify
for namespace := range defaultConfig {
data[namespace] = config.GetNamespace(namespace)
}
for namespace := range config.data {
if _, ok := data[namespace]; !ok {
data[namespace] = config.GetNamespace(namespace)
}
}
return data
}
// GetNamespace returns a map of config key/value pairs with set values merged
// on top of defaults. It is a new map, so modifications will not be stored
func (config *Config) GetNamespace(namespace string) map[string]string {
ns := make(map[string]string)
// Start with defaults
defaultNS, ok := conf.Get().Mistify[namespace]
if ok {
for i, v := range defaultNS {
ns[i] = v
}
}
// Overlay configured values
dataNS, ok := config.data[namespace]
if ok {
for i, v := range dataNS {
ns[i] = v
}
}
return ns
}
// SetNamespace places a set of key/value pairs under a given namespace
func (config *Config) SetNamespace(namespace string, value map[string]string) {
if value == nil {
config.DeleteNamespace(namespace)
} else {
config.data[namespace] = value
}
}
// DeleteNamespace deletes a set namespace. Defaults are not deleted.
func (config *Config) DeleteNamespace(namespace string) {
delete(config.data, namespace)
}
// GetValue retrieves the value of a namespaced key, with set values taking
// precidence over defaults
func (config *Config) GetValue(namespace string, key string) (string, bool) {
// Go compiler isn't smart enough to use the two-value in a direct return
value, ok := config.GetNamespace(namespace)[key]
return value, ok
}
// SetValue sets the value of a namespaced key, creating the namespace if it
// does not already exist.
func (config *Config) SetValue(namespace string, key string, value string) {
ns, ok := config.data[namespace]
if !ok {
config.data[namespace] = make(map[string]string)
ns = config.data[namespace]
}
ns[key] = value
}
// DeleteValue deletes a set namespaced key. Defaults are not deleted.
func (config *Config) DeleteValue(namespace string, key string) {
ns, ok := config.data[namespace]
if !ok {
return
}
delete(ns, key)
}
// Merge merges in set values from another Config into the current one
func (config *Config) Merge(updates *Config) {
for ns, data := range updates.data {
for key, value := range data {
config.SetValue(ns, key, value)
}
}
}
// Clean will remove any set values that match defaults and any empty set
// namespaces. Primarilly used before persisting to reduce redundant and
// unnecessary data.
func (config *Config) Clean() {
for name, ns := range config.data {
// Discard anything set the same as default values
for key, v := range ns {
if v == conf.Get().Mistify[name][key] {
delete(ns, key)
}
}
// Discard completely empty namespaces
if len(ns) == 0 {
delete(config.data, name)
}
}
}
// Load retrieves the persisted set data
func (config *Config) Load() error {
d, err := db.Connect(nil)
if err != nil {
return err
}
sql := `
SELECT namespace, data
FROM config
`
rows, err := d.Query(sql)
if err != nil {
return err
}
for rows.Next() {
var namespace, dataJSON string
if err := rows.Scan(&namespace, &dataJSON); err == nil {
var data map[string]string
if err := json.Unmarshal([]byte(dataJSON), &data); err != nil {
return err
}
config.data[namespace] = data
}
}
return rows.Err()
}
// Decode decodes JSON into a Config
func (config *Config) Decode(data io.Reader) error {
if err := json.NewDecoder(data).Decode(&config.data); err != nil {
return err
}
if config.data == nil {
config.data = make(map[string]map[string]string)
}
return nil
}
// Save persists the set data
func (config *Config) Save() error {
if err := config.Validate(); err != nil {
return err
}
config.Clean()
d, err := db.Connect(nil)
if err != nil {
return err
}
// Clearing and rebuilding the table
// If we need accurate auditing, switch from a txn to a writable CTE
// that handles updates/inserts/deletes more granularly
txn, err := d.Begin()
if err != nil {
return err
}
// Clear the table
if _, err := txn.Exec("TRUNCATE config"); err != nil {
_ = txn.Rollback()
return err
}
// Commit if we don't have anything to save
if len(config.data) == 0 {
return txn.Commit()
}
// Build the variable length values sql and vars array
placeholders := make([]string, len(config.data))
values := make([]interface{}, len(config.data)*2)
i := 0
for namespace, data := range config.data {
placeholders[i] = fmt.Sprintf("($%d, $%d::json)", (i*2)+1, (i*2)+2)
dataJSON, err := json.Marshal(data)
if err != nil {
_ = txn.Rollback()
return err
}
values[2*i] = interface{}(namespace)
values[(2*i)+1] = interface{}(string(dataJSON))
i++
}
sql := `
INSERT INTO config
(namespace, data)
VALUES
`
sql += strings.Join(placeholders, ",")
if _, err = txn.Exec(sql, values...); err != nil {
_ = txn.Rollback()
return err
}
return txn.Commit()
}
// NewConfig creates a new Config instance and initializes the internal data map
func NewConfig() *Config {
return &Config{
data: make(map[string]map[string]string),
}
}