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

"ddev list" shows sites based on container, even if directory is removed: fix for #374 #444

Merged
merged 8 commits into from
Sep 12, 2017

Conversation

nmccrory
Copy link
Contributor

The Problem:

A reoccurring issue in ddev list was its failure to list sites whose directories had been removed but containers were still running. This pitfall in ddev list made it possible for containers (which were no longer useful) to heap up on a user's machine if they repeatedly removed sites using rm -r <dir> instead of ddev rm - which stops all containers associated with a particular site.

The Fix:

TLDR; check to see if a site's root directory exists. If it doesn't exist, store information about the site temporarily and list it with status "not found".
ddev list already had visibility into the ddev containers that were running on a machine, it simply would not include sites with missing directories because those sites would raise an error when attempting to initialize from a missing root site.Init(<root_dir>).

The Test:

  • Start a site
  • rm -r the site directory
  • ddev list
    • ddev list should list the site with "status: not found"
  • ddev rm <site_name>
  • ddev list then docker ps
    • the site and it's containers should no longer be visible

Related Issue Link(s):

#374

tempConfig.Name = siteContainer.Labels["com.ddev.site-name"]
tempSite := &LocalApp{tempConfig}
apps[platformType] = append(apps[platformType], tempSite)
break
Copy link
Contributor

Choose a reason for hiding this comment

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

This break feels kind of weird? what are we breaking from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This break stops the site variable from running .Init(<app_root>) a few lines below

@@ -197,3 +210,15 @@ func ddevContainersRunning() (bool, error) {
}
return false, nil
}

