Conversation
|
Download the artifacts for this pull request:
See Testing a PR. |
|
Please take a look through the places where you changed logic and spend some time improving the OP to explain the underlying reason. A casual look through shows that config merging, which has a long and difficult history (and may not have enough testing) had significant logic changes. Look through at things like that and look at the reasoning. In places where the logic has changed, this PR really needs to explain why. BTW, I really appreciate limiting the scope to config with existing behavior. That was the right thing to do. |
|
Hi @rfay , |
cc28886 to
96d60f0
Compare
|
Rebased |
There was a problem hiding this comment.
I haven't fully studied this, but gave it to Claude for a review.
The general approach is sound, but there are behavioral regressions where test data was changed to accept the new (broken) behavior rather than preserving the old behavior.
Is it legit that the testdata was changed?
Do we need more testing for override_config?
Critical Issues
1. override_config: true is silently broken
This is the most serious issue. The old mergeAdditionalConfigIntoApp in config_merge.go explicitly handled OverrideConfig:
if newConfig.OverrideConfig {
err = app.LoadConfigYamlFile(configPath) // full replacement
} else {
err = mergo.Merge(app, newConfig, mergo.WithAppendSlice, mergo.WithOverride)
}The new code in RecursiveMerge always deep-merges/appends, and never checks OverrideConfig. So a config.override.yaml with override_config: true is silently ignored — it used to allow resetting arrays to [] or overriding boolean values that merging could not touch.
The test expected data was changed to accept this regression. In fulloverridetest/expected/.ddev/config.yaml, additional_hostnames used to be [overrideone, overridetwo, z-one] (override replaced base) but is now [baseone, basetwo, basethree, overrideone, overridetwo, z-one] (all merged together). The feature is documented in templates.go but no longer works.
2. Duplicate env vars no longer deduplicated
The old mergeAdditionalConfigIntoApp called EnvToUniqueEnv and SliceToUniqueSlice after merging, ensuring last-write-wins for duplicate web_environment entries like ORIG_TO_BE_OVERWRITTEN=orig + ORIG_TO_BE_OVERWRITTEN=last.
The new code has no such deduplication. The test expected data for envtest was changed to add ORIG_TO_BE_OVERWRITTEN=orig as a duplicate (where previously =last would win). The comment # This one should win still exists in the source config but the expected output now shows both values — and containers will see the first-defined value, not the intended override.
3. LoadConfigYamlFile semantics changed without clear justification
The original contract was "load one specific file." The new version auto-globs for config.*.yaml in the same directory. This is a hidden behavior change for callers.
To accommodate this, TestPkgConfigDatabaseDBVersion was changed from:
err = app.LoadConfigYamlFile(configFile) // test that this specific file's DB version is correctto:
_, err = app.ReadConfig(false) // different function, different semanticsThis masks that LoadConfigYamlFile changed behavior.
Moderate Issues
4. Global package-level state in pkg/settings
The package has top-level config and factory vars initialized in init(). This is a global singleton, which can cause test pollution. Tests in config_test.go correctly restore state via defer, but anyone using settings.Set(...) or settings.Init() globally could affect other test runs.
5. Tests use assert instead of require
All new tests in pkg/settings/config_test.go and pkg/settings/viper_test.go use assert, which is inconsistent with DDEV project standards. Test failures won't stop execution on the first assertion failure.
6. Double file reading in ReadConfig
The new ReadConfig reads each config file twice: once via os.ReadFile for validateHookYAML, and then again through Viper. Minor inefficiency but worth noting.
What Works Well
- The
floatToStringHookcorrectly solves the8.0→"8"truncation problem - The
RecursiveMergelogic is clean and the intent is sound - Viper not calling
AutomaticEnvis the right choice and is tested TestViperUnmarshalDoesNotPickUpEnvis a good guard against future regressions- The float preservation tests are thorough
Bottom Line
The two test data changes in envtest/expected and fulloverridetest/expected are the key red flags — they indicate the tests were adjusted to match broken behavior rather than fixing the implementation. The override_config: true feature and env var deduplication need to be preserved before this can be merged. The rest of the approach is reasonable.
|
Hi @rfay , I worked on the concerns from your comment. I pushed the changes. This is a summary:
About the concern 4th, I have to check in the future. |
|
@rfay I added the patch about the global state, and now it's stateless. Please check again. |
|
The errors are unrelated to my branch, @rfay |
4afe202 to
fed2213
Compare
|
Rebased and resolved conflicts. I created two commits so that we could easily split the vendor files in another PR. |
|
Thanks @stasadev . Please notice, that the error in the test is unrelated to my branch. |
| out, err = exec.RunHostCommand("mysql", "-uroot", "-proot", "--database=db", "--host=127.0.0.1", "--port="+hostDBPort, "-e", "SHOW TABLES;") | ||
| require.NoError(t, err, "failed host-side mysql command, output='%v'", out) | ||
| // Make sure that the db port is configured and accessible from host. | ||
| // We don't want to require mysql client on host, so just check the port |
There was a problem hiding this comment.
There's no reason to change this for this PR. Please make sure in a hefty PR that you change only things relevant to the PR.
There's an old issue about not requiring mysql on host for testing, it would be OK to address with a PR for that, but not here.
rfay
left a comment
There was a problem hiding this comment.
Thanks for the work on this. I still have only casually looked.
@stasadev is working elsewhere on a massive change that will affect all vendor stuff. He brought that in in
If you haven't already rebased to address that, it's a good time.
He also has developed a technique of separating vendor changes into a single commit to make everything more reviewable, it's a good idea.
| php_version: 5.6 | ||
| webserver_type: apache-fpm | ||
| use_dns_when_possible: false | ||
| override_config: false |
There was a problem hiding this comment.
For this important PR, it's critical to show that all the existing tests work without change. If you actually have a reason for changing something, it needs significant comment.
There was a problem hiding this comment.
Hi, yes you are right. It was temporarily added to test a slice merging issue during the configuration refactor. Once the underlying merging logic was properly fixed and override_config was properly discarded, this line became an unnecessary leftover and has been removed.
It's already rebased. I'm going to convert #8237 into bumping the |
fed2213 to
ed9c695
Compare
|
Rebased. |
|
I checked the last error, it's again non related to this branch. |
|
@agviu the podman rootless test fails quite a lot. I added you as collaborator on this repo. If you accept the invite, does that give you privs to restart the test? (I see you already have privileges for buildkite tests) |
|
Thanks, @rfay. I accepted the invitation, but I don't see any new option or icon about restarting the test. Could you let me know where is usually this option in the GIthub's interface? |
|
@agviu I updated your role to |
|
@rfay , I still cannot see any option for restarting the test... |
|
Restarting jobs requires write access https://github.com/orgs/community/discussions/189061 |
rfay
left a comment
There was a problem hiding this comment.
Thanks for all the careful work and followup on this.
-
It seems that this adds a 3rd yaml dependency, which isn't great, but at least they've moved to the supported one.
-
I would probably prefer if pkg/settings/config.go had the name settings_config.go, (or something different) since in the IDE it's already too easy to open the wrong one. But can live with it as is, especially if you think that's important
-
I liked Mergo :)
-
viper.go's RecursiveMerge seems like the most risk but tests seem to validate it.
-
Since tests have not been changed this should be pretty safe.
-
What about the concerns that viper is outdated and losing favor? I think that conversation probably should have been finished before this PR got all this energy, but I think it's going to be OK. Is viper a friend of yours that you have a lot of experience with @agviu ?
-
The problem with map management in viper automatically using lowercase seems like a bit of risk, and has a good workaround. Does it seem like a risk? Mixed case could appear other places than project_list.yaml couldn't it?
-
FUTURE TODO: We really would like to eliminate the copied code for elements that are in both global and project, and also allow more global config (hooks would be a candidate for example)
I approve. It may be some time before Stas has time to fully look still. Hi from the train near Glenwood Springs, Colorado, where I've been traveling back home from Chicago for about 24 hours.
…r DB port accessibility check" This reverts commit a057e58.
Concurrent read/writes to outputOnceCache triggered "DATA RACE" errors during CI tests. A sync.Mutex was added to util.outputOnce to ensure safe goroutine memory access. [skip buildkite]
a4142b0 to
a39c61a
Compare
|
HI @rfay , thanks for your review and comments.
Finally, 24 hours by train seems like a very long trip. I hope you arrived safe back at home! |
|
Thanks! Sorry for the filename rename request. Seems kind of silly, but it's appreciated. I'm glad to know that Viper is familiar ground for you. That experience brings more safety to this PR. |
stasadev
left a comment
There was a problem hiding this comment.
Looks good to me, thank you!
I have a few small nitpicks about the functions.
| } | ||
|
|
||
| // LoadProjectConfigFromContents loads a main project config and merges optional overrides from pre-read bytes. | ||
| func LoadProjectConfigFromContents(mainPath string, mainContent []byte, overrides []OverrideConfig, target any) error { |
There was a problem hiding this comment.
mainPath is passed to LoadProjectConfigFromContents but never used - it operates entirely on mainContent.
Was this intentional (reserved for something in the future), or can we drop it from the signature?
| return nil | ||
| } | ||
|
|
||
| // EnvToUniqueEnv() makes sure that only the last occurrence of an env (NAME=val or bare NAME) |
There was a problem hiding this comment.
It looks like the EnvToUniqueEnv function is the only one in this file.
I suggest moving it next to util.SliceToUniqueSlice (pkg/util/utils.go), along with its corresponding test TestEnvToUniqueEnv.
…Contents and add unit tests
|
Hi @stasadev , |
|
Wow, what an amazing step. |

