Skip to content

Commit

Permalink
Split out the parsing function to get paths/values from a list of str… (
Browse files Browse the repository at this point in the history
#138)

* 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
  • Loading branch information
cmmarslender authored Jul 30, 2024
1 parent 27bb8ad commit 07e84b6
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 12 deletions.
80 changes: 68 additions & 12 deletions pkg/config/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
}
}
}

Expand Down
70 changes: 70 additions & 0 deletions pkg/config/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
}

0 comments on commit 07e84b6

Please sign in to comment.