github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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 "reflect" 27 "sort" 28 "strings" 29 "sync" 30 31 "github.com/snapcore/snapd/jsonutil" 32 "github.com/snapcore/snapd/overlord/state" 33 ) 34 35 // Transaction holds a copy of the configuration originally present in the 36 // provided state which can be queried and mutated in isolation from 37 // concurrent logic. All changes performed into it are persisted back into 38 // the state at once when Commit is called. 39 // 40 // Transactions are safe to access and modify concurrently. 41 type Transaction struct { 42 mu sync.Mutex 43 state *state.State 44 pristine map[string]map[string]*json.RawMessage // snap => key => value 45 changes map[string]map[string]interface{} 46 } 47 48 // NewTransaction creates a new configuration transaction initialized with the given state. 49 // 50 // The provided state must be locked by the caller. 51 func NewTransaction(st *state.State) *Transaction { 52 transaction := &Transaction{state: st} 53 transaction.changes = make(map[string]map[string]interface{}) 54 55 // Record the current state of the map containing the config of every snap 56 // in the system. We'll use it for this transaction. 57 err := st.Get("config", &transaction.pristine) 58 if err == state.ErrNoState { 59 transaction.pristine = make(map[string]map[string]*json.RawMessage) 60 } else if err != nil { 61 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 62 } 63 return transaction 64 } 65 66 // State returns the system State 67 func (t *Transaction) State() *state.State { 68 return t.state 69 } 70 71 func changes(cfgStr string, cfg map[string]interface{}) []string { 72 var out []string 73 for k := range cfg { 74 switch subCfg := cfg[k].(type) { 75 case map[string]interface{}: 76 out = append(out, changes(cfgStr+"."+k, subCfg)...) 77 case *json.RawMessage: 78 // check if we need to dive into a sub-config 79 var configm map[string]interface{} 80 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*subCfg), &configm); err == nil { 81 // curiously, json decoder decodes json.RawMessage("null") into a nil map, so no change is 82 // reported when we recurse into it. This happens when unsetting a key and the underlying 83 // config path doesn't exist. 84 if len(configm) > 0 { 85 out = append(out, changes(cfgStr+"."+k, configm)...) 86 continue 87 } 88 } 89 out = append(out, []string{cfgStr + "." + k}...) 90 default: 91 out = append(out, []string{cfgStr + "." + k}...) 92 } 93 } 94 return out 95 } 96 97 // Changes returns the changing keys associated with this transaction 98 func (t *Transaction) Changes() []string { 99 var out []string 100 for k := range t.changes { 101 out = append(out, changes(k, t.changes[k])...) 102 } 103 sort.Strings(out) 104 return out 105 } 106 107 // shadowsExternalConfig checks that the given subkeys/value does not 108 // "block" the path to a external config with a non-map type. E.g. if 109 // "network.netplan" is external it must be impossible to set 110 // "network=false" or getting the document under "network" would be 111 // wrong. 112 func shadowsExternalConfig(instanceName string, key string, value interface{}) error { 113 // maps never block the path 114 if v := reflect.ValueOf(value); v.Kind() == reflect.Map { 115 return nil 116 } 117 // be paranoid: this should never happen but if it does we need to know 118 if _, ok := value.(*json.RawMessage); ok { 119 return fmt.Errorf("internal error: shadowsExternalConfig called with *json.RawMessage for snap %q with key %q: %q please report as a bug", instanceName, key, value) 120 } 121 122 externalConfigMu.Lock() 123 km := externalConfigMap[instanceName] 124 externalConfigMu.Unlock() 125 126 for externalKey := range km { 127 if strings.HasPrefix(externalKey, key+".") { 128 return fmt.Errorf("cannot set %q for %q to non-map value because %q is a external configuration", key, instanceName, externalKey) 129 } 130 } 131 132 return nil 133 } 134 135 // Set sets the provided snap's configuration key to the given value. 136 // The provided key may be formed as a dotted key path through nested maps. 137 // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map. 138 // When the key is provided in that form, intermediate maps are mutated 139 // rather than replaced, and created when necessary. 140 // 141 // The provided value must marshal properly by encoding/json. 142 // Changes are not persisted until Commit is called. 143 func (t *Transaction) Set(instanceName, key string, value interface{}) error { 144 t.mu.Lock() 145 defer t.mu.Unlock() 146 147 config := t.changes[instanceName] 148 if config == nil { 149 config = make(map[string]interface{}) 150 } 151 152 data, err := json.Marshal(value) 153 if err != nil { 154 return fmt.Errorf("cannot marshal snap %q option %q: %s", instanceName, key, err) 155 } 156 raw := json.RawMessage(data) 157 158 subkeys, err := ParseKey(key) 159 if err != nil { 160 return err 161 } 162 163 // Check whether it's trying to traverse a non-map from pristine. This 164 // would go unperceived by the configuration patching below. 165 if len(subkeys) > 1 { 166 var result interface{} 167 err = getFromConfig(instanceName, subkeys, 0, t.pristine[instanceName], &result) 168 if err != nil && !IsNoOption(err) { 169 return err 170 } 171 } 172 // check that we do not "block" a path to external config with non-maps 173 if err := shadowsExternalConfig(instanceName, key, value); err != nil { 174 return err 175 } 176 177 // config here is never nil and PatchConfig always operates 178 // directly on and returns config if it's a 179 // map[string]interface{} 180 _, err = PatchConfig(instanceName, subkeys, 0, config, &raw) 181 if err != nil { 182 return err 183 } 184 185 t.changes[instanceName] = config 186 return nil 187 } 188 189 func (t *Transaction) copyPristine(snapName string) map[string]*json.RawMessage { 190 out := make(map[string]*json.RawMessage) 191 if config, ok := t.pristine[snapName]; ok { 192 for k, v := range config { 193 out[k] = v 194 } 195 } 196 return out 197 } 198 199 // Get unmarshals into result the cached value of the provided snap's configuration key. 200 // If the key does not exist, an error of type *NoOptionError is returned. 201 // The provided key may be formed as a dotted key path through nested maps. 202 // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map. 203 // 204 // Transactions do not see updates from the current state or from other transactions. 205 func (t *Transaction) Get(snapName, key string, result interface{}) error { 206 t.mu.Lock() 207 defer t.mu.Unlock() 208 209 subkeys, err := ParseKey(key) 210 if err != nil { 211 return err 212 } 213 214 // merge external config and then commit changes onto a copy of pristine configuration, so that get has a complete view of the config. 215 config := t.copyPristine(snapName) 216 if err := mergeConfigWithExternal(snapName, key, &config); err != nil { 217 return err 218 } 219 applyChanges(config, t.changes[snapName]) 220 221 purgeNulls(config) 222 return getFromConfig(snapName, subkeys, 0, config, result) 223 } 224 225 // GetMaybe unmarshals into result the cached value of the provided snap's configuration key. 226 // If the key does not exist, no error is returned. 227 // 228 // Transactions do not see updates from the current state or from other transactions. 229 func (t *Transaction) GetMaybe(instanceName, key string, result interface{}) error { 230 err := t.Get(instanceName, key, result) 231 if err != nil && !IsNoOption(err) { 232 return err 233 } 234 return nil 235 } 236 237 // GetPristine unmarshals the cached pristine (before applying any 238 // changes) value of the provided snap's configuration key into 239 // result. 240 // 241 // If the key does not exist, an error of type *NoOptionError is returned. 242 func (t *Transaction) GetPristine(snapName, key string, result interface{}) error { 243 t.mu.Lock() 244 defer t.mu.Unlock() 245 246 subkeys, err := ParseKey(key) 247 if err != nil { 248 return err 249 } 250 251 return getFromConfig(snapName, subkeys, 0, t.pristine[snapName], result) 252 } 253 254 // GetPristineMaybe unmarshals the cached pristine (before applying any 255 // changes) value of the provided snap's configuration key into 256 // result. 257 // 258 // If the key does not exist, no error is returned. 259 func (t *Transaction) GetPristineMaybe(instanceName, key string, result interface{}) error { 260 err := t.GetPristine(instanceName, key, result) 261 if err != nil && !IsNoOption(err) { 262 return err 263 } 264 return nil 265 } 266 267 func getFromConfig(instanceName string, subkeys []string, pos int, config map[string]*json.RawMessage, result interface{}) error { 268 // special case - get root document 269 if len(subkeys) == 0 { 270 if len(config) == 0 { 271 return &NoOptionError{SnapName: instanceName} 272 } 273 raw := jsonRaw(config) 274 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { 275 return fmt.Errorf("internal error: cannot unmarshal snap %q root document: %s", instanceName, err) 276 } 277 return nil 278 } 279 280 raw, ok := config[subkeys[pos]] 281 if !ok { 282 return &NoOptionError{SnapName: instanceName, Key: strings.Join(subkeys[:pos+1], ".")} 283 } 284 285 // There is a known problem with json raw messages representing nulls when they are stored in nested structures, such as 286 // config map inside our state. These are turned into nils and need to be handled explicitly. 287 if raw == nil { 288 m := json.RawMessage("null") 289 raw = &m 290 } 291 292 if pos+1 == len(subkeys) { 293 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { 294 key := strings.Join(subkeys, ".") 295 return fmt.Errorf("internal error: cannot unmarshal snap %q option %q into %T: %s, json: %s", instanceName, key, result, err, *raw) 296 } 297 return nil 298 } 299 300 var configm map[string]*json.RawMessage 301 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &configm); err != nil { 302 return fmt.Errorf("snap %q option %q is not a map", instanceName, strings.Join(subkeys[:pos+1], ".")) 303 } 304 return getFromConfig(instanceName, subkeys, pos+1, configm, result) 305 } 306 307 // Commit applies to the state the configuration changes made in the transaction 308 // and updates the observed configuration to the result of the operation. 309 // 310 // The state associated with the transaction must be locked by the caller. 311 func (t *Transaction) Commit() { 312 t.mu.Lock() 313 defer t.mu.Unlock() 314 315 if len(t.changes) == 0 { 316 return 317 } 318 319 // Update our copy of the config with the most recent one from the state. 320 err := t.state.Get("config", &t.pristine) 321 if err == state.ErrNoState { 322 t.pristine = make(map[string]map[string]*json.RawMessage) 323 } else if err != nil { 324 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 325 } 326 327 // Iterate through the write cache and save each item but exclude external configuration 328 for instanceName, snapChanges := range t.changes { 329 clearExternalConfig(instanceName, snapChanges) 330 331 config := t.pristine[instanceName] 332 // due to LP #1917870 we might have a hook configure task in flight 333 // that tries to apply config over nil map, create it if nil. 334 if config == nil { 335 config = make(map[string]*json.RawMessage) 336 } 337 applyChanges(config, snapChanges) 338 purgeNulls(config) 339 t.pristine[instanceName] = config 340 } 341 342 t.state.Set("config", t.pristine) 343 344 // The cache has been flushed, reset it. 345 t.changes = make(map[string]map[string]interface{}) 346 } 347 348 func applyChanges(config map[string]*json.RawMessage, changes map[string]interface{}) { 349 for k, v := range changes { 350 config[k] = commitChange(config[k], v) 351 } 352 } 353 354 func jsonRaw(v interface{}) *json.RawMessage { 355 data, err := json.Marshal(v) 356 if err != nil { 357 panic(fmt.Errorf("internal error: cannot marshal configuration: %v", err)) 358 } 359 raw := json.RawMessage(data) 360 return &raw 361 } 362 363 func commitChange(pristine *json.RawMessage, change interface{}) *json.RawMessage { 364 switch change := change.(type) { 365 case *json.RawMessage: 366 return change 367 case map[string]interface{}: 368 if pristine == nil { 369 return jsonRaw(change) 370 } 371 var pristinem map[string]*json.RawMessage 372 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*pristine), &pristinem); err != nil { 373 // Not a map. Overwrite with the change. 374 return jsonRaw(change) 375 } 376 for k, v := range change { 377 pristinem[k] = commitChange(pristinem[k], v) 378 } 379 return jsonRaw(pristinem) 380 } 381 panic(fmt.Errorf("internal error: unexpected configuration type %T", change)) 382 } 383 384 // overlapsWithExternalConfig() return true if the requested key overlaps with 385 // the given external key. E.g. 386 // true: for requested key "a" and external key "a.external" 387 // false for requested key "z" and external key "a.external" 388 func overlapsWithExternalConfig(requestedKey, externalKey string) (bool, error) { 389 requestedSubkeys, err := ParseKey(requestedKey) 390 if err != nil { 391 return false, fmt.Errorf("cannot check overlap for requested key: %v", err) 392 } 393 externalSubkeys, err := ParseKey(externalKey) 394 if err != nil { 395 return false, fmt.Errorf("cannot check overlap for external key: %v", err) 396 } 397 for i := range requestedSubkeys { 398 if i >= len(externalSubkeys) { 399 return true, nil 400 } 401 if externalSubkeys[i] != requestedSubkeys[i] { 402 return false, nil 403 } 404 } 405 return true, nil 406 } 407 408 // mergeConfigWithExternal takes the given configuration and merges it 409 // with the external configuration values by calling the registered 410 // external configuration function of the given snap. The merged config 411 // is returned. 412 func mergeConfigWithExternal(instanceName, requestedKey string, origConfig *map[string]*json.RawMessage) error { 413 externalConfigMu.Lock() 414 km, ok := externalConfigMap[instanceName] 415 externalConfigMu.Unlock() 416 if !ok { 417 return nil 418 } 419 420 // create a "patch" from the external entries 421 patch := make(map[string]interface{}) 422 for externalKey, externalFn := range km { 423 if externalFn == nil { 424 continue 425 } 426 // check if the requested key is part of the external 427 // configuration 428 partOf, err := overlapsWithExternalConfig(requestedKey, externalKey) 429 if err != nil { 430 return err 431 } 432 if !partOf { 433 continue 434 } 435 436 // Pass the right key to the externalFn(), this can 437 // either be a subtree of the external-tree or the 438 // other externalKey itself. 439 k := requestedKey 440 if len(requestedKey) < len(externalKey) { 441 k = externalKey 442 } 443 res, err := externalFn(k) 444 if err != nil { 445 return err 446 } 447 patch[externalKey] = jsonRaw(res) 448 } 449 if len(patch) == 0 { 450 return nil 451 } 452 453 // create a "working copy" of the config and apply the patches on top 454 config := jsonRaw(*origConfig) 455 patchKeys := sortPatchKeysByDepth(patch) 456 for _, subkeys := range patchKeys { 457 // patch[key] above got assigned jsonRaw() so this cast is ok 458 raw := patch[subkeys].(*json.RawMessage) 459 mergedConfig, err := PatchConfig(instanceName, strings.Split(subkeys, "."), 0, config, raw) 460 if err != nil { 461 return err 462 } 463 // PatchConfig got *json.RawMessage as input and 464 // returns the same type so this cast is ok (but be defensive) 465 config, ok = mergedConfig.(*json.RawMessage) 466 if !ok { 467 return fmt.Errorf("internal error: PatchConfig in mergeConfigWithExternal did not return a *json.RawMessage please report this as a bug") 468 } 469 } 470 471 // XXX: unmarshaling on top of something leaves values in place 472 // (no problem here because we only add external things) 473 // convert back to the original config 474 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), origConfig); err != nil { 475 return err 476 } 477 478 return nil 479 } 480 481 // clearExternalConfig iterates over a given config and removes any values 482 // that come from external configuration. This is used before committing a 483 // config to disk. 484 func clearExternalConfig(instanceName string, snapChanges map[string]interface{}) { 485 externalConfigMu.Lock() 486 km := externalConfigMap[instanceName] 487 externalConfigMu.Unlock() 488 489 clearExternalConfigRecursive(km, snapChanges, "") 490 } 491 492 func clearExternalConfigRecursive(km map[string]ExternalCfgFunc, config map[string]interface{}, keyprefix string) { 493 if len(keyprefix) > 0 { 494 keyprefix += "." 495 } 496 for key, value := range config { 497 // any top-level external keys are removed 498 if _, ok := km[keyprefix+key]; ok { 499 delete(config, key) 500 // we can skip looking for nested config if we 501 // removed the top-level 502 continue 503 } 504 // and nested configs are inspected 505 if m, ok := value.(map[string]interface{}); ok { 506 clearExternalConfigRecursive(km, m, keyprefix+key) 507 } 508 } 509 } 510 511 // IsNoOption returns whether the provided error is a *NoOptionError. 512 func IsNoOption(err error) bool { 513 _, ok := err.(*NoOptionError) 514 return ok 515 } 516 517 // NoOptionError indicates that a config option is not set. 518 type NoOptionError struct { 519 SnapName string 520 Key string 521 } 522 523 func (e *NoOptionError) Error() string { 524 if e.Key == "" { 525 return fmt.Sprintf("snap %q has no configuration", e.SnapName) 526 } 527 return fmt.Sprintf("snap %q has no %q configuration option", e.SnapName, e.Key) 528 } 529 530 // ExternalCfgFunc can be used for external "transaction.Get()" calls 531 type ExternalCfgFunc func(key string) (result interface{}, err error) 532 533 // externalConfigMap contain hook functions for "external" configuration. The 534 // first level of the map is the snapName and then the external keys in 535 // dotted notation e.g. "network.netplan". Any data under a external 536 // configuration option is never stored directly in the state. 537 var ( 538 externalConfigMap map[string]map[string]ExternalCfgFunc 539 externalConfigMu sync.Mutex 540 ) 541 542 // RegisterExternalConfig allows to register a function that is called 543 // when the configuration for the given config key for a given 544 // snapname is requested. 545 // 546 // This is useful when the configuration is stored externally, 547 // e.g. the systems hostname is stored via `hostnamectl` and it does not 548 // make sense to store it the snapd state. Examples are: 549 // - system.timezone 550 // - system.hostname 551 func RegisterExternalConfig(snapName, key string, vf ExternalCfgFunc) error { 552 externalConfigMu.Lock() 553 defer externalConfigMu.Unlock() 554 555 if _, err := ParseKey(key); err != nil { 556 return fmt.Errorf("cannot register external config: %v", err) 557 } 558 559 if externalConfigMap == nil { 560 externalConfigMap = make(map[string]map[string]ExternalCfgFunc) 561 } 562 if _, ok := externalConfigMap[snapName]; !ok { 563 externalConfigMap[snapName] = make(map[string]ExternalCfgFunc) 564 } 565 externalConfigMap[snapName][key] = vf 566 return nil 567 }