github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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  
    87  	switch config := config.(type) {
    88  	case nil:
    89  		// Missing update map. Create and nest final value under it.
    90  		configm := make(map[string]interface{})
    91  		_, err := PatchConfig(snapName, subkeys, pos, configm, value)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  		return configm, nil
    96  
    97  	case *json.RawMessage:
    98  		// Raw replaces pristine on commit. Unpack, update, and repack.
    99  		var configm map[string]interface{}
   100  
   101  		if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil {
   102  			return nil, fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos], "."))
   103  		}
   104  		_, err := PatchConfig(snapName, subkeys, pos, configm, value)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  		return jsonRaw(configm), nil
   109  
   110  	case map[string]interface{}:
   111  		// Update map to apply against pristine on commit.
   112  		if pos+1 == len(subkeys) {
   113  			config[subkeys[pos]] = value
   114  			return config, nil
   115  		} else {
   116  			result, err := PatchConfig(snapName, subkeys, pos+1, config[subkeys[pos]], value)
   117  			if err != nil {
   118  				return nil, err
   119  			}
   120  			config[subkeys[pos]] = result
   121  			return config, nil
   122  		}
   123  	}
   124  	return nil, fmt.Errorf("internal error: unexpected configuration type %T", config)
   125  }
   126  
   127  // GetSnapConfig retrieves the raw configuration of a given snap.
   128  func GetSnapConfig(st *state.State, snapName string) (*json.RawMessage, error) {
   129  	var config map[string]*json.RawMessage
   130  	err := st.Get("config", &config)
   131  	if err == state.ErrNoState {
   132  		return nil, nil
   133  	}
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	snapcfg, ok := config[snapName]
   138  	if !ok {
   139  		return nil, nil
   140  	}
   141  	return snapcfg, nil
   142  }
   143  
   144  // SetSnapConfig replaces the configuration of a given snap.
   145  func SetSnapConfig(st *state.State, snapName string, snapcfg *json.RawMessage) error {
   146  	var config map[string]*json.RawMessage
   147  	err := st.Get("config", &config)
   148  	// empty nil snapcfg should be an empty message, but deal with "null" as well.
   149  	isNil := snapcfg == nil || len(*snapcfg) == 0 || bytes.Compare(*snapcfg, []byte("null")) == 0
   150  	if err == state.ErrNoState {
   151  		if isNil {
   152  			// bail out early
   153  			return nil
   154  		}
   155  		config = make(map[string]*json.RawMessage, 1)
   156  	} else if err != nil {
   157  		return err
   158  	}
   159  	if isNil {
   160  		delete(config, snapName)
   161  	} else {
   162  		config[snapName] = snapcfg
   163  	}
   164  	st.Set("config", config)
   165  	return nil
   166  }
   167  
   168  // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config.
   169  // It doesn't do anything if there is no configuration for given snap in the state.
   170  // The caller is responsible for locking the state.
   171  func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   172  	var config map[string]*json.RawMessage                    // snap => configuration
   173  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   174  
   175  	// Get current configuration of the snap from state
   176  	err := st.Get("config", &config)
   177  	if err == state.ErrNoState {
   178  		return nil
   179  	} else if err != nil {
   180  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   181  	}
   182  	snapcfg, ok := config[snapName]
   183  	if !ok {
   184  		return nil
   185  	}
   186  
   187  	err = st.Get("revision-config", &revisionConfig)
   188  	if err == state.ErrNoState {
   189  		revisionConfig = make(map[string]map[string]*json.RawMessage)
   190  	} else if err != nil {
   191  		return err
   192  	}
   193  	cfgs := revisionConfig[snapName]
   194  	if cfgs == nil {
   195  		cfgs = make(map[string]*json.RawMessage)
   196  	}
   197  	cfgs[rev.String()] = snapcfg
   198  	revisionConfig[snapName] = cfgs
   199  	st.Set("revision-config", revisionConfig)
   200  	return nil
   201  }
   202  
   203  // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName.
   204  // If no configuration exists for given revision it does nothing (no error).
   205  // The caller is responsible for locking the state.
   206  func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   207  	var config map[string]*json.RawMessage                    // snap => configuration
   208  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   209  
   210  	err := st.Get("revision-config", &revisionConfig)
   211  	if err == state.ErrNoState {
   212  		return nil
   213  	} else if err != nil {
   214  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   215  	}
   216  
   217  	err = st.Get("config", &config)
   218  	if err == state.ErrNoState {
   219  		config = make(map[string]*json.RawMessage)
   220  	} else if err != nil {
   221  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   222  	}
   223  
   224  	if cfg, ok := revisionConfig[snapName]; ok {
   225  		if revCfg, ok := cfg[rev.String()]; ok {
   226  			config[snapName] = revCfg
   227  			st.Set("config", config)
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // DiscardRevisionConfig removes configuration snapshot of given snap/revision.
   235  // If no configuration exists for given revision it does nothing (no error).
   236  // The caller is responsible for locking the state.
   237  func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   238  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   239  	err := st.Get("revision-config", &revisionConfig)
   240  	if err == state.ErrNoState {
   241  		return nil
   242  	} else if err != nil {
   243  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   244  	}
   245  
   246  	if revCfgs, ok := revisionConfig[snapName]; ok {
   247  		delete(revCfgs, rev.String())
   248  		if len(revCfgs) == 0 {
   249  			delete(revisionConfig, snapName)
   250  		} else {
   251  			revisionConfig[snapName] = revCfgs
   252  		}
   253  		st.Set("revision-config", revisionConfig)
   254  	}
   255  	return nil
   256  }
   257  
   258  // DeleteSnapConfig removed configuration of given snap from the state.
   259  func DeleteSnapConfig(st *state.State, snapName string) error {
   260  	var config map[string]map[string]*json.RawMessage // snap => key => value
   261  
   262  	err := st.Get("config", &config)
   263  	if err == state.ErrNoState {
   264  		return nil
   265  	} else if err != nil {
   266  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   267  	}
   268  	if _, ok := config[snapName]; ok {
   269  		delete(config, snapName)
   270  		st.Set("config", config)
   271  	}
   272  	return nil
   273  }
   274  
   275  // Conf is an interface describing both state and transaction.
   276  type Conf interface {
   277  	Get(snapName, key string, result interface{}) error
   278  	GetMaybe(snapName, key string, result interface{}) error
   279  	GetPristine(snapName, key string, result interface{}) error
   280  	Set(snapName, key string, value interface{}) error
   281  	Changes() []string
   282  	State() *state.State
   283  }
   284  
   285  // ConfGetter is an interface for reading of config values.
   286  type ConfGetter interface {
   287  	Get(snapName, key string, result interface{}) error
   288  	GetMaybe(snapName, key string, result interface{}) error
   289  }
   290  
   291  // Patch sets values in cfg for the provided snap's configuration
   292  // based on patch.
   293  // patch keys can be dotted as the key argument to Set.
   294  // The patch is applied according to the order of its keys sorted by depth,
   295  // with top keys sorted first.
   296  func Patch(cfg Conf, snapName string, patch map[string]interface{}) error {
   297  	patchKeys := sortPatchKeysByDepth(patch)
   298  	for _, key := range patchKeys {
   299  		if err := cfg.Set(snapName, key, patch[key]); err != nil {
   300  			return err
   301  		}
   302  	}
   303  	return nil
   304  }
   305  
   306  func sortPatchKeysByDepth(patch map[string]interface{}) []string {
   307  	if len(patch) == 0 {
   308  		return nil
   309  	}
   310  	depths := make(map[string]int, len(patch))
   311  	keys := make([]string, 0, len(patch))
   312  	for k := range patch {
   313  		depths[k] = strings.Count(k, ".")
   314  		keys = append(keys, k)
   315  	}
   316  
   317  	sort.Slice(keys, func(i, j int) bool {
   318  		return depths[keys[i]] < depths[keys[j]]
   319  	})
   320  	return keys
   321  }