The Issue
Use viper completely for config.
How This PR Solves The Issue
Introduces a new
pkg/settingspackage that uses Viper as a unified YAML configuration loader. This replaces the manualos.ReadFile+yaml.Unmarshalloops inconfig.goandglobal_config.gowith a structured, testable abstraction..ddev/config.yamland~/.ddev/global_config.yamlloading.AutomaticEnv,BindEnv) are intentionally excluded and deferred to a future PR to prevent regressions and maintain stability.project_list.yamlloading remains untouched. An earlier attempt in this branch to parse the project list via Viper was reverted because Viper's default behavior of lowercasing all map keys corrupted mixed-case project names.NewConfigProvider) without the need for distinct "clean" profiles.What changed
New:
pkg/settings/config.go— Defines the isolatedConfigProviderandProviderFactoryinterfaces, plus high-level package functions likeLoadGlobalConfigandLoadProjectConfig.viper.go— Implements the interfaces using Viper.floatToStringHookthat preserves decimal precision when YAML values like8.0are unmarshaled into string fields (Viper/mapstructure would otherwise truncate this to"8").RecursiveMerge): Historically, DDEV relied onyaml.Unmarshal's native behavior of incrementally deep-merging arrays and maps when called multiple times on the sameDdevAppstruct. When migrating to Viper, we lost this implicit struct-merging behavior, as Viper tends to replace complex collections rather than deep-merging them. To maintain 100% backward compatibility with how users expectconfig.*.yamloverrides to behave, we now parse the main config and overrides into independentmap[string]anyrepresentations, use a customRecursiveMergefunction to explicitly and safely deep-merge the maps and append slices, and finally decode the merged result into the target struct.config_test.go/viper_test.go— Comprehensive test suite covering config loading, factory isolation, float precision, and struct unmarshaling.TestLoadProjectConfigexplicitly tests thatRecursiveMergecorrectly deep-merges map configurations (likeweb_environment) and properly appends slices (likehooks) during override parsing.Modified:
pkg/ddevapp/config.goReadConfigandLoadConfigYamlFilenow delegate entirely tosettings.LoadProjectConfig(app.ConfigPath, overrides, app)instead of executing manual YAML unmarshaling loops. Overrides are processed securely viaRecursiveMerge.hooksis now explicitly done before passing files to the settings loader to ensure safe parsing.mergeAdditionalConfigIntoAppas its logic is now safely encapsulated within thesettingspackage.Modified:
pkg/globalconfig/global_config.goReadGlobalConfignow delegates entirely tosettings.LoadGlobalConfig(globalConfigFile, &DdevGlobalConfig). This function is intentionally kept separate fromLoadProjectConfigbecause it parses a single file with no dynamic overrides, and therefore avoids the complexity of theRecursiveMergelogic.Manual Testing Instructions
Use
ddevnormally, everything should work as always. Verify that project overrides (likeconfig.override.yaml) correctly append slice data (e.g.,hooks) and merge map data (e.g.,web_environment) without replacing the original collections.Automated Testing Overview
masterbranch vs this branch and the output was the same. In particular, the testsTestConfigValidate,TestPHPConfig, andTestTimezoneConfigfail identically inmasteras they do here.Release/Deployment Notes
Nothing special.