// exists returns whether the given file or directory exists or not
func exists(path string) (bool, error) {
Copy link
Contributor

@beeradb beeradb Aug 25, 2017

Choose a reason for hiding this comment

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

Please use the FileExists from our fileutils package: https://github.com/drud/ddev/blob/master/pkg/fileutil/files.go#L119

It's already imported here, so it should be an easy update.

Copy link
Contributor

@beeradb beeradb left a comment

Choose a reason for hiding this comment

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

I ran through this and it looks like it breaks some existing functionality.

Here's what I did

  • Started two sites. "drudio" and "ddevd7"
  • Run a ddev list (master) to ensure both were shown in the output
  • Deleted the "drudio" folder.
  • Run ddev list. Observed only a "ddevd7" site.
  • Compile this PR. Run ddev list. Observered only "drudio" listed. It showed the deleted folder "~/Projects/testing/drudio"
  • Checked out master again
  • Run ddev list on master. Observed only "ddevd7" listed.

tl;dr: When a site is deleted it appears that this PR only shows the deleted site. Or perhaps just the first site.

Console output:

drudapi/docker.nginx-php-fpm-hosting git:(master) ✗ » ddev list
2 local sites found.
NAME    TYPE       LOCATION                   URL                       STATUS
drudio  wordpress  ~/Projects/testing/drudio  http://drudio.ddev.local  running
ddevd7  drupal7    ~/Projects/ddev/ddevd7     http://ddevd7.ddev.local  running

DDEV ROUTER STATUS: running
drudapi/docker.nginx-php-fpm-hosting git:(master) ✗ » z ddev
drud/ddev git:(nginx-tag) » hub checkout https://github.com/drud/ddev/pull/444/files
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 12 (delta 6), reused 12 (delta 6), pack-reused 0
Unpacking objects: 100% (12/12), done.
From git://github.com/nmccrory/ddev
 * [new branch]      ddev-list-rmdir -> nmccrory/ddev-list-rmdir
Branch nmccrory-ddev-list-rmdir set up to track remote branch ddev-list-rmdir from nmccrory.
Switched to a new branch 'nmccrory-ddev-list-rmdir'
drud/ddev git:(nmccrory-ddev-list-rmdir) » rm -rf ~/Projects/testing/drudio
drud/ddev git:(nmccrory-ddev-list-rmdir) » ddev list
1 local site found.
NAME    TYPE     LOCATION                URL                       STATUS
ddevd7  drupal7  ~/Projects/ddev/ddevd7  http://ddevd7.ddev.local  running

DDEV ROUTER STATUS: running
drud/ddev git:(nmccrory-ddev-list-rmdir) » make darwin
building darwin from ./cmd/... ./pkg/...
drud/ddev git:(nmccrory-ddev-list-rmdir) » ddev list
1 local site found.
NAME    TYPE  LOCATION                   URL                       STATUS
drudio        ~/Projects/testing/drudio  http://drudio.ddev.local  web service unhealthy

DDEV ROUTER STATUS: running
drud/ddev git:(nmccrory-ddev-list-rmdir) » git checkout master
drud/ddev git:(master) » make darwin
building darwin from ./cmd/... ./pkg/...
drud/ddev git:(master) » ddev list
1 local site found.
NAME    TYPE     LOCATION                URL                       STATUS
ddevd7  drupal7  ~/Projects/ddev/ddevd7  http://ddevd7.ddev.local  running

@nmccrory
Copy link
Contributor Author

Thanks for testing this and giving your feedback @beeradb. I'll investigate into what's causing the issue (maybe the break statement as you mentioned above).

@rickmanelius
Copy link
Contributor

@nmccrory @beeradb I've followed the conversation in our team Slack channel. I believe the summarized approach is that ddev list should still show containers that persist even when the directory has been removed. However, there are two possible scenarios:

  1. The codebase is still intact, but the .ddev folder has been removed.
  2. The codebase is removed.

In both cases, we should probably note the issue in the status column and (at the bottom of the ddev list output) provide instructions how to either resolve OR remove the containers. Is that the summarized proposal? Right now, the PR summary mentions a status of "not found", but it doesn't indicate what instructions or helper text would be provided to help resolve the detached containers.

@rfay
Copy link
Member

rfay commented Aug 30, 2017

If you could just push a dummy commit into this it should pass surf-windows @nmccrory

@rfay
Copy link
Member

rfay commented Aug 31, 2017

@nmccrory could you comment on why this couldn't be handled in the status field, the same as what happens when a particular service hasn't come up or has been disabled? I know you explored that, just don't understand why it didn't work out.

1__default__bash_

@@ -46,7 +49,7 @@ var PluginMap = map[string]App{
}

// GetPluginApp will return an application of the type specified by pluginType
func GetPluginApp(pluginType string) (App, error) {
func GetPluginApp(pluginType string) (*LocalApp, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please consider this piece rather heavily. This works best now because it is the only way I could add the InitFromMissingDirectory(string, string) method on type LocalApp, without defining it as a required part of the App interface.

In the future, if we deal with other App types that are not LocalApp we would need to revert to returning the App generic. Right now however, the only possible return value is LocalApp. I'm not sure what our long term plans are or if there are situations where this may not be the case. Can definitely use feedback here...

Copy link
Member

Choose a reason for hiding this comment

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

I'd say don't do it and don't bother with adding the InitFromMissingDirectory(), as elsewhere.

@nmccrory
Copy link
Contributor Author

nmccrory commented Sep 6, 2017

This PR is ready for review.

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.

Minor changes requested - Just lose the extra function and do those 2 lines inline. Add some comments, etc.

  • You probably need to test the other commands. For example, ddev describe TestMainPkgWordpress doesn't work here, neither does ddev rm TestMainPkgWordpress or ddev stop TestMainPkgWordpress or ddev logs TestMainPkgWordpress, which people will want. Maybe should also be fixed at this time? My bet is they're pretty easy given what you've already learned. Probably it just means adding your general approach to those. Or maybe they can't be done. I'm ok if it's impossible, but it would be nice to see the general case solved.

if err != nil {
siteName := siteContainer.Labels["com.ddev.site-name"]
appType := siteContainer.Labels["com.ddev.app-type"]
site.InitFromMissingDirectory(siteName, appType)
Copy link
Member

@rfay rfay Sep 6, 2017

Choose a reason for hiding this comment

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

I don't think it's worth adding the function here. There are only 2 lines in the function, and it probably has a misleading name. So

  • just take those 2 lines and put them here.
  • Add a comment for this stanza saying what the situation is.
  • Also, make sure there aren't other reasons that the site.Init() might fail that would cause misbehavior in this stanza. (Like do we have a siteContainer OK?)

I'm just saying do this:

				if err != nil {
					siteName := siteContainer.Labels["com.ddev.site-name"]
					appType := siteContainer.Labels["com.ddev.app-type"]
					site.AppConfig.Name = siteName
					site.AppConfig.AppType = appType
					log.Printf("Failed to init %v err=%v", approot, err)
				}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately in this case, we can't access the site's AppConfig from this context. This has something to do with the way we are using the App interface, but regardless we need to brainstorm because I agree that it should not have to be its own function...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Brainstorm successful: we can work around this situation by casting site as type LocalApp. From there you can edit values in .AppConfig.

@@ -47,6 +47,7 @@ func (l *LocalApp) GetType() string {
func (l *LocalApp) Init(basePath string) error {
config, err := ddevapp.NewConfig(basePath, "")
if err != nil {
l.AppConfig = config
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment above this saying why we're saving it away, so somebody doesn't remove this someday.

@@ -46,7 +49,7 @@ var PluginMap = map[string]App{
}

// GetPluginApp will return an application of the type specified by pluginType
func GetPluginApp(pluginType string) (App, error) {
func GetPluginApp(pluginType string) (*LocalApp, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd say don't do it and don't bother with adding the InitFromMissingDirectory(), as elsewhere.

@nmccrory
Copy link
Contributor Author

@rfay Ready for review

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 think this is ready, please make the one fix I requested. I don't care about the variable name as long as it doesn't shadow "site".

apps[platformType] = append(apps[platformType], site)
if err != nil {
// Cast 'site' from type App to type LocalApp, so we can manually enter AppConfig values.
site := site.(*LocalApp)
Copy link
Member

Choose a reason for hiding this comment

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

The cast returns 2 args, the struct (if it exists) and "ok" that tells you whether it existed. I'd rather see

siteStruct, ok := site.(*LocalApp)
                                      if (ok) {
                                              siteStruct.AppConfig.Name = siteName
                                              siteStruct.AppConfig.AppType = appType
                                      }

There are two issues here:

  1. Catch the case where the cast doesn't work (unlikely, but defensive code is good)
  2. Don't change the meaning of the variable "site" in the middle of the code. It was one thing, and changing it to something completely different could throw somebody working on this in the future. Never forget that this has a life after you work on it.

@nmccrory
Copy link
Contributor Author

Requested changes have been made. Thanks for your feedback, Randy! @rfay

// Cast 'site' from type App to type LocalApp, so we can manually enter AppConfig values.
siteStruct, ok := site.(*LocalApp)
if !ok {
log.Fatalf("Failed to init %v err=%v", approot, 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 don't mind the path you took here, but the error message makes no sense. Please make it make sense. It needs to be about the failure of the cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this context I feel we would want this error to be the one presented. It is more important than the failure to cast because casting only happens when we can't find a config in the app's root directory. If we can't resolve the missing directory issue by casting, we want to return the overall error that the cast is nested in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think presenting the failure to cast message on the line before the log.Fatalf would be a good compromise.

Copy link
Contributor

@beeradb beeradb left a comment

Choose a reason for hiding this comment

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

This looks really close. I think we need one more conditional to check for the absence of a ddev config as well. My original testing method was just to move my .ddev folder after starting, and no errors were reported.

If we can get that other check in place I think we'll be 👍

@@ -266,6 +268,10 @@ func (l *LocalApp) SiteStatus() string {
var siteStatus string
services := map[string]string{"web": "", "db": ""}

if !fileutil.FileExists(l.AppRoot()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This only notifies a user if the entire AppRoot is missing. The case where just a .ddev/config.yaml file is missing is also an issue. It would be great to add another check here (and error return) if the config can't be found.

@nmccrory
Copy link
Contributor Author

@beeradb - your feedback has been incorporated. ddev list now changes the site status if the site is missing a .ddev/config.yml file!

@rfay
Copy link
Member

rfay commented Sep 12, 2017

@beeradb if you're good with this we should let him get it in because the followup work uses some of these things.

Copy link
Contributor

@beeradb beeradb left a comment

Choose a reason for hiding this comment

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

This worked as expected for me:

drud/ddev ‹minikube› git:(nmccrory-ddev-list-rmdir) » ddev list
1 local site found.
NAME         TYPE     LOCATION                        URL                            STATUS
d7kickstart  drupal7  ~/Projects/testing/d7kickstart  http://d7kickstart.ddev.local  .ddev/config.yml missing

I would note that a full path would probably be preferable under status, but I don't see a need to hold this up for that. It might be nice to add the full path to the output in one of the follow-ups to this, though.

Thanks so much for the work on this. Here's a baloon🎈

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

Successfully merging this pull request may close these issues.

None yet

4 participants