-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Correctly match environment variables to YAML-inlined structs in configuration #4639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: evanebb <git@evanus.nl>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im fine this this change. One thing I'm wondering about is, if we need to add a test to config package which shows this works. Otherwise LGTM
Signed-off-by: evanebb <git@evanus.nl>
@milosgajdos I've added a test to the configuration package in b65ccca. |
PTAL @thaJeztah |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some suggestions; here's a quick write-up of them combined if you want to copy/paste;
// For fields i
8000
nlined in the YAML configuration file, the environment variables also need to be inlined
// Example struct tag for inlined field: `yaml:",inline"`
_, yamlOpts, _ := strings.Cut(sf.Tag.Get("yaml"), ",")
if yamlOpts == "inline" && sf.Type.Kind() == reflect.Struct {
// Inlined struct, check whether the env variable corresponds to a field inside this struct
// Maps could also be inlined, but since we don't need it right now it is not supported
inlined := v.Field(i)
for j := range inlined.NumField() {
if strings.EqualFold(inlined.Type().Field(j).Name, path[0]) {
return p.overwriteFields(inlined, fullpath, path, payload)
}
}
}
yamlTag := sf.Tag.Get("yaml") | ||
split := strings.Split(yamlTag, ",") | ||
// Skip the first entry, which is the field name | ||
isInlined := slices.Contains(split[1:], "inline") | ||
|
||
if isInlined && sf.Type.Kind() == reflect.Struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably use strings.Cut
for this;
yamlTag := sf.Tag.Get("yaml") | |
split := strings.Split(yamlTag, ",") | |
// Skip the first entry, which is the field name | |
isInlined := slices.Contains(split[1:], "inline") | |
if isInlined && sf.Type.Kind() == reflect.Struct { | |
_, yamlOpts, _ := strings.Cut(sf.Tag.Get("yaml"), ",") | |
if yamlOpts == "inline" && sf.Type.Kind() == reflect.Struct { |
// Inlined struct, check whether the env variable corresponds to a field inside this struct | ||
// Maps could also be inlined, but since we don't need it right now it is not supported | ||
inlined := v.Field(i) | ||
for j := 0; j < inlined.NumField(); j++ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current versions of Go allow simplifying this;
for j := range inlined.NumField() {
// Maps could also be inlined, but since we don't need it right now it is not supported | ||
inlined := v.Field(i) | ||
for j := 0; j < inlined.NumField(); j++ { | ||
if strings.ToUpper(inlined.Type().Field(j).Name) == path[0] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to use Equalfold
for these, as it doesn't allocate;
if strings.ToUpper(inlined.Type().Field(j).Name) == path[0] { | |
if strings.EqualFold(inlined.Type().Field(j).Name, path[0]) { |
} | ||
} | ||
} | ||
|
||
upper := strings.ToUpper(sf.Name) | ||
if _, present := byUpperCase[upper]; present { | ||
panic(fmt.Sprintf("field name collision in configuration object: %s", sf.Name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR, but why are we using a panic()
here? This function has an error
return; can't we just return an error? 🤔
This is a quick and dirty fix for #4621. The underlying issue is as mentioned in #4621 (comment).
This fixes it by checking for the
inline
directive inside YAML struct tags when parsing environment variables into the configuration struct. If it finds this directive, it will check if the environment variable corresponds to a field inside the inlined struct and overwrite that instead.In order to keep things backward compatible for people using the workaround, the code will still accept an environment variable with the full inlined struct field name in it.
For example, for the Redis configuration, which has an inline struct field named Options, both
REGISTRY_REDIS_OPTIONS_ADDRS
andREGISTRY_REDIS_ADDRS
will point to the same struct field.Feel free to close this if you'd like to see it handled differently; tying custom env variable parsing logic to the
yaml
struct tag is pretty janky, but it is a simple fix. A rewrite of the entire configuration package (maybe just using a third-party package that handles all of this) would be nice, but that would warrant some more discussion I'd guess.