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  }