mirror of
https://github.com/safing/portbase
synced 2025-09-02 02:29:59 +00:00
Add value migrations to config options
This commit is contained in:
parent
83b709526e
commit
3afd5009bf
4 changed files with 28 additions and 2 deletions
|
@ -66,6 +66,9 @@ type PossibleValue struct {
|
||||||
// Format: <vendor/package>:<scope>:<identifier> //.
|
// Format: <vendor/package>:<scope>:<identifier> //.
|
||||||
type Annotations map[string]interface{}
|
type Annotations map[string]interface{}
|
||||||
|
|
||||||
|
// MigrationFunc is a function that migrates a config option value.
|
||||||
|
type MigrationFunc func(option *Option, value any) any
|
||||||
|
|
||||||
// Well known annotations defined by this package.
|
// Well known annotations defined by this package.
|
||||||
const (
|
const (
|
||||||
// DisplayHintAnnotation provides a hint for the user
|
// DisplayHintAnnotation provides a hint for the user
|
||||||
|
@ -259,6 +262,9 @@ type Option struct {
|
||||||
// Annotations is considered mutable and setting/reading annotation keys
|
// Annotations is considered mutable and setting/reading annotation keys
|
||||||
// must be performed while the option is locked.
|
// must be performed while the option is locked.
|
||||||
Annotations Annotations
|
Annotations Annotations
|
||||||
|
// Migrations holds migration functions that are given the raw option value
|
||||||
|
// before any validation is run. The returned value is then used.
|
||||||
|
Migrations []MigrationFunc `json:"-"`
|
||||||
|
|
||||||
activeValue *valueCache // runtime value (loaded from config file or set by user)
|
activeValue *valueCache // runtime value (loaded from config file or set by user)
|
||||||
activeDefaultValue *valueCache // runtime default value (may be set internally)
|
activeDefaultValue *valueCache // runtime default value (may be set internally)
|
||||||
|
@ -361,6 +367,7 @@ func (option *Option) ValidateValue(value any) error {
|
||||||
option.Lock()
|
option.Lock()
|
||||||
defer option.Unlock()
|
defer option.Unlock()
|
||||||
|
|
||||||
|
value = migrateValue(option, value)
|
||||||
if _, err := validateValue(option, value); err != nil {
|
if _, err := validateValue(option, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ optionsLoop:
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// migrate value
|
||||||
|
configValue = migrateValue(option, configValue)
|
||||||
// validate value
|
// validate value
|
||||||
valueCache, err := validateValue(option, configValue)
|
valueCache, err := validateValue(option, configValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -56,6 +56,7 @@ func ValidateConfig(newValues map[string]interface{}) (validationErrors []*Valid
|
||||||
option.Lock()
|
option.Lock()
|
||||||
defer option.Unlock()
|
defer option.Unlock()
|
||||||
|
|
||||||
|
newValue = migrateValue(option, newValue)
|
||||||
_, err := validateValue(option, newValue)
|
_, err := validateValue(option, newValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrors = append(validationErrors, err)
|
validationErrors = append(validationErrors, err)
|
||||||
|
@ -88,6 +89,7 @@ func ReplaceConfig(newValues map[string]interface{}) (validationErrors []*Valida
|
||||||
|
|
||||||
option.activeValue = nil
|
option.activeValue = nil
|
||||||
if ok {
|
if ok {
|
||||||
|
newValue = migrateValue(option, newValue)
|
||||||
valueCache, err := validateValue(option, newValue)
|
valueCache, err := validateValue(option, newValue)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
option.activeValue = valueCache
|
option.activeValue = valueCache
|
||||||
|
@ -125,6 +127,7 @@ func ReplaceDefaultConfig(newValues map[string]interface{}) (validationErrors []
|
||||||
|
|
||||||
option.activeDefaultValue = nil
|
option.activeDefaultValue = nil
|
||||||
if ok {
|
if ok {
|
||||||
|
newValue = migrateValue(option, newValue)
|
||||||
valueCache, err := validateValue(option, newValue)
|
valueCache, err := validateValue(option, newValue)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
option.activeDefaultValue = valueCache
|
option.activeDefaultValue = valueCache
|
||||||
|
@ -160,6 +163,7 @@ func setConfigOption(key string, value any, push bool) (err error) {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
option.activeValue = nil
|
option.activeValue = nil
|
||||||
} else {
|
} else {
|
||||||
|
value = migrateValue(option, value)
|
||||||
valueCache, vErr := validateValue(option, value)
|
valueCache, vErr := validateValue(option, value)
|
||||||
if vErr == nil {
|
if vErr == nil {
|
||||||
option.activeValue = valueCache
|
option.activeValue = valueCache
|
||||||
|
@ -201,6 +205,7 @@ func setDefaultConfigOption(key string, value interface{}, push bool) (err error
|
||||||
if value == nil {
|
if value == nil {
|
||||||
option.activeDefaultValue = nil
|
option.activeDefaultValue = nil
|
||||||
} else {
|
} else {
|
||||||
|
value = migrateValue(option, value)
|
||||||
valueCache, vErr := validateValue(option, value)
|
valueCache, vErr := validateValue(option, value)
|
||||||
if vErr == nil {
|
if vErr == nil {
|
||||||
option.activeDefaultValue = valueCache
|
option.activeDefaultValue = valueCache
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type valueCache struct {
|
type valueCache struct {
|
||||||
|
@ -64,6 +66,18 @@ func isAllowedPossibleValue(opt *Option, value interface{}) error {
|
||||||
return errors.New("value is not allowed")
|
return errors.New("value is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrateValue runs all value migrations.
|
||||||
|
func migrateValue(option *Option, value any) any {
|
||||||
|
for _, migration := range option.Migrations {
|
||||||
|
newValue := migration(option, value)
|
||||||
|
if newValue != value {
|
||||||
|
log.Debugf("config: migrated %s value from %v to %v", option.Key, value, newValue)
|
||||||
|
}
|
||||||
|
value = newValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
// validateValue ensures that value matches the expected type of option.
|
// validateValue ensures that value matches the expected type of option.
|
||||||
// It does not create a copy of the value!
|
// It does not create a copy of the value!
|
||||||
func validateValue(option *Option, value interface{}) (*valueCache, *ValidationError) { //nolint:gocyclo
|
func validateValue(option *Option, value interface{}) (*valueCache, *ValidationError) { //nolint:gocyclo
|
||||||
|
@ -76,8 +90,6 @@ func validateValue(option *Option, value interface{}) (*valueCache, *ValidationE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reflect.TypeOf(value).ConvertibleTo(reflect.TypeOf(""))
|
|
||||||
|
|
||||||
var validated *valueCache
|
var validated *valueCache
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
Loading…
Add table
Reference in a new issue