From 07e84b66f1a01fabb9dd206236372a4e43379271 Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Tue, 30 Jul 2024 17:45:13 -0500 Subject: [PATCH] =?UTF-8?q?Split=20out=20the=20parsing=20function=20to=20g?= =?UTF-8?q?et=20paths/values=20from=20a=20list=20of=20str=E2=80=A6=20(#138?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Split out the parsing function to get paths/values from a list of strings * Add function to just parse paths from strings (assumed to not have =value in the string). Adds tests, and fixes a bug discovered with tests introduced by allowing both . and __ for separators --- pkg/config/env.go | 80 +++++++++++++++++++++++++++++++++++------- pkg/config/env_test.go | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 12 deletions(-) diff --git a/pkg/config/env.go b/pkg/config/env.go index d5d1c85..34ad038 100644 --- a/pkg/config/env.go +++ b/pkg/config/env.go @@ -21,7 +21,7 @@ import ( func (c *ChiaConfig) FillValuesFromEnvironment() error { valuesToUpdate := getAllChiaVars() for _, pAndV := range valuesToUpdate { - err := c.SetFieldByPath(pAndV.path, pAndV.value) + err := c.SetFieldByPath(pAndV.Path, pAndV.Value) if err != nil { return err } @@ -30,32 +30,88 @@ func (c *ChiaConfig) FillValuesFromEnvironment() error { return nil } -type pathAndValue struct { - path []string - value string +// PathAndValue is a struct to represent the path minus any prefix and the value to set +type PathAndValue struct { + Path []string + Value string } -func getAllChiaVars() map[string]pathAndValue { +func getAllChiaVars() map[string]PathAndValue { // Most shells don't allow `.` in env names, but docker will and its easier to visualize the `.`, so support both // `.` and `__` as valid path segment separators // chia.full_node.port // chia__full_node__port - separators := []string{".", "__"} envVars := os.Environ() - finalVars := map[string]pathAndValue{} + return ParsePathsAndValuesFromStrings(envVars, true) +} + +// ParsePathsAndValuesFromStrings takes a list of strings and parses out paths and values +// requirePrefix determines if the string must be prefixed with chia. or chia__ +// This is typically used when parsing env vars, not so much with flags +func ParsePathsAndValuesFromStrings(pathStrings []string, requirePrefix bool) map[string]PathAndValue { + separators := []string{".", "__"} + finalVars := map[string]PathAndValue{} for _, sep := range separators { prefix := fmt.Sprintf("chia%s", sep) - for _, env := range envVars { - if strings.HasPrefix(env, prefix) { + for _, env := range pathStrings { + if requirePrefix { + if strings.HasPrefix(env, prefix) { + pair := strings.SplitN(env, "=", 2) + if len(pair) == 2 { + finalVars[pair[0][len(prefix):]] = PathAndValue{ + Path: strings.Split(pair[0], sep)[1:], // This is the Path in the config to the Value to edit minus the "chia" prefix + Value: pair[1], + } + } + } + } else { pair := strings.SplitN(env, "=", 2) if len(pair) == 2 { - finalVars[pair[0][len(prefix):]] = pathAndValue{ - path: strings.Split(pair[0], sep)[1:], // This is the path in the config to the value to edit minus the "chia" prefix - value: pair[1], + // Ensure that we don't overwrite something that is already in the finalVars + // UNLESS the path is longer than the value already there + // Shorter paths can happen if not requiring a prefix and we added the full path + // in the first iteration, but actually uses a separator later in the list + path := strings.Split(pair[0], sep) + if _, set := finalVars[pair[0]]; !set || (set && len(path) > len(finalVars[pair[0]].Path)) { + finalVars[pair[0]] = PathAndValue{ + Path: path, + Value: pair[1], + } } } } + + } + } + + return finalVars +} + +// ParsePathsFromStrings takes a list of strings and parses out paths +// requirePrefix determines if the string must be prefixed with chia. or chia__ +// This is typically used when parsing env vars, not so much with flags +func ParsePathsFromStrings(pathStrings []string, requirePrefix bool) map[string][]string { + separators := []string{".", "__"} + finalVars := map[string][]string{} + + for _, sep := range separators { + prefix := fmt.Sprintf("chia%s", sep) + for _, env := range pathStrings { + if requirePrefix { + if strings.HasPrefix(env, prefix) { + finalVars[env[len(prefix):]] = strings.Split(env, sep)[1:] + } + } else { + // Ensure that we don't overwrite something that is already in the finalVars + // UNLESS the path is longer than the value already there + // Shorter paths can happen if not requiring a prefix and we added the full path + // in the first iteration, but actually uses a separator later in the list + path := strings.Split(env, sep) + if _, set := finalVars[env]; !set || (set && len(path) > len(finalVars[env])) { + finalVars[env] = path + } + } } } diff --git a/pkg/config/env_test.go b/pkg/config/env_test.go index 3ecfa06..369df34 100644 --- a/pkg/config/env_test.go +++ b/pkg/config/env_test.go @@ -67,3 +67,73 @@ func TestChiaConfig_FillValuesFromEnvironment(t *testing.T) { assert.Equal(t, defaultConfig.SelectedNetwork, "unittestnet") assert.Equal(t, defaultConfig.Logging.LogLevel, "INFO") } + +func TestChiaConfig_ParsePathsAndValuesFromStrings(t *testing.T) { + // A mix of paths with and without prefixes with both separators + strings := []string{ + "chia.full_node.port=8444", + "chia__full_node__db_sync=auto", + "full_node.db_readers=4", + "full_node__database_path=testing.db", + } + + // Test that both strings with prefixes are matched with requirePrefix + result := config.ParsePathsAndValuesFromStrings(strings, true) + assert.Len(t, result, 2) + assert.Contains(t, result, "full_node.port") + assert.Equal(t, []string{"full_node", "port"}, result["full_node.port"].Path) + assert.Equal(t, "8444", result["full_node.port"].Value) + assert.Contains(t, result, "full_node__db_sync") + assert.Equal(t, []string{"full_node", "db_sync"}, result["full_node__db_sync"].Path) + assert.Equal(t, "auto", result["full_node__db_sync"].Value) + assert.NotContains(t, result, "full_node.db_readers") + assert.NotContains(t, result, "full_node__database_path") + + // Test that both strings with prefixes are matched with requirePrefix + result = config.ParsePathsAndValuesFromStrings(strings, false) + assert.Len(t, result, 4) // 4 because it won't strip chia prefix if its found + assert.Contains(t, result, "chia.full_node.port") + assert.Equal(t, []string{"chia", "full_node", "port"}, result["chia.full_node.port"].Path) + assert.Equal(t, "8444", result["chia.full_node.port"].Value) + assert.Contains(t, result, "chia__full_node__db_sync") + assert.Equal(t, []string{"chia", "full_node", "db_sync"}, result["chia__full_node__db_sync"].Path) + assert.Equal(t, "auto", result["chia__full_node__db_sync"].Value) + assert.Contains(t, result, "full_node.db_readers") + assert.Equal(t, []string{"full_node", "db_readers"}, result["full_node.db_readers"].Path) + assert.Equal(t, "4", result["full_node.db_readers"].Value) + assert.Contains(t, result, "full_node__database_path") + assert.Equal(t, []string{"full_node", "database_path"}, result["full_node__database_path"].Path) + assert.Equal(t, "testing.db", result["full_node__database_path"].Value) +} + +func TestChiaConfig_ParsePathsFromStrings(t *testing.T) { + // A mix of paths with and without prefixes with both separators + strings := []string{ + "chia.full_node.port", + "chia__full_node__db_sync", + "full_node.db_readers", + "full_node__database_path", + } + + // Test that both strings with prefixes are matched with requirePrefix + result := config.ParsePathsFromStrings(strings, true) + assert.Len(t, result, 2) + assert.Contains(t, result, "full_node.port") + assert.Equal(t, []string{"full_node", "port"}, result["full_node.port"]) + assert.Contains(t, result, "full_node__db_sync") + assert.Equal(t, []string{"full_node", "db_sync"}, result["full_node__db_sync"]) + assert.NotContains(t, result, "full_node.db_readers") + assert.NotContains(t, result, "full_node__database_path") + + // Test that both strings with prefixes are matched with requirePrefix + result = config.ParsePathsFromStrings(strings, false) + assert.Len(t, result, 4) // 4 because it won't strip chia prefix if its found + assert.Contains(t, result, "chia.full_node.port") + assert.Equal(t, []string{"chia", "full_node", "port"}, result["chia.full_node.port"]) + assert.Contains(t, result, "chia__full_node__db_sync") + assert.Equal(t, []string{"chia", "full_node", "db_sync"}, result["chia__full_node__db_sync"]) + assert.Contains(t, result, "full_node.db_readers") + assert.Equal(t, []string{"full_node", "db_readers"}, result["full_node.db_readers"]) + assert.Contains(t, result, "full_node__database_path") + assert.Equal(t, []string{"full_node", "database_path"}, result["full_node__database_path"]) +}