Skip to content

Commit

Permalink
fix: solve mutagen failure, restart should check for new version, che…
Browse files Browse the repository at this point in the history
…ck composer.json in container, fixes #5089 (#5106)
  • Loading branch information
rfay committed Jul 10, 2023
1 parent 8c5e0b0 commit 7294710
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 50 deletions.
12 changes: 12 additions & 0 deletions cmd/ddev/cmd/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ ddev restart --all`,
instrumentationApp = projects[0]
}

skip, err := cmd.Flags().GetBool("skip-confirmation")
if err != nil {
util.Failed(err.Error())
}

// Look for version change and opt-in to instrumentation if it has changed.
err = checkDdevVersionAndOptInInstrumentation(skip)
if err != nil {
util.Failed(err.Error())
}

for _, app := range projects {

output.UserOut.Printf("Restarting project %s...", app.GetName())
Expand All @@ -52,6 +63,7 @@ ddev restart --all`,
}

func init() {
RestartCmd.Flags().BoolP("skip-confirmation", "y", false, "Skip any confirmation steps")
RestartCmd.Flags().BoolVarP(&restartAll, "all", "a", false, "restart all projects")
RootCmd.AddCommand(RestartCmd)
}
59 changes: 39 additions & 20 deletions pkg/ddevapp/ddevapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,26 @@ import (
"gopkg.in/yaml.v3"
)

// SiteRunning defines the string used to denote running sites.
const SiteRunning = "running"
const (
// SiteRunning defines the string used to denote running sites.
SiteRunning = "running"
SiteStarting = "starting"

// SiteStarting is the string for a project that is starting
const SiteStarting = "starting"
// SiteStopped means a site where the containers were not found/do not exist, but the project is there.
SiteStopped = "stopped"

// SiteStopped defines the string used to denote a site where the containers were not found/do not exist, but the project is there.
const SiteStopped = "stopped"
// SiteDirMissing defines the string used to denote when a site is missing its application directory.
SiteDirMissing = "project directory missing"

// SiteDirMissing defines the string used to denote when a site is missing its application directory.
const SiteDirMissing = "project directory missing"
// SiteConfigMissing defines the string used to denote when a site is missing its .ddev/config.yml file.
SiteConfigMissing = ".ddev/config.yaml missing"

// SiteConfigMissing defines the string used to denote when a site is missing its .ddev/config.yml file.
const SiteConfigMissing = ".ddev/config.yaml missing"
// SitePaused defines the string used to denote when a site is in the paused (docker stopped) state.
SitePaused = "paused"

// SitePaused defines the string used to denote when a site is in the paused (docker stopped) state.
const SitePaused = "paused"

// SiteUnhealthy is the status for a project whose services are not all running
const SiteUnhealthy = "unhealthy"
// SiteUnhealthy is the status for a project whose services are not all reporting healthy yet
SiteUnhealthy = "unhealthy"
)

// DatabaseDefault is the default database/version
var DatabaseDefault = DatabaseDesc{nodeps.MariaDB, nodeps.MariaDBDefaultVersion}
Expand Down Expand Up @@ -761,8 +761,11 @@ func (app *DdevApp) ExportDB(dumpFile string, compressionType string, targetDB s
return err
}

