github.com/safing/portbase@v0.19.5/config/set.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/tevino/abool"
     8  )
     9  
    10  var (
    11  	// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
    12  	ErrInvalidJSON = errors.New("json string invalid")
    13  
    14  	// ErrInvalidOptionType is returned by SetConfigOption and SetDefaultConfigOption if given an unsupported option type.
    15  	ErrInvalidOptionType = errors.New("invalid option value type")
    16  
    17  	validityFlag     = abool.NewBool(true)
    18  	validityFlagLock sync.RWMutex
    19  )
    20  
    21  // getValidityFlag returns a flag that signifies if the configuration has been changed. This flag must not be changed, only read.
    22  func getValidityFlag() *abool.AtomicBool {
    23  	validityFlagLock.RLock()
    24  	defer validityFlagLock.RUnlock()
    25  	return validityFlag
    26  }
    27  
    28  // signalChanges marks the configs validtityFlag as dirty and eventually
    29  // triggers a config change event.
    30  func signalChanges() {
    31  	// reset validity flag
    32  	validityFlagLock.Lock()
    33  	validityFlag.SetTo(false)
    34  	validityFlag = abool.NewBool(true)
    35  	validityFlagLock.Unlock()
    36  
    37  	module.TriggerEvent(ChangeEvent, nil)
    38  }
    39  
    40  // ValidateConfig validates the given configuration and returns all validation
    41  // errors as well as whether the given configuration contains unknown keys.
    42  func ValidateConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool, containsUnknown bool) {
    43  	// RLock the options because we are not adding or removing
    44  	// options from the registration but rather only checking the
    45  	// options value which is guarded by the option's lock itself.
    46  	optionsLock.RLock()
    47  	defer optionsLock.RUnlock()
    48  
    49  	var checked int
    50  	for key, option := range options {
    51  		newValue, ok := newValues[key]
    52  		if ok {
    53  			checked++
    54  
    55  			func() {
    56  				option.Lock()
    57  				defer option.Unlock()
    58  
    59  				newValue = migrateValue(option, newValue)
    60  				_, err := validateValue(option, newValue)
    61  				if err != nil {
    62  					validationErrors = append(validationErrors, err)
    63  				}
    64  
    65  				if option.RequiresRestart {
    66  					requiresRestart = true
    67  				}
    68  			}()
    69  		}
    70  	}
    71  
    72  	return validationErrors, requiresRestart, checked < len(newValues)
    73  }
    74  
    75  // ReplaceConfig sets the (prioritized) user defined config.
    76  func ReplaceConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) {
    77  	// RLock the options because we are not adding or removing
    78  	// options from the registration but rather only update the
    79  	// options value which is guarded by the option's lock itself.
    80  	optionsLock.RLock()
    81  	defer optionsLock.RUnlock()
    82  
    83  	for key, option := range options {
    84  		newValue, ok := newValues[key]
    85  
    86  		func() {
    87  			option.Lock()
    88  			defer option.Unlock()
    89  
    90  			option.activeValue = nil
    91  			if ok {
    92  				newValue = migrateValue(option, newValue)
    93  				valueCache, err := validateValue(option, newValue)
    94  				if err == nil {
    95  					option.activeValue = valueCache
    96  				} else {
    97  					validationErrors = append(validationErrors, err)
    98  				}
    99  			}
   100  			handleOptionUpdate(option, true)
   101  
   102  			if option.RequiresRestart {
   103  				requiresRestart = true
   104  			}
   105  		}()
   106  	}
   107  
   108  	signalChanges()
   109  
   110  	return validationErrors, requiresRestart
   111  }
   112  
   113  // ReplaceDefaultConfig sets the (fallback) default config.
   114  func ReplaceDefaultConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) {
   115  	// RLock the options because we are not adding or removing
   116  	// options from the registration but rather only update the
   117  	// options value which is guarded by the option's lock itself.
   118  	optionsLock.RLock()
   119  	defer optionsLock.RUnlock()
   120  
   121  	for key, option := range options {
   122  		newValue, ok := newValues[key]
   123  
   124  		func() {
   125  			option.Lock()
   126  			defer option.Unlock()
   127  
   128  			option.activeDefaultValue = nil
   129  			if ok {
   130  				newValue = migrateValue(option, newValue)
   131  				valueCache, err := validateValue(option, newValue)
   132  				if err == nil {
   133  					option.activeDefaultValue = valueCache
   134  				} else {
   135  					validationErrors = append(validationErrors, err)
   136  				}
   137  			}
   138  			handleOptionUpdate(option, true)
   139  
   140  			if option.RequiresRestart {
   141  				requiresRestart = true
   142  			}
   143  		}()
   144  	}
   145  
   146  	signalChanges()
   147  
   148  	return validationErrors, requiresRestart
   149  }
   150  
   151  // SetConfigOption sets a single value in the (prioritized) user defined config.
   152  func SetConfigOption(key string, value any) error {
   153  	return setConfigOption(key, value, true)
   154  }
   155  
   156  func setConfigOption(key string, value any, push bool) (err error) {
   157  	option, err := GetOption(key)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	option.Lock()
   163  	if value == nil {
   164  		option.activeValue = nil
   165  	} else {
   166  		value = migrateValue(option, value)
   167  		valueCache, vErr := validateValue(option, value)
   168  		if vErr == nil {
   169  			option.activeValue = valueCache
   170  		} else {
   171  			err = vErr
   172  		}
   173  	}
   174  
   175  	// Add the "restart pending" annotation if the settings requires a restart.
   176  	if option.RequiresRestart {
   177  		option.setAnnotation(RestartPendingAnnotation, true)
   178  	}
   179  
   180  	handleOptionUpdate(option, push)
   181  	option.Unlock()
   182  
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	// finalize change, activate triggers
   188  	signalChanges()
   189  
   190  	return SaveConfig()
   191  }
   192  
   193  // SetDefaultConfigOption sets a single value in the (fallback) default config.
   194  func SetDefaultConfigOption(key string, value interface{}) error {
   195  	return setDefaultConfigOption(key, value, true)
   196  }
   197  
   198  func setDefaultConfigOption(key string, value interface{}, push bool) (err error) {
   199  	option, err := GetOption(key)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	option.Lock()
   205  	if value == nil {
   206  		option.activeDefaultValue = nil
   207  	} else {
   208  		value = migrateValue(option, value)
   209  		valueCache, vErr := validateValue(option, value)
   210  		if vErr == nil {
   211  			option.activeDefaultValue = valueCache
   212  		} else {
   213  			err = vErr
   214  		}
   215  	}
   216  
   217  	// Add the "restart pending" annotation if the settings requires a restart.
   218  	if option.RequiresRestart {
   219  		option.setAnnotation(RestartPendingAnnotation, true)
   220  	}
   221  
   222  	handleOptionUpdate(option, push)
   223  	option.Unlock()
   224  
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	// finalize change, activate triggers
   230  	signalChanges()
   231  
   232  	// Do not save the configuration, as it only saves the active values, not the
   233  	// active default value.
   234  	return nil
   235  }