github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 := t.changes[instanceName]
   119  	if config == nil {
   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  
   144  	// config here is never nil and PatchConfig always operates
   145  	// directly on and returns config if it's a
   146  	// map[string]interface{}
   147  	_, err = PatchConfig(instanceName, subkeys, 0, config, &raw)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	t.changes[instanceName] = config
   153  	return nil
   154  }
   155  
   156  func (t *Transaction) copyPristine(snapName string) map[string]*json.RawMessage {
   157  	out := make(map[string]*json.RawMessage)
   158  	if config, ok := t.pristine[snapName]; ok {
   159  		for k, v := range config {
   160  			out[k] = v
   161  		}
   162  	}
   163  	return out
   164  }
   165  
   166  // Get unmarshals into result the cached value of the provided snap's configuration key.
   167  // If the key does not exist, an error of type *NoOptionError is returned.
   168  // The provided key may be formed as a dotted key path through nested maps.
   169  // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map.
   170  //
   171  // Transactions do not see updates from the current state or from other transactions.
   172  func (t *Transaction) Get(snapName, key string, result interface{}) error {
   173  	t.mu.Lock()
   174  	defer t.mu.Unlock()
   175  
   176  	subkeys, err := ParseKey(key)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	// commit changes onto a copy of pristine configuration, so that get has a complete view of the config.
   182  	config := t.copyPristine(snapName)
   183  	applyChanges(config, t.changes[snapName])
   184  
   185  	purgeNulls(config)
   186  	return getFromConfig(snapName, subkeys, 0, config, result)
   187  }
   188  
   189  // GetMaybe unmarshals into result the cached value of the provided snap's configuration key.
   190  // If the key does not exist, no error is returned.
   191  //
   192  // Transactions do not see updates from the current state or from other transactions.
   193  func (t *Transaction) GetMaybe(instanceName, key string, result interface{}) error {
   194  	err := t.Get(instanceName, key, result)
   195  	if err != nil && !IsNoOption(err) {
   196  		return err
   197  	}
   198  	return nil
   199  }
   200  
   201  // GetPristine unmarshals the cached pristine (before applying any
   202  // changes) value of the provided snap's configuration key into
   203  // result.
   204  //
   205  // If the key does not exist, an error of type *NoOptionError is returned.
   206  func (t *Transaction) GetPristine(snapName, key string, result interface{}) error {
   207  	t.mu.Lock()
   208  	defer t.mu.Unlock()
   209  
   210  	subkeys, err := ParseKey(key)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	return getFromConfig(snapName, subkeys, 0, t.pristine[snapName], result)
   216  }
   217  
   218  // GetPristineMaybe unmarshals the cached pristine (before applying any
   219  // changes) value of the provided snap's configuration key into
   220  // result.
   221  //
   222  // If the key does not exist, no error is returned.
   223  func (t *Transaction) GetPristineMaybe(instanceName, key string, result interface{}) error {
   224  	err := t.GetPristine(instanceName, key, result)
   225  	if err != nil && !IsNoOption(err) {
   226  		return err
   227  	}
   228  	return nil
   229  }
   230  
   231  func getFromConfig(instanceName string, subkeys []string, pos int, config map[string]*json.RawMessage, result interface{}) error {
   232  	// special case - get root document
   233  	if len(subkeys) == 0 {
   234  		if len(config) == 0 {
   235  			return &NoOptionError{SnapName: instanceName}
   236  		}
   237  		raw := jsonRaw(config)
   238  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil {
   239  			return fmt.Errorf("internal error: cannot unmarshal snap %q root document: %s", instanceName, err)
   240  		}
   241  		return nil
   242  	}
   243  
   244  	raw, ok := config[subkeys[pos]]
   245  	if !ok {
   246  		return &NoOptionError{SnapName: instanceName, Key: strings.Join(subkeys[:pos+1], ".")}
   247  	}
   248  
   249  	// There is a known problem with json raw messages representing nulls when they are stored in nested structures, such as
   250  	// config map inside our state. These are turned into nils and need to be handled explicitly.
   251  	if raw == nil {
   252  		m := json.RawMessage("null")
   253  		raw = &m
   254  	}
   255  
   256  	if pos+1 == len(subkeys) {
   257  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil {
   258  			key := strings.Join(subkeys, ".")
   259  			return fmt.Errorf("internal error: cannot unmarshal snap %q option %q into %T: %s, json: %s", instanceName, key, result, err, *raw)
   260  		}
   261  		return nil
   262  	}
   263  
   264  	var configm map[string]*json.RawMessage
   265  	if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &configm); err != nil {
   266  		return fmt.Errorf("snap %q option %q is not a map", instanceName, strings.Join(subkeys[:pos+1], "."))
   267  	}
   268  	return getFromConfig(instanceName, subkeys, pos+1, configm, result)
   269  }
   270  
   271  // Commit applies to the state the configuration changes made in the transaction
   272  // and updates the observed configuration to the result of the operation.
   273  //
   274  // The state associated with the transaction must be locked by the caller.
   275  func (t *Transaction) Commit() {
   276  	t.mu.Lock()
   277  	defer t.mu.Unlock()
   278  
   279  	if len(t.changes) == 0 {
   280  		return
   281  	}
   282  
   283  	// Update our copy of the config with the most recent one from the state.
   284  	err := t.state.Get("config", &t.pristine)
   285  	if err == state.ErrNoState {
   286  		t.pristine = make(map[string]map[string]*json.RawMessage)
   287  	} else if err != nil {
   288  		panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err))
   289  	}
   290  
   291  	// Iterate through the write cache and save each item.
   292  	for instanceName, snapChanges := range t.changes {
   293  		config := t.pristine[instanceName]
   294  		// due to LP #1917870 we might have a hook configure task in flight
   295  		// that tries to apply config over nil map, create it if nil.
   296  		if config == nil {
   297  			config = make(map[string]*json.RawMessage)
   298  		}
   299  		applyChanges(config, snapChanges)
   300  		purgeNulls(config)
   301  		t.pristine[instanceName] = config
   302  	}
   303  
   304  	t.state.Set("config", t.pristine)
   305  
   306  	// The cache has been flushed, reset it.
   307  	t.changes = make(map[string]map[string]interface{})
   308  }
   309  
   310  func applyChanges(config map[string]*json.RawMessage, changes map[string]interface{}) {
   311  	for k, v := range changes {
   312  		config[k] = commitChange(config[k], v)
   313  	}
   314  }
   315  
   316  func jsonRaw(v interface{}) *json.RawMessage {
   317  	data, err := json.Marshal(v)
   318  	if err != nil {
   319  		panic(fmt.Errorf("internal error: cannot marshal configuration: %v", err))
   320  	}
   321  	raw := json.RawMessage(data)
   322  	return &raw
   323  }
   324  
   325  func commitChange(pristine *json.RawMessage, change interface{}) *json.RawMessage {
   326  	switch change := change.(type) {
   327  	case *json.RawMessage:
   328  		return change
   329  	case map[string]interface{}:
   330  		if pristine == nil {
   331  			return jsonRaw(change)
   332  		}
   333  		var pristinem map[string]*json.RawMessage
   334  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*pristine), &pristinem); err != nil {
   335  			// Not a map. Overwrite with the change.
   336  			return jsonRaw(change)
   337  		}
   338  		for k, v := range change {
   339  			pristinem[k] = commitChange(pristinem[k], v)
   340  		}
   341  		return jsonRaw(pristinem)
   342  	}
   343  	panic(fmt.Errorf("internal error: unexpected configuration type %T", change))
   344  }
   345  
   346  // IsNoOption returns whether the provided error is a *NoOptionError.
   347  func IsNoOption(err error) bool {
   348  	_, ok := err.(*NoOptionError)
   349  	return ok
   350  }
   351  
   352  // NoOptionError indicates that a config option is not set.
   353  type NoOptionError struct {
   354  	SnapName string
   355  	Key      string
   356  }
   357  
   358  func (e *NoOptionError) Error() string {
   359  	if e.Key == "" {
   360  		return fmt.Sprintf("snap %q has no configuration", e.SnapName)
   361  	}
   362  	return fmt.Sprintf("snap %q has no %q configuration option", e.SnapName, e.Key)
   363  }