common/helper: add a helper to rename a configuration setting

This commit is contained in:
Vincent Bernat
2024-08-21 19:15:35 +02:00
parent 11a27dbc04
commit c948b9779e
4 changed files with 85 additions and 79 deletions

View File

@@ -106,6 +106,39 @@ func DefaultValuesUnmarshallerHook[Configuration any](defaultConfiguration Confi
} }
} }
// RenameKeyUnmarshallerHook move a configuration setting from one place to another.
func RenameKeyUnmarshallerHook[Configuration any](zeroConfiguration Configuration, fromLabel, toLabel string) mapstructure.DecodeHookFunc {
return func(from, to reflect.Value) (interface{}, error) {
if from.Kind() != reflect.Map || from.IsNil() || to.Type() != reflect.TypeOf(zeroConfiguration) {
return from.Interface(), nil
}
// country-database → geo-database
var fromKey, toKey *reflect.Value
fromMap := from.MapKeys()
for i, k := range fromMap {
k = ElemOrIdentity(k)
if k.Kind() != reflect.String {
return from.Interface(), nil
}
if MapStructureMatchName(k.String(), fromLabel) {
fromKey = &fromMap[i]
} else if MapStructureMatchName(k.String(), toLabel) {
toKey = &fromMap[i]
}
}
if fromKey != nil && toKey != nil {
return nil, fmt.Errorf("cannot have both %q and %q", fromKey.String(), toKey.String())
}
if fromKey != nil {
from.SetMapIndex(reflect.ValueOf(toLabel), from.MapIndex(*fromKey))
from.SetMapIndex(*fromKey, reflect.Value{})
}
return from.Interface(), nil
}
}
// ParametrizedConfigurationUnmarshallerHook will help decode a configuration // ParametrizedConfigurationUnmarshallerHook will help decode a configuration
// structure parametrized by a type by selecting the appropriate concrete type // structure parametrized by a type by selecting the appropriate concrete type
// depending on the type contained in the source. We have two configuration // depending on the type contained in the source. We have two configuration

View File

@@ -119,6 +119,54 @@ func TestDefaultValuesConfig(t *testing.T) {
}) })
} }
func TestRenameConfig(t *testing.T) {
type Configuration struct {
UnchangedLabel string
NewLabel string
}
RegisterMapstructureUnmarshallerHook(RenameKeyUnmarshallerHook(Configuration{}, "OldLabel", "NewLabel"))
TestConfigurationDecode(t, ConfigurationDecodeCases{
{
Description: "no rename needed",
Initial: func() interface{} { return Configuration{} },
Configuration: func() interface{} {
return gin.H{
"unchanged-label": "hello",
"new-label": "bye",
}
},
Expected: Configuration{
UnchangedLabel: "hello",
NewLabel: "bye",
},
}, {
Description: "rename needed",
Initial: func() interface{} { return Configuration{} },
Configuration: func() interface{} {
return gin.H{
"unchanged-label": "hello",
"old-label": "bye",
}
},
Expected: Configuration{
UnchangedLabel: "hello",
NewLabel: "bye",
},
}, {
Description: "conflicts",
Initial: func() interface{} { return Configuration{} },
Configuration: func() interface{} {
return gin.H{
"unchanged-label": "hello",
"old-label": "bye",
"new-label": "whatt?",
}
},
Error: true,
},
})
}
func TestParametrizedConfig(t *testing.T) { func TestParametrizedConfig(t *testing.T) {
type InnerConfigurationType1 struct { type InnerConfigurationType1 struct {
CC string CC string

View File

@@ -4,8 +4,6 @@
package metadata package metadata
import ( import (
"fmt"
"reflect"
"time" "time"
"akvorado/common/helpers" "akvorado/common/helpers"
@@ -13,8 +11,6 @@ import (
"akvorado/inlet/metadata/provider/gnmi" "akvorado/inlet/metadata/provider/gnmi"
"akvorado/inlet/metadata/provider/snmp" "akvorado/inlet/metadata/provider/snmp"
"akvorado/inlet/metadata/provider/static" "akvorado/inlet/metadata/provider/static"
"github.com/mitchellh/mapstructure"
) )
// Configuration describes the configuration for the metadata client // Configuration describes the configuration for the metadata client
@@ -49,40 +45,6 @@ func DefaultConfiguration() Configuration {
} }
} }
// ConfigurationUnmarshallerHook renames "provider" to "providers".
func ConfigurationUnmarshallerHook() mapstructure.DecodeHookFunc {
return func(from, to reflect.Value) (interface{}, error) {
if from.Kind() != reflect.Map || from.IsNil() || to.Type() != reflect.TypeOf(Configuration{}) {
return from.Interface(), nil
}
// provider → providers
{
var providerKey, providersKey *reflect.Value
fromKeys := from.MapKeys()
for i, k := range fromKeys {
k = helpers.ElemOrIdentity(k)
if k.Kind() != reflect.String {
return from.Interface(), nil
}
if helpers.MapStructureMatchName(k.String(), "Provider") {
providerKey = &fromKeys[i]
} else if helpers.MapStructureMatchName(k.String(), "Providers") {
providersKey = &fromKeys[i]
}
}
if providersKey != nil && providerKey != nil {
return nil, fmt.Errorf("cannot have both %q and %q", providerKey.String(), providersKey.String())
}
if providerKey != nil {
from.SetMapIndex(reflect.ValueOf("providers"), from.MapIndex(*providerKey))
from.SetMapIndex(*providerKey, reflect.Value{})
}
}
return from.Interface(), nil
}
}
// ProviderConfiguration represents the configuration for a metadata provider. // ProviderConfiguration represents the configuration for a metadata provider.
type ProviderConfiguration struct { type ProviderConfiguration struct {
// Config is the actual configuration for the provider. // Config is the actual configuration for the provider.
@@ -101,7 +63,8 @@ var providers = map[string](func() provider.Configuration){
} }
func init() { func init() {
helpers.RegisterMapstructureUnmarshallerHook(ConfigurationUnmarshallerHook()) helpers.RegisterMapstructureUnmarshallerHook(
helpers.RenameKeyUnmarshallerHook(Configuration{}, "Provider", "Providers"))
helpers.RegisterMapstructureUnmarshallerHook( helpers.RegisterMapstructureUnmarshallerHook(
helpers.ParametrizedConfigurationUnmarshallerHook(ProviderConfiguration{}, providers)) helpers.ParametrizedConfigurationUnmarshallerHook(ProviderConfiguration{}, providers))
} }

View File

@@ -4,12 +4,7 @@
package geoip package geoip
import ( import (
"fmt"
"reflect"
"akvorado/common/helpers" "akvorado/common/helpers"
"github.com/mitchellh/mapstructure"
) )
// Configuration describes the configuration for the GeoIP component. // Configuration describes the configuration for the GeoIP component.
@@ -29,40 +24,7 @@ func DefaultConfiguration() Configuration {
return Configuration{} return Configuration{}
} }
// ConfigurationUnmarshallerHook normalize GeoIP configuration:
// - replace country-database by geo-database
func ConfigurationUnmarshallerHook() mapstructure.DecodeHookFunc {
return func(from, to reflect.Value) (interface{}, error) {
if from.Kind() != reflect.Map || from.IsNil() || to.Type() != reflect.TypeOf(Configuration{}) {
return from.Interface(), nil
}
// country-database → geo-database
var countryKey, geoKey *reflect.Value
fromMap := from.MapKeys()
for i, k := range fromMap {
k = helpers.ElemOrIdentity(k)
if k.Kind() != reflect.String {
return from.Interface(), nil
}
if helpers.MapStructureMatchName(k.String(), "CountryDatabase") {
countryKey = &fromMap[i]
} else if helpers.MapStructureMatchName(k.String(), "GeoDatabase") {
geoKey = &fromMap[i]
}
}
if countryKey != nil && geoKey != nil {
return nil, fmt.Errorf("cannot have both %q and %q", countryKey.String(), geoKey.String())
}
if countryKey != nil {
from.SetMapIndex(reflect.ValueOf("geo-database"), from.MapIndex(*countryKey))
from.SetMapIndex(*countryKey, reflect.Value{})
}
return from.Interface(), nil
}
}
func init() { func init() {
helpers.RegisterMapstructureUnmarshallerHook(ConfigurationUnmarshallerHook()) helpers.RegisterMapstructureUnmarshallerHook(
helpers.RenameKeyUnmarshallerHook(Configuration{}, "CountryDatabase", "GeoDatabase"))
} }