Skip to content
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

refactor: Use project_list.yaml as source of truth, fixes #5918 #5926

Merged
merged 7 commits into from Apr 3, 2024

Conversation

GuySartorelli
Copy link
Collaborator

The Issue

Users have been confused by having two copies of the project list, and haven't known which to update if they need to manually update the list.

How This PR Solves The Issue

  1. If there's no project_list.yaml file and the global_config.yaml file contains a project list (i.e. if someone is upgrading from an older version of DDEV), copy the list into project_list.yaml
    • This is a departure from the previous behaviour which would copy the list from global_config.yaml if there was any difference between the two lists
  2. Remove ProjectList from the GlobalConfig type
    • This means the next time global_config.yaml is written, the project list will be removed from that file.

Note that I have done this in two commits to show why some of the changes are necessary. I'll add PR comments to help clarify this.

Manual Testing Instructions

  1. Ensure you have at least one project in your project list prior to checking out this PR
  2. Remove project_list.yaml if you have it
  3. Run ddev list - it should list the project(s) correctly, regardless of what state they were in
  4. Confirm that you now have a project_list.yaml file
  5. Run any command that will result in global config being written (e.g. if your last_started_version has changed, run ddev start in a project)
  6. Confirm that global_config.yaml no longer has a list pf projects

Automated Testing Overview

No automated tests need to change - they were all already relying on project_list.yaml.

Related Issue Link(s)

Release/Deployment Notes

This shouldn't affect anyone other than reducing confusion.

@GuySartorelli GuySartorelli requested a review from a team as a code owner March 4, 2024 07:12
Copy link

github-actions bot commented Mar 4, 2024

Comment on lines 312 to 315
// Remove empty project list from global config
// In a future release it will be removed from the struct but
// for now it's needed to make upgrading easier.
cfgbytes = []byte(strings.Replace(string(cfgbytes), "project_info: {}", "", 1))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to ensure we don't have an empty project list in global config. It felt quite hacky, but was better than keeping an empty list in the file which would lead to further confusion.

Comment on lines 78 to 86
ProjectList map[string]*ProjectInfo `yaml:"project_info"`
// ProjectList is retained to make upgrading easier and should be removed after a few releases
ProjectList map[string]*ProjectInfo `yaml:"project_info"`
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't like keeping this in the type since it's no longer intended to be kept there, but this is how we'd do this if we don't have an intermediary legacy type.

