github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/config/helpers.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"regexp"
    27  	"sort"
    28  	"strings"
    29  
    30  	"github.com/snapcore/snapd/jsonutil"
    31  	"github.com/snapcore/snapd/overlord/state"
    32  	"github.com/snapcore/snapd/snap"
    33  )
    34  
    35  var validKey = regexp.MustCompile("^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$")
    36  
    37  func ParseKey(key string) (subkeys []string, err error) {
    38  	if key == "" {
    39  		return []string{}, nil
    40  	}
    41  	subkeys = strings.Split(key, ".")
    42  	for _, subkey := range subkeys {
    43  		if !validKey.MatchString(subkey) {
    44  			return nil, fmt.Errorf("invalid option name: %q", subkey)
    45  		}
    46  	}
    47  	return subkeys, nil
    48  }
    49  
    50  func purgeNulls(config interface{}) interface{} {
    51  	switch config := config.(type) {
    52  	// map of json raw messages is the starting point for purgeNulls, this is the configuration we receive
    53  	case map[string]*json.RawMessage:
    54  		for k, v := range config {
    55  			if cfg := purgeNulls(v); cfg != nil {
    56  				config[k] = cfg.(*json.RawMessage)
    57  			} else {
    58  				delete(config, k)
    59  			}
    60  		}
    61  	case map[string]interface{}:
    62  		for k, v := range config {
    63  			if cfg := purgeNulls(v); cfg != nil {
    64  				config[k] = cfg
    65  			} else {
    66  				delete(config, k)
    67  			}
    68  		}
    69  	case *json.RawMessage:
    70  		if config == nil {
    71  			return nil
    72  		}
    73  		var configm interface{}
    74  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil {
    75  			panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err))
    76  		}
    77  		if cfg := purgeNulls(configm); cfg != nil {
    78  			return jsonRaw(cfg)
    79  		}
    80  		return nil
    81  	}
    82  	return config
    83  }
    84  
    85  func PatchConfig(snapName string, subkeys []string, pos int, config interface{}, value *json.RawMessage) (interface{}, error) {
    86  	switch config := config.(type) {
    87  	case nil:
    88  		// Missing update map. Create and nest final value under it.
    89  		configm := make(map[string]interface{})
    90  		_, err := PatchConfig(snapName, subkeys, pos, configm, value)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  		return configm, nil
    95  
    96  	case *json.RawMessage:
    97  		// Raw replaces pristine on commit. Unpack, update, and repack.
    98  		var configm map[string]interface{}
    99  
   100  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil {
   101  			return nil, fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos], "."))
   102  		}
   103  		// preserve the invariant that if PatchConfig is
   104  		// passed a map[string]interface{} it is not nil.
   105  		// If the value was set to null in the same
   106  		// transaction use (interface{})(nil) which is handled
   107  		// by the first case here.
   108  		// (see LP: #1920773)
   109  		var cfg interface{}
   110  		if configm != nil {
   111  			cfg = configm
   112  		}
   113  		result, err := PatchConfig(snapName, subkeys, pos, cfg, value)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  
   118  		// PatchConfig may have recreated higher level element that was previously set to null in same
   119  		// transaction; returning the result for PatchConfig rather than just configm ensures we do
   120  		// support cases where a previously unset path is set back.
   121  		return jsonRaw(result), nil
   122  
   123  	case map[string]interface{}:
   124  		// Update map to apply against pristine on commit.
   125  		if pos+1 == len(subkeys) {
   126  			config[subkeys[pos]] = value
   127  			return config, nil
   128  		} else {
   129  			result, err := PatchConfig(snapName, subkeys, pos+1, config[subkeys[pos]], value)
   130  			if err != nil {
   131  				return nil, err
   132  			}
   133  			config[subkeys[pos]] = result
   134  			return config, nil
   135  		}
   136  	}
   137  	return nil, fmt.Errorf("internal error: unexpected configuration type %T", config)
   138  }
   139  
   140  // GetSnapConfig retrieves the raw configuration of a given snap.
   141  func GetSnapConfig(st *state.State, snapName string) (*json.RawMessage, error) {
   142  	var config map[string]*json.RawMessage
   143  	err := st.Get("config", &config)
   144  	if err == state.ErrNoState {
   145  		return nil, nil
   146  	}
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	snapcfg, ok := config[snapName]
   151  	if !ok {
   152  		return nil, nil
   153  	}
   154  	return snapcfg, nil
   155  }
   156  
   157  // SetSnapConfig replaces the configuration of a given snap.
   158  func SetSnapConfig(st *state.State, snapName string, snapcfg *json.RawMessage) error {
   159  	var config map[string]*json.RawMessage
   160  	err := st.Get("config", &config)
   161  	// empty nil snapcfg should be an empty message, but deal with "null" as well.
   162  	isNil := snapcfg == nil || len(*snapcfg) == 0 || bytes.Compare(*snapcfg, []byte("null")) == 0
   163  	if err == state.ErrNoState {
   164  		if isNil {
   165  			// bail out early
   166  			return nil
   167  		}
   168  		config = make(map[string]*json.RawMessage, 1)
   169  	} else if err != nil {
   170  		return err
   171  	}
   172  	if isNil {
   173  		delete(config, snapName)
   174  	} else {
   175  		config[snapName] = snapcfg
   176  	}
   177  	st.Set("config", config)
   178  	return nil
   179  }
   180  
   181  // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config.
   182  // It doesn't do anything if there is no configuration for given snap in the state.
   183  // The caller is responsible for locking the state.
   184  func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   185  	var config map[string]*json.RawMessage                    // snap => configuration
   186  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   187  
   188  	// Get current configuration of the snap from state
   189  	err := st.Get("config", &config)
   190  	if err == state.ErrNoState {
   191  		return nil
   192  	} else if err != nil {
   193  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   194  	}
   195  	snapcfg, ok := config[snapName]
   196  	if !ok {
   197  		return nil
   198  	}
   199  
   200  	err = st.Get("revision-config", &revisionConfig)
   201  	if err == state.ErrNoState {
   202  		revisionConfig = make(map[string]map[string]*json.RawMessage)
   203  	} else if err != nil {
   204  		return err
   205  	}
   206  	cfgs := revisionConfig[snapName]
   207  	if cfgs == nil {
   208  		cfgs = make(map[string]*json.RawMessage)
   209  	}
   210  	cfgs[rev.String()] = snapcfg
   211  	revisionConfig[snapName] = cfgs
   212  	st.Set("revision-config", revisionConfig)
   213  	return nil
   214  }
   215  
   216  // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName.
   217  // If no configuration exists for given revision it does nothing (no error).
   218  // The caller is responsible for locking the state.
   219  func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   220  	var config map[string]*json.RawMessage                    // snap => configuration
   221  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   222  
   223  	err := st.Get("revision-config", &revisionConfig)
   224  	if err == state.ErrNoState {
   225  		return nil
   226  	} else if err != nil {
   227  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   228  	}
   229  
   230  	err = st.Get("config", &config)
   231  	if err == state.ErrNoState {
   232  		config = make(map[string]*json.RawMessage)
   233  	} else if err != nil {
   234  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   235  	}
   236  
   237  	if cfg, ok := revisionConfig[snapName]; ok {
   238  		if revCfg, ok := cfg[rev.String()]; ok {
   239  			config[snapName] = revCfg
   240  			st.Set("config", config)
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // DiscardRevisionConfig removes configuration snapshot of given snap/revision.
   248  // If no configuration exists for given revision it does nothing (no error).
   249  // The caller is responsible for locking the state.
   250  func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   251  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   252  	err := st.Get("revision-config", &revisionConfig)
   253  	if err == state.ErrNoState {
   254  		return nil
   255  	} else if err != nil {
   256  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   257  	}
   258  
   259  	if revCfgs, ok := revisionConfig[snapName]; ok {
   260  		delete(revCfgs, rev.String())
   261  		if len(revCfgs) == 0 {
   262  			delete(revisionConfig, snapName)
   263  		} else {
   264  			revisionConfig[snapName] = revCfgs
   265  		}
   266  		st.Set("revision-config", revisionConfig)
   267  	}
   268  	return nil
   269  }
   270  
   271  // DeleteSnapConfig removed configuration of given snap from the state.
   272  func DeleteSnapConfig(st *state.State, snapName string) error {
   273  	var config map[string]map[string]*json.RawMessage // snap => key => value
   274  
   275  	err := st.Get("config", &config)
   276  	if err == state.ErrNoState {
   277  		return nil
   278  	} else if err != nil {
   279  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   280  	}
   281  	if _, ok := config[snapName]; ok {
   282  		delete(config, snapName)
   283  		st.Set("config", config)
   284  	}
   285  	return nil
   286  }
   287  
   288  // Conf is an interface describing both state and transaction.
   289  type Conf interface {
   290  	Get(snapName, key string, result interface{}) error
   291  	GetMaybe(snapName, key string, result interface{}) error
   292  	GetPristine(snapName, key string, result interface{}) error
   293  	Set(snapName, key string, value interface{}) error
   294  	Changes() []string
   295  	State() *state.State
   296  }
   297  
   298  // ConfGetter is an interface for reading of config values.
   299  type ConfGetter interface {
   300  	Get(snapName, key string, result interface{}) error
   301  	GetMaybe(snapName, key string, result interface{}) error
   302  	GetPristine(snapName, key string, result interface{}) error
   303  }
   304  
   305  // Patch sets values in cfg for the provided snap's configuration
   306  // based on patch.
   307  // patch keys can be dotted as the key argument to Set.
   308  // The patch is applied according to the order of its keys sorted by depth,
   309  // with top keys sorted first.
   310  func Patch(cfg Conf, snapName string, patch map[string]interface{}) error {
   311  	patchKeys := sortPatchKeysByDepth(patch)
   312  	for _, key := range patchKeys {
   313  		if err := cfg.Set(snapName, key, patch[key]); err != nil {
   314  			return err
   315  		}
   316  	}
   317  	return nil
   318  }
   319  
   320  func sortPatchKeysByDepth(patch map[string]interface{}) []string {
   321  	if len(patch) == 0 {
   322  		return nil
   323  	}
   324  	depths := make(map[string]int, len(patch))
   325  	keys := make([]string, 0, len(patch))
   326  	for k := range patch {
   327  		depths[k] = strings.Count(k, ".")
   328  		keys = append(keys, k)
   329  	}
   330  
   331  	sort.Slice(keys, func(i, j int) bool {
   332  		return depths[keys[i]] < depths[keys[j]]
   333  	})
   334  	return keys
   335  }