8000 Correctly match environment variables to YAML-inlined structs in configuration by evanebb · Pull Request #4639 · distribution/distribution · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

evanebb
Copy link
Contributor
@evanebb evanebb commented May 20, 2025

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 and REGISTRY_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.

@github-actions github-actions bot added the area/config Related to registry config label May 20, 2025
Copy link
Member
@milosgajdos milosgajdos left a 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>
@evanebb
Copy link
Contributor Author
evanebb commented May 23, 2025

@milosgajdos I've added a test to the configuration package in b65ccca.

@milosgajdos
Copy link
Member

PTAL @thaJeztah

Copy link
Member
@thaJeztah thaJeztah left a 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)
				}
			}
		}

Comment on lines +212 to +217
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 {
Copy link
Member

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;

Suggested change
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++ {
Copy link
Member

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] {
Copy link
Member

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;

Suggested change
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))
Copy link
Member

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? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/config Related to registry config
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0