github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"strings"
    28  
    29  	"github.com/snapcore/snapd/features"
    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  	isNil := snapcfg == nil || len(*snapcfg) == 0
   149  	if err == state.ErrNoState {
   150  		if isNil {
   151  			// bail out early
   152  			return nil
   153  		}
   154  		config = make(map[string]*json.RawMessage, 1)
   155  	} else if err != nil {
   156  		return err
   157  	}
   158  	if isNil {
   159  		delete(config, snapName)
   160  	} else {
   161  		config[snapName] = snapcfg
   162  	}
   163  	st.Set("config", config)
   164  	return nil
   165  }
   166  
   167  // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config.
   168  // It doesn't do anything if there is no configuration for given snap in the state.
   169  // The caller is responsible for locking the state.
   170  func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   171  	var config map[string]*json.RawMessage                    // snap => configuration
   172  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   173  
   174  	// Get current configuration of the snap from state
   175  	err := st.Get("config", &config)
   176  	if err == state.ErrNoState {
   177  		return nil
   178  	} else if err != nil {
   179  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   180  	}
   181  	snapcfg, ok := config[snapName]
   182  	if !ok {
   183  		return nil
   184  	}
   185  
   186  	err = st.Get("revision-config", &revisionConfig)
   187  	if err == state.ErrNoState {
   188  		revisionConfig = make(map[string]map[string]*json.RawMessage)
   189  	} else if err != nil {
   190  		return err
   191  	}
   192  	cfgs := revisionConfig[snapName]
   193  	if cfgs == nil {
   194  		cfgs = make(map[string]*json.RawMessage)
   195  	}
   196  	cfgs[rev.String()] = snapcfg
   197  	revisionConfig[snapName] = cfgs
   198  	st.Set("revision-config", revisionConfig)
   199  	return nil
   200  }
   201  
   202  // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName.
   203  // If no configuration exists for given revision it does nothing (no error).
   204  // The caller is responsible for locking the state.
   205  func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   206  	var config map[string]*json.RawMessage                    // snap => configuration
   207  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   208  
   209  	err := st.Get("revision-config", &revisionConfig)
   210  	if err == state.ErrNoState {
   211  		return nil
   212  	} else if err != nil {
   213  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   214  	}
   215  
   216  	err = st.Get("config", &config)
   217  	if err == state.ErrNoState {
   218  		config = make(map[string]*json.RawMessage)
   219  	} else if err != nil {
   220  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   221  	}
   222  
   223  	if cfg, ok := revisionConfig[snapName]; ok {
   224  		if revCfg, ok := cfg[rev.String()]; ok {
   225  			config[snapName] = revCfg
   226  			st.Set("config", config)
   227  		}
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  // DiscardRevisionConfig removes configuration snapshot of given snap/revision.
   234  // If no configuration exists for given revision it does nothing (no error).
   235  // The caller is responsible for locking the state.
   236  func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error {
   237  	var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration
   238  	err := st.Get("revision-config", &revisionConfig)
   239  	if err == state.ErrNoState {
   240  		return nil
   241  	} else if err != nil {
   242  		return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err)
   243  	}
   244  
   245  	if revCfgs, ok := revisionConfig[snapName]; ok {
   246  		delete(revCfgs, rev.String())
   247  		if len(revCfgs) == 0 {
   248  			delete(revisionConfig, snapName)
   249  		} else {
   250  			revisionConfig[snapName] = revCfgs
   251  		}
   252  		st.Set("revision-config", revisionConfig)
   253  	}
   254  	return nil
   255  }
   256  
   257  // DeleteSnapConfig removed configuration of given snap from the state.
   258  func DeleteSnapConfig(st *state.State, snapName string) error {
   259  	var config map[string]map[string]*json.RawMessage // snap => key => value
   260  
   261  	err := st.Get("config", &config)
   262  	if err == state.ErrNoState {
   263  		return nil
   264  	} else if err != nil {
   265  		return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)
   266  	}
   267  	if _, ok := config[snapName]; ok {
   268  		delete(config, snapName)
   269  		st.Set("config", config)
   270  	}
   271  	return nil
   272  }
   273  
   274  // Conf is an interface describing both state and transaction.
   275  type Conf interface {
   276  	Get(snapName, key string, result interface{}) error
   277  	Set(snapName, key string, value interface{}) error
   278  	Changes() []string
   279  	State() *state.State
   280  }
   281  
   282  // GetFeatureFlag returns the value of a given feature flag.
   283  func GetFeatureFlag(tr Conf, feature features.SnapdFeature) (bool, error) {
   284  	var isEnabled interface{}
   285  	snapName, confName := feature.ConfigOption()
   286  	if err := tr.Get(snapName, confName, &isEnabled); err != nil && !IsNoOption(err) {
   287  		return false, err
   288  	}
   289  	switch isEnabled {
   290  	case true, "true":
   291  		return true, nil
   292  	case false, "false":
   293  		return false, nil
   294  	case nil, "":
   295  		return feature.IsEnabledWhenUnset(), nil
   296  	}
   297  	return false, fmt.Errorf("%s can only be set to 'true' or 'false', got %q", feature, isEnabled)
   298  }