Comment on lines 81 to 87
// Old versions of DDEV stored the project list in the global config.
// For ease of upgrade, we need to be able to extract that data and move it to
// the new project_list.yaml
// In a future release, remove this.
type LegacyGlobalConfig struct {
ProjectList map[string]*ProjectInfo `yaml:"project_info"`
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lets us remove ProjectList from the GlobalConfig type. It means any time the global config is saved, the project list (if there was one in the file) is removed without having to do something hackish like an above comment points out.

It also gives us a clean way to marshal the list from the YAML file - if we removed ProjectList from GlobalConfig but didn't add something like this, we'd have to parse the file manually which would be even worse.

Comment on lines 251 to 255
// Get the legacy project list if there is one
err = yaml.Unmarshal(source, &ddevLegacyGlobalConfig)
if err != nil {
return err
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this in ReadGlobalConfig because this is the best point to guarantee that:

  1. We haven't overwritten the config file yet
  2. We haven't run ReadProjectList yet

Comment on lines 494 to 488
// If someone upgrades from an earlier version, the global config may hold the project list.
if ddevLegacyGlobalConfig.ProjectList != nil && len(ddevLegacyGlobalConfig.ProjectList) > 0 {
DdevProjectList = ddevLegacyGlobalConfig.ProjectList
err := WriteProjectList(DdevProjectList)
// Whether there's an error or nil here we want to return
return err
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to check legacy config if the file doesn't exist. If the file exists, it's the source of truth even if it's empty.

Comment on lines +506 to +495
// Whether there's an error or nil here we want to return
return err
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We either created a new file here, or we had an unexpected error. Either way there's no projects to read so we can skip the "read the projects list from the file" logic below.

@rfay rfay marked this pull request as draft March 5, 2024 21:53
@rfay
Copy link
Member

rfay commented Mar 5, 2024

Moved to draft until tests start working

@rfay
Copy link
Member

rfay commented Mar 20, 2024

This is essential for DDEV v1.23.0 IMO, thanks for all your work on it, and we know you don't have unlimited time for it.

@rfay rfay force-pushed the 20240304_GuySartorelli_project-list branch from d3676e3 to ac05207 Compare March 20, 2024 16:38
@rfay
Copy link
Member

rfay commented Mar 20, 2024

Rebased

@GuySartorelli GuySartorelli force-pushed the 20240304_GuySartorelli_project-list branch from ac05207 to 7e3f55e Compare March 21, 2024 21:05
@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented Mar 21, 2024

Rebased again 'cause GitHub said it was still out of date with Master.

The error can be easily reproduced by running

GOTEST_SHORT=1 make test  TESTARGS="-run TestConfigDatabaseVersion"
    config_test.go:521: 
        	Error Trace:	/home/gsartorelli/dump/temp/ddev/cmd/ddev/cmd/config_test.go:521
        	Error:      	Received unexpected error:
        	            	exit status 1
        	Test:       	TestConfigDatabaseVersion
        	Messages:   	Failed to run ddev config --database mariadb:10.11: this project root /home/gsartorelli/tmp/ddevtest/TestConfigDatabaseVersion3259693668 already contains a project named TestConfigDatabaseVersion. You may want to remove the existing project with "ddev stop --unlist TestConfigDatabaseVersion"

If we keep this part of the code from ReadProjectList() (along with keeping ProjectList in the GlobalConfig struct), the tests pass:

if !reflect.DeepEqual(DdevGlobalConfig.ProjectList, DdevProjectList) {
	DdevProjectList = DdevGlobalConfig.ProjectList
	err := WriteProjectList(DdevProjectList)
	if err != nil {
		return err
	}
}

In other words, something in the tests is either writing directly to the project list in global config, or writing the wrong thing directly to the new project list. The two get out of sync, so it grabs everything from the global config and discards whatever's in the new list. Or maybe that's a red herring, I'm really not sure.

I think the problem might lie in RemoveProjectInfo somehow? Or maybe its in the logic of the TestConfigDatabaseVersion test itself? I'm not quite sure, and could use a hand tracing this.

@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented Mar 21, 2024

I tried adding

err = globalconfig.RemoveProjectInfo(t.Name())
assert.NoError(err)

into the for _, dbTypeVersion := range versionsToTest { loop just after running app.stop() but that didn't work.

Appending strconv.Itoa(rand.Int()) after t.Name() for the project name in the ddev config command within the loop in the test also doesn't work. It still complains about an existing project named "TestConfigDatabaseVersion". It seems that it's not the name of the project being created that matters.

@GuySartorelli
Copy link
Collaborator Author

Changing the name of the first project that's created in the test changes the name of the project that is included in the error message. So it's clearly explicitly complaining that that first-created project exists and points to the same directory that the config call in the loop is trying to create a project in.

I just for the life of me don't seem to be able to find a way to remove that project correctly before creating the next one.

@GuySartorelli GuySartorelli force-pushed the 20240304_GuySartorelli_project-list branch from 7e3f55e to e34353a Compare March 21, 2024 22:14
@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented Mar 21, 2024

Okay, I have something that passes that test locally now.
Fingers crossed.

I'm pretty sure the change I made to the test is okay, but please do check that the test is still testing what it needs to test.

@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented Mar 22, 2024

auth-ssh_test.go is failing, but only in one CI run... any guesses as to whether that's intermittent and would clear up if it was re-run? I'm guessing it probably would, since that seems unrelated to these changes?

@rfay
Copy link
Member

rfay commented Mar 22, 2024

I re-ran the nginx-fpm test. It's always good to understand what's going on with a fail, but you're always welcome to restart.

@GuySartorelli
Copy link
Collaborator Author

I only have the ability to restart the buildkite builds - I don't seem to have the right permissions for restart gha ones.

@rfay
Copy link
Member

rfay commented Mar 22, 2024

Sent you invitation. Hopefully that will give you the power.

@GuySartorelli
Copy link
Collaborator Author

Aha! Yes, thank you, I have the option now for next time 💙

@GuySartorelli GuySartorelli marked this pull request as ready for review March 22, 2024 04:41
@GuySartorelli
Copy link
Collaborator Author

All green! 🥳

@tyler36
Copy link
Collaborator

tyler36 commented Mar 22, 2024

Great work @GuySartorelli!

@GuySartorelli GuySartorelli requested a review from rfay March 24, 2024 20:06
Copy link
Member

@rfay rfay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this is fine, and will run through a quick test, but would love to discuss/consider just doing

	ProjectList                      map[string]*ProjectInfo     `yaml:"project_info,omitempty"`

and make sure that it gets emptied after processing.

@@ -514,6 +514,8 @@ func TestConfigDatabaseVersion(t *testing.T) {
// add a database into the config and nothing else
for _, dbTypeVersion := range versionsToTest {
_ = app.Stop(true, false)
_, err := exec.RunHostCommand(DdevBin, "delete", "-Oy", t.Name())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this does any harm, but what did you hit here that made you do this? Is it related to this PR? Did some old project get left laying around?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #5926 (comment) and all my comments in this PR since then :p

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please revisit this without the delete here to see if you can find out what the real problem is? It appears that you're having to patch something that is caused by something entirely different. This kind of adaptation of a test is really suspicious, as you know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like this code is just trying to call ddev config inside a directory which already has a ddev project created and that this should have been failing previously

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except... it doesn't seem to have failed except in this PR. Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. The bit of code I mentioned in #5926 (comment) is being removed in this PR - if we keep that there we don't get the error, because there's no projects in the global config list and there are projects in the new projects list. That discrepancy combined with the mentioned code results in clearing the new project list, so as far as DDEV is concerned there's no project there.

This PR removes that code, which means DDEV is now fully aware that there is a project there, causing the error.

I'm not sure if this was something introduced when I added the new project list file, or since then, or maybe I'm just wrong about the cause here? But that's what it looks like to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chasing it. I'm often unable to follow your references, too many context switches. If you could re-mention things instead of linking to other comments maybe I'll do better. It sounds like you're saying that another test has left a project around instead of doing an app.Stop(true, false) or something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not another test - this same test. It's running ddev config in a loop in the same directory. It is doing app.Stop(true, false) but that seems to be insufficient to remove the project from the project list file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If app.Stop(true,false) isn't removing the project from the project list file, it's an introduced bug. Every single test would fail if that were common. Please step through in debugger and figure out what's going on. Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know - I'll introduce a new test that explicitly tests app.Stop(true,false) and debug it. If such a test already existed I wouldn't have come up with this solution since it would have been clear what I found was a red herring.
I'll try to find time this week to do this.

@@ -514,6 +514,8 @@ func TestConfigDatabaseVersion(t *testing.T) {
// add a database into the config and nothing else
for _, dbTypeVersion := range versionsToTest {
_ = app.Stop(true, false)
_, err := exec.RunHostCommand(DdevBin, "delete", "-Oy", t.Name())
assert.NoError(err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been leaning more toward require() lately, which stops the test. Less time chasing irrelevant things. It's not necessary to accept this, I'm just commenting.

Suggested change
assert.NoError(err)
require.NoError(t, err)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth making a push to change all of them at some stage and get CI to catch that.
I'll change this if you want me to use the omitempty discussed in all the other comments - but if you're happy leaving that as is then I'll leave this as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not important. You're just such an important contributor that I'm doing a bit of coaching. Nothing wrong with what's here.

@@ -33,6 +32,8 @@ var (
DdevGlobalConfig GlobalConfig
// DdevProjectList is the list of all existing DDEV projects
DdevProjectList map[string]*ProjectInfo
// Old versions of DDEV stored the project list in the global config.
ddevLegacyGlobalConfig LegacyGlobalConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting technique, but couldn't we just allow the old ProjectList in current config, but not write it out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that's what the omitempty does in #5926 (review)?

If so, I have no problem doing that. I just wasn't aware that was an option.
When I tried doing it without omitempty it ended up with an empty map in the yaml file which is obviously misleading - for anyone who looks at that file it'd look like DDEV has incorrectly said there's no projects!

// the new project_list.yaml
// In a future release, remove this.
type LegacyGlobalConfig struct {
ProjectList map[string]*ProjectInfo `yaml:"project_info"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, tempted to allow the ProjectList in current global config, just not write it (just make sure it's empty and omitempty

@rfay
Copy link
Member

rfay commented Mar 24, 2024

I did manually test and look through, and it works perfectly as is.

@GuySartorelli
Copy link
Collaborator Author

Let me know if you want the omitempty thing or not - it's unclear from your comments whether it's fine as is or whether that's blocking merging.

@rfay
Copy link
Member

rfay commented Mar 24, 2024

Let me know if you want the omitempty thing or not

I would really appreciate it if you could experiment with that approach. I think it might be cleaner.

@GuySartorelli
Copy link
Collaborator Author

Let me know if you want the omitempty thing or not

I would really appreciate it if you could experiment with that approach. I think it might be cleaner.

Sweet, I'll try make some time this week to get that done.

@rfay rfay requested a review from stasadev March 25, 2024 14:11
@rfay rfay force-pushed the 20240304_GuySartorelli_project-list branch from e34353a to 9785509 Compare March 28, 2024 17:09
@rfay
Copy link
Member

rfay commented Mar 28, 2024

Rebased, fixed merge conflicts.

@rfay
Copy link
Member

rfay commented Apr 1, 2024

@stasadev or I will experiment with the omitempty if you don't have time for it and that's OK with you. Want to get this in this week. Thanks!

@GuySartorelli
Copy link
Collaborator Author

Sweet, yes please do.

@stasadev stasadev force-pushed the 20240304_GuySartorelli_project-list branch from 9785509 to 46aafe9 Compare April 2, 2024 19:54
@GuySartorelli GuySartorelli requested a review from a team as a code owner April 2, 2024 19:54
@stasadev
Copy link
Member

stasadev commented Apr 2, 2024

I think the problem might lie in RemoveProjectInfo somehow?

@GuySartorelli, your idea was in the right direction, the project list was not updated, when it was needed.

That's because we had globalconfig.WriteGlobalConfig() inside app.Stop() - this statement is responsible for updating the project list.

I rewrote RemoveProjectInfo and removed globalconfig.WriteGlobalConfig() from app.Stop() - this is not needed anymore.

I'm sure this is fine, and will run through a quick test, but would love to discuss/consider just doing omitempty

Done, running tests.

@stasadev stasadev requested a review from rfay April 3, 2024 11:09
Copy link
Member

@rfay rfay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this again and manually tested, and it looks great to me! Thanks so much! 🙏🏼

@rfay rfay merged commit bdae26f into ddev:master Apr 3, 2024
23 checks passed
@GuySartorelli GuySartorelli deleted the 20240304_GuySartorelli_project-list branch April 3, 2024 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants