From 0127264cd8817dd9944f023305bc222d322889d2 Mon Sep 17 00:00:00 2001 From: sina Date: Mon, 5 Aug 2024 20:22:01 +0200 Subject: [PATCH] Fix parameters camelCase support in loadEnvVars for environment variable parsing (#588) * Added mapenv JSON tag function and unit tests for server load balancer strategy configuration * Added `transformEnvVariable` function to format environment variable names based on JSON tags * Modified `loadEnvVars` to use the new `transformEnvVariable` function * Added `generateTagMapping` to create mappings from struct fields' JSON tags * Added unit test `ServerLoadBalancerStrategyOverwrite` to verify that the server load balancer strategy is set and loaded correctly from environment variables * Added unit test `pluginDefaultPolicyOverwrite` to verify that the plugin default policy is set and loaded correctly from environment variables * Added `initializeConfig` helper function to simplify configuration setup for tests * Updated `TestLoadEnvVariables` to include a scenario for server load balancer strategy overwrite * Updated comment for `generateTagMapping` function to clarify tag case handling --- config/config.go | 62 ++++++++++++++++++++++++++++++++++++++++--- config/config_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 7401cc69..50785d8a 100644 --- a/config/config.go +++ b/config/config.go @@ -335,9 +335,40 @@ func (c *Config) LoadPluginEnvVars(ctx context.Context) *gerr.GatewayDError { } func loadEnvVars() *env.Env { - return env.Provider(EnvPrefix, ".", func(env string) string { - return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(env, EnvPrefix)), "_", ".") - }) + return env.Provider(EnvPrefix, ".", transformEnvVariable) +} + +// transformEnvVariable transforms the environment variable name to a format based on JSON tags. +func transformEnvVariable(envVar string) string { + structs := []interface{}{ + &API{}, + &Logger{}, + &Pool{}, + &Proxy{}, + &Server{}, + &Metrics{}, + &PluginConfig{}, + } + tagMapping := make(map[string]string) + generateTagMapping(structs, tagMapping) + + lowerEnvVar := strings.ToLower(strings.TrimPrefix(envVar, EnvPrefix)) + parts := strings.Split(lowerEnvVar, "_") + + var transformedParts strings.Builder + + for i, part := range parts { + if i > 0 { + transformedParts.WriteString(".") + } + if mappedValue, exists := tagMapping[part]; exists { + transformedParts.WriteString(mappedValue) + } else { + transformedParts.WriteString(part) + } + } + + return transformedParts.String() } // LoadGlobalConfigFile loads the plugin configuration file. @@ -594,3 +625,28 @@ func (c *Config) ValidateGlobalConfig(ctx context.Context) *gerr.GatewayDError { return nil } + +// generateTagMapping generates a map of JSON tags to lower case json tags. +func generateTagMapping(structs []interface{}, tagMapping map[string]string) { + for _, s := range structs { + structValue := reflect.ValueOf(s).Elem() + structType := structValue.Type() + + for i := range structValue.NumField() { + field := structType.Field(i) + fieldValue := structValue.Field(i) + + // Handle nested structs + if field.Type.Kind() == reflect.Struct { + generateTagMapping([]interface{}{fieldValue.Addr().Interface()}, tagMapping) + } + + jsonTag := field.Tag.Get("json") + if jsonTag != "" { + tagMapping[strings.ToLower(jsonTag)] = jsonTag + } else { + tagMapping[strings.ToLower(field.Name)] = strings.ToLower(field.Name) + } + } + } +} diff --git a/config/config_test.go b/config/config_test.go index 3f4d1b81..e1211f2f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,6 +2,8 @@ package config import ( "context" + "fmt" + "strings" "testing" "github.com/knadh/koanf" @@ -142,3 +144,62 @@ func TestMergeGlobalConfig(t *testing.T) { // The log level should now be debug. assert.Equal(t, "debug", config.Global.Loggers[Default].Level) } + +// initializeConfig initializes the configuration with the given context. +// It returns a pointer to the Config struct. If configuration initialization fails, +// the test will fail with an error message. +func initializeConfig(ctx context.Context, t *testing.T) *Config { + t.Helper() + config := NewConfig(ctx, Config{ + GlobalConfigFile: parentDir + GlobalConfigFilename, + PluginConfigFile: parentDir + PluginsConfigFilename, + }) + err := config.InitConfig(ctx) + require.Nil(t, err) + return config +} + +// serverLoadBalancerStrategyOverwrite sets the environment variable for server nested configuration +// and verifies that the configuration is correctly loaded with the expected value. +func ServerLoadBalancerStrategyOverwrite(t *testing.T) { + t.Helper() + ctx := context.Background() + // Convert to uppercase + upperDefaultGroup := strings.ToUpper(Default) + + // Format environment variable name + envVarName := fmt.Sprintf("GATEWAYD_SERVERS_%s_LOADBALANCER_STRATEGY", upperDefaultGroup) + + // Set the environment variable + t.Setenv(envVarName, "test") + config := initializeConfig(ctx, t) + assert.Equal(t, "test", config.Global.Servers[Default].LoadBalancer.Strategy) +} + +// pluginDefaultPolicyOverwrite sets the environment variable for plugin configuration +// and verifies that the configuration is correctly loaded with the expected value. +func pluginDefaultPolicyOverwrite(t *testing.T) { + t.Helper() + ctx := context.Background() + + // Set the environment variable + t.Setenv("GATEWAYD_DEFAULTPOLICY", "test") + config := initializeConfig(ctx, t) + assert.Equal(t, "test", config.Plugin.DefaultPolicy) +} + +// TestLoadEnvVariables runs a suite of tests to verify that environment variables are correctly +// loaded into the configuration. Each test scenario sets a specific environment variable and +// checks if the configuration reflects the expected value. +func TestLoadEnvVariables(t *testing.T) { + scenarios := map[string]func(t *testing.T){ + "serverLoadBalancerStrategyOverwrite": ServerLoadBalancerStrategyOverwrite, + "pluginLocalPathOverwrite": pluginDefaultPolicyOverwrite, + } + + for scenario, fn := range scenarios { + t.Run(scenario, func(t *testing.T) { + fn(t) + }) + } +}