github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/configstate/config/transaction.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package config 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "sort" 27 "strings" 28 "sync" 29 30 "github.com/snapcore/snapd/jsonutil" 31 "github.com/snapcore/snapd/overlord/state" 32 ) 33 34 // Transaction holds a copy of the configuration originally present in the 35 // provided state which can be queried and mutated in isolation from 36 // concurrent logic. All changes performed into it are persisted back into 37 // the state at once when Commit is called. 38 // 39 // Transactions are safe to access and modify concurrently. 40 type Transaction struct { 41 mu sync.Mutex 42 state *state.State 43 pristine map[string]map[string]*json.RawMessage // snap => key => value 44 changes map[string]map[string]interface{} 45 } 46 47 // NewTransaction creates a new configuration transaction initialized with the given state. 48 // 49 // The provided state must be locked by the caller. 50 func NewTransaction(st *state.State) *Transaction { 51 transaction := &Transaction{state: st} 52 transaction.changes = make(map[string]map[string]interface{}) 53 54 // Record the current state of the map containing the config of every snap 55 // in the system. We'll use it for this transaction. 56 err := st.Get("config", &transaction.pristine) 57 if err == state.ErrNoState { 58 transaction.pristine = make(map[string]map[string]*json.RawMessage) 59 } else if err != nil { 60 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 61 } 62 return transaction 63 } 64 65 // State returns the system State 66 func (t *Transaction) State() *state.State { 67 return t.state 68 } 69 70 func changes(cfgStr string, cfg map[string]interface{}) []string { 71 var out []string 72 for k := range cfg { 73 switch subCfg := cfg[k].(type) { 74 case map[string]interface{}: 75 out = append(out, changes(cfgStr+"."+k, subCfg)...) 76 case *json.RawMessage: 77 // check if we need to dive into a sub-config 78 var configm map[string]interface{} 79 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*subCfg), &configm); err == nil { 80 // curiously, json decoder decodes json.RawMessage("null") into a nil map, so no change is 81 // reported when we recurse into it. This happens when unsetting a key and the underlying 82 // config path doesn't exist. 83 if len(configm) > 0 { 84 out = append(out, changes(cfgStr+"."+k, configm)...) 85 continue 86 } 87 } 88 out = append(out, []string{cfgStr + "." + k}...) 89 default: 90 out = append(out, []string{cfgStr + "." + k}...) 91 } 92 } 93 return out 94 } 95 96 // Changes returns the changing keys associated with this transaction 97 func (t *Transaction) Changes() []string { 98 var out []string 99 for k := range t.changes { 100 out = append(out, changes(k, t.changes[k])...) 101 } 102 sort.Strings(out) 103 return out 104 } 105 106 // Set sets the provided snap's configuration key to the given value. 107 // The provided key may be formed as a dotted key path through nested maps. 108 // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map. 109 // When the key is provided in that form, intermediate maps are mutated 110 // rather than replaced, and created when necessary. 111 // 112 // The provided value must marshal properly by encoding/json. 113 // Changes are not persisted until Commit is called. 114 func (t *Transaction) Set(instanceName, key string, value interface{}) error { 115 t.mu.Lock() 116 defer t.mu.Unlock() 117 118 config, ok := t.changes[instanceName] 119 if !ok { 120 config = make(map[string]interface{}) 121 } 122 123 data, err := json.Marshal(value) 124 if err != nil { 125 return fmt.Errorf("cannot marshal snap %q option %q: %s", instanceName, key, err) 126 } 127 raw := json.RawMessage(data) 128 129 subkeys, err := ParseKey(key) 130 if err != nil { 131 return err 132 } 133 134 // Check whether it's trying to traverse a non-map from pristine. This 135 // would go unperceived by the configuration patching below. 136 if len(subkeys) > 1 { 137 var result interface{} 138 err = getFromConfig(instanceName, subkeys, 0, t.pristine[instanceName], &result) 139 if err != nil && !IsNoOption(err) { 140 return err 141 } 142 } 143 _, err = PatchConfig(instanceName, subkeys, 0, config, &raw) 144 if err != nil { 145 return err 146 } 147 148 t.changes[instanceName] = config 149 return nil 150 } 151 152 func (t *Transaction) copyPristine(snapName string) map[string]*json.RawMessage { 153 out := make(map[string]*json.RawMessage) 154 if config, ok := t.pristine[snapName]; ok { 155 for k, v := range config { 156 out[k] = v 157 } 158 } 159 return out 160 } 161 162 // Get unmarshals into result the cached value of the provided snap's configuration key. 163 // If the key does not exist, an error of type *NoOptionError is returned. 164 // The provided key may be formed as a dotted key path through nested maps. 165 // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map. 166 // 167 // Transactions do not see updates from the current state or from other transactions. 168 func (t *Transaction) Get(snapName, key string, result interface{}) error { 169 t.mu.Lock() 170 defer t.mu.Unlock() 171 172 subkeys, err := ParseKey(key) 173 if err != nil { 174 return err 175 } 176 177 // commit changes onto a copy of pristine configuration, so that get has a complete view of the config. 178 config := t.copyPristine(snapName) 179 applyChanges(config, t.changes[snapName]) 180 181 purgeNulls(config) 182 return getFromConfig(snapName, subkeys, 0, config, result) 183 } 184 185 // GetMaybe unmarshals into result the cached value of the provided snap's configuration key. 186 // If the key does not exist, no error is returned. 187 // 188 // Transactions do not see updates from the current state or from other transactions. 189 func (t *Transaction) GetMaybe(instanceName, key string, result interface{}) error { 190 err := t.Get(instanceName, key, result) 191 if err != nil && !IsNoOption(err) { 192 return err 193 } 194 return nil 195 } 196 197 // GetPristine unmarshals the cached pristine (before applying any 198 // changes) value of the provided snap's configuration key into 199 // result. 200 // 201 // If the key does not exist, an error of type *NoOptionError is returned. 202 func (t *Transaction) GetPristine(snapName, key string, result interface{}) error { 203 t.mu.Lock() 204 defer t.mu.Unlock() 205 206 subkeys, err := ParseKey(key) 207 if err != nil { 208 return err 209 } 210 211 return getFromConfig(snapName, subkeys, 0, t.pristine[snapName], result) 212 } 213 214 // GetPristineMaybe unmarshals the cached pristine (before applying any 215 // changes) value of the provided snap's configuration key into 216 // result. 217 // 218 // If the key does not exist, no error is returned. 219 func (t *Transaction) GetPristineMaybe(instanceName, key string, result interface{}) error { 220 err := t.GetPristine(instanceName, key, result) 221 if err != nil && !IsNoOption(err) { 222 return err 223 } 224 return nil 225 } 226 227 func getFromConfig(instanceName string, subkeys []string, pos int, config map[string]*json.RawMessage, result interface{}) error { 228 // special case - get root document 229 if len(subkeys) == 0 { 230 if len(config) == 0 { 231 return &NoOptionError{SnapName: instanceName} 232 } 233 raw := jsonRaw(config) 234 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { 235 return fmt.Errorf("internal error: cannot unmarshal snap %q root document: %s", instanceName, err) 236 } 237 return nil 238 } 239 240 raw, ok := config[subkeys[pos]] 241 if !ok { 242 return &NoOptionError{SnapName: instanceName, Key: strings.Join(subkeys[:pos+1], ".")} 243 } 244 245 // There is a known problem with json raw messages representing nulls when they are stored in nested structures, such as 246 // config map inside our state. These are turned into nils and need to be handled explicitly. 247 if raw == nil { 248 m := json.RawMessage("null") 249 raw = &m 250 } 251 252 if pos+1 == len(subkeys) { 253 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { 254 key := strings.Join(subkeys, ".") 255 return fmt.Errorf("internal error: cannot unmarshal snap %q option %q into %T: %s, json: %s", instanceName, key, result, err, *raw) 256 } 257 return nil 258 } 259 260 var configm map[string]*json.RawMessage 261 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &configm); err != nil { 262 return fmt.Errorf("snap %q option %q is not a map", instanceName, strings.Join(subkeys[:pos+1], ".")) 263 } 264 return getFromConfig(instanceName, subkeys, pos+1, configm, result) 265 } 266 267 // Commit applies to the state the configuration changes made in the transaction 268 // and updates the observed configuration to the result of the operation. 269 // 270 // The state associated with the transaction must be locked by the caller. 271 func (t *Transaction) Commit() { 272 t.mu.Lock() 273 defer t.mu.Unlock() 274 275 if len(t.changes) == 0 { 276 return 277 } 278 279 // Update our copy of the config with the most recent one from the state. 280 err := t.state.Get("config", &t.pristine) 281 if err == state.ErrNoState { 282 t.pristine = make(map[string]map[string]*json.RawMessage) 283 } else if err != nil { 284 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 285 } 286 287 // Iterate through the write cache and save each item. 288 for instanceName, snapChanges := range t.changes { 289 config, ok := t.pristine[instanceName] 290 if !ok { 291 config = make(map[string]*json.RawMessage) 292 } 293 applyChanges(config, snapChanges) 294 purgeNulls(config) 295 t.pristine[instanceName] = config 296 } 297 298 t.state.Set("config", t.pristine) 299 300 // The cache has been flushed, reset it. 301 t.changes = make(map[string]map[string]interface{}) 302 } 303 304 func applyChanges(config map[string]*json.RawMessage, changes map[string]interface{}) { 305 for k, v := range changes { 306 config[k] = commitChange(config[k], v) 307 } 308 } 309 310 func jsonRaw(v interface{}) *json.RawMessage { 311 data, err := json.Marshal(v) 312 if err != nil { 313 panic(fmt.Errorf("internal error: cannot marshal configuration: %v", err)) 314 } 315 raw := json.RawMessage(data) 316 return &raw 317 } 318 319 func commitChange(pristine *json.RawMessage, change interface{}) *json.RawMessage { 320 switch change := change.(type) { 321 case *json.RawMessage: 322 return change 323 case map[string]interface{}: 324 if pristine == nil { 325 return jsonRaw(change) 326 } 327 var pristinem map[string]*json.RawMessage 328 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*pristine), &pristinem); err != nil { 329 // Not a map. Overwrite with the change. 330 return jsonRaw(change) 331 } 332 for k, v := range change { 333 pristinem[k] = commitChange(pristinem[k], v) 334 } 335 return jsonRaw(pristinem) 336 } 337 panic(fmt.Errorf("internal error: unexpected configuration type %T", change)) 338 } 339 340 // IsNoOption returns whether the provided error is a *NoOptionError. 341 func IsNoOption(err error) bool { 342 _, ok := err.(*NoOptionError) 343 return ok 344 } 345 346 // NoOptionError indicates that a config option is not set. 347 type NoOptionError struct { 348 SnapName string 349 Key string 350 } 351 352 func (e *NoOptionError) Error() string { 353 if e.Key == "" { 354 return fmt.Sprintf("snap %q has no configuration", e.SnapName) 355 } 356 return fmt.Sprintf("snap %q has no %q configuration option", e.SnapName, e.Key) 357 }