// SiteStatus returns the current status of an application determined from web and db service health.
// SiteStatus returns the current status of a project determined from web and db service health.
// returns status, statusDescription
// Can return SiteConfigMissing, SiteDirMissing, SiteStopped, SiteStarting, SiteRunning, SitePaused,
// or another status returned from dockerutil.GetContainerHealth(), including
// "exited", "restarting", "healthy"
func (app *DdevApp) SiteStatus() (string, string) {
if !fileutil.FileExists(app.GetAppRoot()) {
return SiteDirMissing, fmt.Sprintf(`%s: %v; Please "ddev stop --unlist %s"`, SiteDirMissing, app.GetAppRoot(), app.Name)
Expand Down Expand Up @@ -1291,6 +1294,7 @@ Fix with 'ddev config global --required-docker-compose-version="" --use-docker-c
if err != nil {
return err
}
util.Debug("mutagen status after sync: %s", mStatus)

dur := util.FormatDuration(mutagenDuration())
if mStatus == "ok" {
Expand All @@ -1304,6 +1308,21 @@ Fix with 'ddev config global --required-docker-compose-version="" --use-docker-c
}
}

// At this point we should have all files synced inside the container
// Verify that we have composer.json inside container if we have it in project root
// This is possibly a temporary need for debugging https://github.com/ddev/ddev/issues/5089
// TODO: Consider removing this check when #5089 is resolved
if !app.NoProjectMount && fileutil.FileExists(filepath.Join(app.GetComposerRoot(false, false), "composer.json")) {
util.Debug("Checking for composer.json in container")
stdout, stderr, err := app.Exec(&ExecOpts{
Cmd: fmt.Sprintf("test -f %s", path.Join(app.GetComposerRoot(true, false), "composer.json")),
})

if err != nil {
return fmt.Errorf("composer.json not found in container, stdout='%s', stderr='%s': %v; please report this situation, https://github.com/ddev/ddev/issues; probably can be fixed with ddev restart", stdout, stderr, err)
}
}

util.Debug("Running /start.sh in ddev-webserver")
stdout, stderr, err := app.Exec(&ExecOpts{
// Send output to /var/tmp/logpipe to get it to docker logs
Expand Down Expand Up @@ -2798,14 +2817,14 @@ func GetContainerName(app *DdevApp, service string) string {
return "ddev-" + app.Name + "-" + service
}

// GetContainer returns the containerID of the app service name provided.
// GetContainer returns the container struct of the app service name provided.
func GetContainer(app *DdevApp, service string) (*docker.APIContainers, error) {
name := GetContainerName(app, service)
cid, err := dockerutil.FindContainerByName(name)
if err != nil || cid == nil {
container, err := dockerutil.FindContainerByName(name)
if err != nil || container == nil {
return nil, fmt.Errorf("unable to find container %s: %v", name, err)
}
return cid, nil
return container, nil
}

// FormatSiteStatus formats "paused" or "running" with color
Expand Down
1 change: 1 addition & 0 deletions pkg/ddevapp/ddevapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ func TestMain(m *testing.M) {
// This is normally done by Testsite.Prepare()
_ = os.Setenv("DDEV_NONINTERACTIVE", "true")
_ = os.Setenv("MUTAGEN_DATA_DIRECTORY", globalconfig.GetMutagenDataDirectory())
_ = os.Setenv("DOCKER_CLI_HINTS", "false")

// If GOTEST_SHORT is an integer, then use it as index for a single usage
// in the array. Any value can be used, it will default to just using the
Expand Down
76 changes: 47 additions & 29 deletions pkg/ddevapp/mutagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ func CreateOrResumeMutagenSync(app *DdevApp) error {

go func() {
err = app.MutagenSyncFlush()
util.Debug("gofunc flushed mutagen sync session '%s' err=%v", syncName, err)
flushErr <- err
return
}()
Expand Down Expand Up @@ -432,39 +433,56 @@ func (app *DdevApp) GetMutagenSyncID() (id string, err error) {

// MutagenSyncFlush performs a mutagen sync flush, waits for result, and checks for errors
func (app *DdevApp) MutagenSyncFlush() error {
status, _ := app.SiteStatus()
if status == SiteRunning && app.IsMutagenEnabled() {
syncName := MutagenSyncName(app.Name)
if !MutagenSyncExists(app) {
return errors.Errorf("Mutagen sync session '%s' does not exist", syncName)
}
if status, shortResult, session, err := app.MutagenStatus(); err == nil {
switch status {
case "paused":
util.Debug("mutagen sync %s is paused, so not flushing", syncName)
return nil
case "failing":
util.Warning("mutagen sync session %s has status '%s': shortResult='%v', err=%v, session contents='%v'", syncName, status, shortResult, err, session)
default:
// This extra sync resume recommended by @xenoscopic to catch situation where
// not paused but also not connected, in which case the flush will fail.
out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "resume", syncName)
if err != nil {
return fmt.Errorf("mutagen resume flush %s failed, output=%s, err=%v", syncName, out, err)
}
out, err = exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "flush", syncName)
if err != nil {
return fmt.Errorf("mutagen sync flush %s failed, output=%s, err=%v", syncName, out, err)
}
if !app.IsMutagenEnabled() {
return nil
}

container, err := GetContainer(app, "web")
if err != nil {
return fmt.Errorf("failed to get web container, err='%v'", err)
}

// Discussions of container.State in
// https://stackoverflow.com/questions/32427684/what-are-the-possible-states-for-a-docker-container
// and https://medium.com/@BeNitinAgarwal/lifecycle-of-docker-container-d2da9f85959
if container.State != "running" {
return fmt.Errorf("mutagenSyncFlush() not mutagen-syncing project %s with web container is in state %s, but must be 'running'", app.Name, container.State)
}
syncName := MutagenSyncName(app.Name)
if !MutagenSyncExists(app) {
return errors.Errorf("Mutagen sync session '%s' does not exist", syncName)
}
if status, shortResult, session, err := app.MutagenStatus(); err == nil {
util.Debug("mutagen sync %s status='%s', shortResult='%v', session='%v', err='%v'", syncName, status, shortResult, session, err)
switch status {
case "paused":
util.Debug("mutagen sync %s is paused, so not flushing", syncName)
return nil
case "failing":
util.Warning("mutagen sync session %s has status '%s': shortResult='%v', err=%v, session contents='%v'", syncName, status, shortResult, err, session)
default:
// This extra sync resume recommended by @xenoscopic to catch situation where
// not paused but also not connected, in which case the flush will fail.
util.Debug("default case resuming mutagen sync session '%s'", syncName)
out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "resume", syncName)
if err != nil {
return fmt.Errorf("mutagen resume flush %s failed, output=%s, err=%v", syncName, out, err)
}
util.Debug("default case flushing mutagen sync session '%s'", syncName)
out, err = exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "flush", syncName)
if err != nil {
return fmt.Errorf("mutagen sync flush %s failed, output=%s, err=%v", syncName, out, err)
}
util.Debug("default case output of mutagen sync='%s'", out)
}
}

status, _, _, err := app.MutagenStatus()
if (status != "ok" && status != "problems" && status != "paused" && status != "failing") || err != nil {
return err
}
util.Debug("Flushed mutagen sync session '%s'", syncName)
status, short, _, err := app.MutagenStatus()
util.Debug("mutagen sync status %s in MutagenSyncFlush(): status='%s', short='%s', err='%v'", syncName, status, short, err)
if (status != "ok" && status != "problems" && status != "paused" && status != "failing") || err != nil {
return err
}
util.Debug("Flushed mutagen sync session '%s'", syncName)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/dockerutil/dockerutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func InspectContainer(name string) (*docker.Container, error) {
return x, err
}

// FindContainerByName takes a container name and returns the container ID
// FindContainerByName takes a container name and returns the container
// If container is not found, returns nil with no error
func FindContainerByName(name string) (*docker.APIContainers, error) {
client := GetDockerClient()
Expand Down

0 comments on commit 7294710

Please sign in to comment.