Skip to content

Commit

Permalink
Fix parameters camelCase support in loadEnvVars for environment varia…
Browse files Browse the repository at this point in the history
…ble parsing (gatewayd-io#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
  • Loading branch information
sinadarbouy authored Aug 5, 2024
1 parent 359981c commit 0127264
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 3 deletions.
62 changes: 59 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
}
}
}
61 changes: 61 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"context"
"fmt"
"strings"
"testing"

"github.com/knadh/koanf"
Expand Down Expand Up @@ -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)
})
}
}

0 comments on commit 0127264

Please sign in to comment.