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  }