diff --git a/cmd/ddev/cmd/config.go b/cmd/ddev/cmd/config.go index 6efd9ef158dd..b720e9ba7adf 100644 --- a/cmd/ddev/cmd/config.go +++ b/cmd/ddev/cmd/config.go @@ -164,7 +164,7 @@ func handleConfigRun(cmd *cobra.Command, args []string) { util.Failed("Please do not use `ddev config` in your home directory") } - err = app.ProcessHooks("pre-config") + _, _, err = app.ProcessHooks("pre-config") if err != nil { util.Failed("Failed to process hook 'pre-config'") } @@ -210,7 +210,7 @@ func handleConfigRun(cmd *cobra.Command, args []string) { if err != nil { util.Failed("Failed to write provider config: %v", err) } - err = app.ProcessHooks("post-config") + _, _, err = app.ProcessHooks("post-config") if err != nil { util.Failed("Failed to process hook 'post-config'") } diff --git a/pkg/ddevapp/composer.go b/pkg/ddevapp/composer.go index b854991489da..c377f3a86411 100644 --- a/pkg/ddevapp/composer.go +++ b/pkg/ddevapp/composer.go @@ -12,7 +12,7 @@ import ( // Composer runs composer commands in the web container, managing pre- and post- hooks func (app *DdevApp) Composer(args []string) (string, string, error) { - err := app.ProcessHooks("pre-composer") + _, _, err := app.ProcessHooks("pre-composer") if err != nil { return "", "", fmt.Errorf("Failed to process pre-composer hooks: %v", err) } @@ -30,10 +30,9 @@ func (app *DdevApp) Composer(args []string) (string, string, error) { if runtime.GOOS == "windows" && !nodeps.IsDockerToolbox() { fileutil.ReplaceSimulatedLinks(app.AppRoot) } - err = app.ProcessHooks("post-composer") + _, _, err = app.ProcessHooks("post-composer") if err != nil { - return stdout, stderr, fmt.Errorf("Failed to process post-composer hooks: %v", err) + return "", "", fmt.Errorf("Failed to process post-composer hooks: %v", err) } return stdout, stderr, nil - } diff --git a/pkg/ddevapp/ddevapp.go b/pkg/ddevapp/ddevapp.go index ceb0e3a4dcd8..1ee24491cdfa 100644 --- a/pkg/ddevapp/ddevapp.go +++ b/pkg/ddevapp/ddevapp.go @@ -168,7 +168,7 @@ func (app *DdevApp) FindContainerByType(containerType string) (*docker.APIContai // Describe returns a map which provides detailed information on services associated with the running site. func (app *DdevApp) Describe() (map[string]interface{}, error) { - err := app.ProcessHooks("pre-describe") + _, _, err := app.ProcessHooks("pre-describe") if err != nil { return nil, fmt.Errorf("Failed to process pre-describe hooks: %v", err) } @@ -229,7 +229,7 @@ func (app *DdevApp) Describe() (map[string]interface{}, error) { appDesc["bgsyncimg"] = app.BgsyncImage appDesc["dbaimg"] = app.DBAImage - err = app.ProcessHooks("post-describe") + _, _, err = app.ProcessHooks("post-describe") if err != nil { return nil, fmt.Errorf("Failed to process post-describe hooks: %v", err) } @@ -299,7 +299,7 @@ func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool) error return err } - err = app.ProcessHooks("pre-import-db") + _, _, err = app.ProcessHooks("pre-import-db") if err != nil { return err } @@ -403,7 +403,7 @@ func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool) error return fmt.Errorf("failed to clean up %s after import: %v", dbPath, err) } - err = app.ProcessHooks("post-import-db") + _, _, err = app.ProcessHooks("post-import-db") if err != nil { return err } @@ -518,7 +518,7 @@ type PullOptions struct { // Pull performs an import from the a configured provider plugin, if one exists. func (app *DdevApp) Pull(provider Provider, opts *PullOptions) error { var err error - err = app.ProcessHooks("pre-pull") + _, _, err = app.ProcessHooks("pre-pull") if err != nil { return fmt.Errorf("Failed to process pre-pull hooks: %v", err) } @@ -579,7 +579,7 @@ func (app *DdevApp) Pull(provider Provider, opts *PullOptions) error { } } } - err = app.ProcessHooks("post-pull") + _, _, err = app.ProcessHooks("post-pull") if err != nil { return fmt.Errorf("Failed to process post-pull hooks: %v", err) } @@ -591,7 +591,7 @@ func (app *DdevApp) Pull(provider Provider, opts *PullOptions) error { func (app *DdevApp) ImportFiles(importPath string, extPath string) error { app.DockerEnv() - if err := app.ProcessHooks("pre-import-files"); err != nil { + if _, _, err := app.ProcessHooks("pre-import-files"); err != nil { return err } @@ -599,7 +599,7 @@ func (app *DdevApp) ImportFiles(importPath string, extPath string) error { return err } - if err := app.ProcessHooks("post-import-files"); err != nil { + if _, _, err := app.ProcessHooks("post-import-files"); err != nil { return err } @@ -652,7 +652,8 @@ func (app *DdevApp) ComposeFiles() ([]string, error) { } // ProcessHooks executes Tasks defined in Hooks -func (app *DdevApp) ProcessHooks(hookName string) error { +func (app *DdevApp) ProcessHooks(hookName string) (string, string, error) { + var stdout, stderr string if cmds := app.Hooks[hookName]; len(cmds) > 0 { output.UserOut.Printf("Executing %s hook...", hookName) } @@ -660,21 +661,28 @@ func (app *DdevApp) ProcessHooks(hookName string) error { for _, c := range app.Hooks[hookName] { a := NewTask(app, c) if a == nil { - return fmt.Errorf("unable to create task from %v", c) + return "", "", fmt.Errorf("unable to create task from %v", c) } output.UserOut.Printf("=== Running task: %s, output below", a.GetDescription()) - stdout, stderr, err := a.Execute() - //output.UserOut.Println(stdout + "\n" + stderr) + taskout, taskerr, err := a.Execute() + if taskout != "" { + output.UserOut.Println(taskout) + } + if taskerr != "" { + output.UserOut.Errorln(taskerr) + } if err != nil { - output.UserOut.Errorf("task failed: %v: %s %s", a.GetDescription(), stdout, stderr) + output.UserOut.Errorf("task failed: %v: %v", a.GetDescription(), err) output.UserOut.Warn("A task failure does not mean that ddev failed, but your hook configuration has a command that failed.") } + stdout = stdout + taskout + stderr = stderr + taskerr } - return nil + return stdout, stderr, nil } // Start initiates docker-compose up @@ -708,7 +716,7 @@ func (app *DdevApp) Start() error { return err } - err = app.ProcessHooks("pre-start") + _, _, err = app.ProcessHooks("pre-start") if err != nil { return err } @@ -804,7 +812,7 @@ func (app *DdevApp) Start() error { return err } - err = app.ProcessHooks("post-start") + _, _, err = app.ProcessHooks("post-start") if err != nil { return err } @@ -839,7 +847,7 @@ func (app *DdevApp) Exec(opts *ExecOpts) (string, string, error) { if opts.Service == "" { return "", "", fmt.Errorf("no service provided") } - err := app.ProcessHooks("pre-exec") + _, _, err := app.ProcessHooks("pre-exec") if err != nil { return "", "", fmt.Errorf("Failed to process pre-exec hooks: %v", err) } @@ -893,7 +901,7 @@ func (app *DdevApp) Exec(opts *ExecOpts) (string, string, error) { stdoutResult, stderrResult, err = dockerutil.ComposeCmd(files, exec...) } - hookErr := app.ProcessHooks("post-exec") + _, _, hookErr := app.ProcessHooks("post-exec") if hookErr != nil { return stdoutResult, stderrResult, fmt.Errorf("Failed to process post-exec hooks: %v", hookErr) } @@ -1115,7 +1123,7 @@ func (app *DdevApp) Pause() error { return fmt.Errorf("no project to stop") } - err := app.ProcessHooks("pre-pause") + _, _, err := app.ProcessHooks("pre-pause") if err != nil { return err } @@ -1128,7 +1136,7 @@ func (app *DdevApp) Pause() error { if _, _, err := dockerutil.ComposeCmd(files, "stop"); err != nil { return err } - err = app.ProcessHooks("post-pause") + _, _, err = app.ProcessHooks("post-pause") if err != nil { return err } @@ -1230,7 +1238,7 @@ func (app *DdevApp) DetermineSettingsPathLocation() (string, error) { // Snapshot forces a mariadb snapshot of the db to be written into .ddev/db_snapshots // Returns the dirname of the snapshot and err func (app *DdevApp) Snapshot(snapshotName string) (string, error) { - err := app.ProcessHooks("pre-snapshot") + _, _, err := app.ProcessHooks("pre-snapshot") if err != nil { return "", fmt.Errorf("Failed to process pre-stop hooks: %v", err) } @@ -1268,7 +1276,7 @@ func (app *DdevApp) Snapshot(snapshotName string) (string, error) { } util.Success("Created database snapshot %s in %s", snapshotName, hostSnapshotDir) - err = app.ProcessHooks("post-snapshot") + _, _, err = app.ProcessHooks("post-snapshot") if err != nil { return snapshotName, fmt.Errorf("Failed to process pre-stop hooks: %v", err) } @@ -1279,7 +1287,7 @@ func (app *DdevApp) Snapshot(snapshotName string) (string, error) { // The project must be stopped and docker volume removed and recreated for this to work. func (app *DdevApp) RestoreSnapshot(snapshotName string) error { var err error - err = app.ProcessHooks("pre-restore-snapshot") + _, _, err = app.ProcessHooks("pre-restore-snapshot") if err != nil { return fmt.Errorf("Failed to process pre-restore-snapshot hooks: %v", err) } @@ -1330,7 +1338,7 @@ func (app *DdevApp) RestoreSnapshot(snapshotName string) error { util.CheckErr(err) util.Success("Restored database snapshot: %s", hostSnapshotDir) - err = app.ProcessHooks("post-restore-snapshot") + _, _, err = app.ProcessHooks("post-restore-snapshot") if err != nil { return fmt.Errorf("Failed to process post-restore-snapshot hooks: %v", err) } @@ -1342,7 +1350,7 @@ func (app *DdevApp) Stop(removeData bool, createSnapshot bool) error { app.DockerEnv() var err error - err = app.ProcessHooks("pre-stop") + _, _, err = app.ProcessHooks("pre-stop") if err != nil { return fmt.Errorf("Failed to process pre-stop hooks: %v", err) } @@ -1394,7 +1402,7 @@ func (app *DdevApp) Stop(removeData bool, createSnapshot bool) error { util.Success("Project data/database removed from docker volume for project %s", app.Name) } - err = app.ProcessHooks("post-stop") + _, _, err = app.ProcessHooks("post-stop") if err != nil { return fmt.Errorf("Failed to process post-stop hooks: %v", err) } diff --git a/pkg/ddevapp/ddevapp_test.go b/pkg/ddevapp/ddevapp_test.go index 84b8b6540943..b847133c6504 100644 --- a/pkg/ddevapp/ddevapp_test.go +++ b/pkg/ddevapp/ddevapp_test.go @@ -1533,21 +1533,23 @@ func TestProcessHooks(t *testing.T) { }, } - stdout := util.CaptureUserOut() - err = app.ProcessHooks("hook-test") + goStdout := util.CaptureUserOut() + + stdout, _, err := app.ProcessHooks("hook-test") assert.NoError(err) // Ignore color in output, can be different in different OS's - out := vtclean.Clean(stdout(), false) - - assert.Contains(out, "Executing hook-test hook") - assert.Contains(out, "Exec command 'ls /usr/local/bin/composer' in container/service 'web'") - assert.Contains(out, "Exec command 'echo something' on the host") - assert.Contains(out, "Exec command 'echo MYSQL_USER=${MYSQL_USER}' in container/service 'db'") - assert.Contains(out, "MYSQL_USER=db") - assert.Contains(out, "Exec command 'echo TestProcessHooks > /var/www/html/TestProcessHooks${DDEV_ROUTER_HTTPS_PORT}.txt' in container/service 'web'") - assert.Contains(out, "Exec command 'touch /var/tmp/TestProcessHooks && touch /var/www/html/touch_works_after_and.txt' in container/service 'web',") - assert.Contains(out, "Twig, the flexible, fast, and secure template") + stdout = vtclean.Clean(stdout, false) + goStdoutStr := vtclean.Clean(goStdout(), false) + + assert.Contains(goStdoutStr, "Executing hook-test hook") + assert.Contains(goStdoutStr, "Exec command 'ls /usr/local/bin/composer' in container/service 'web'") + assert.Contains(goStdoutStr, "Exec command 'echo something' on the host") + assert.Contains(goStdoutStr, "Exec command 'echo MYSQL_USER=${MYSQL_USER}' in container/service 'db'") + assert.Contains(stdout, "MYSQL_USER=db") + assert.Contains(goStdoutStr, "Exec command 'echo TestProcessHooks > /var/www/html/TestProcessHooks${DDEV_ROUTER_HTTPS_PORT}.txt' in container/service 'web'") + assert.Contains(goStdoutStr, "Exec command 'touch /var/tmp/TestProcessHooks && touch /var/www/html/touch_works_after_and.txt' in container/service 'web',") + assert.Contains(stdout, "Twig, the flexible, fast, and secure template") assert.FileExists(filepath.Join(app.AppRoot, fmt.Sprintf("TestProcessHooks%s.txt", app.RouterHTTPSPort))) assert.FileExists(filepath.Join(app.AppRoot, "touch_works_after_and.txt")) diff --git a/pkg/ddevapp/task.go b/pkg/ddevapp/task.go index 607c6f6460ff..b41e8e7b2b2d 100644 --- a/pkg/ddevapp/task.go +++ b/pkg/ddevapp/task.go @@ -1,7 +1,6 @@ package ddevapp import ( - "bytes" "fmt" "os" "os/exec" @@ -42,7 +41,6 @@ func (c ExecTask) Execute() (string, string, error) { stdout, stderr, err := c.app.Exec(&ExecOpts{ Service: c.service, Cmd: c.exec, - Tty: true, }) return stdout, stderr, err @@ -70,14 +68,14 @@ func (c ExecHostTask) Execute() (string, string, error) { execAry := strings.Split(c.exec, " ") - cmd := exec.Command(execAry[0], execAry[1:]...) - var stdout, stderr bytes.Buffer - cmd.Stderr = &stderr - cmd.Stdout = &stdout - err = cmd.Run() + stderr := []byte{} + stdout, err := exec.Command(execAry[0], execAry[1:]...).Output() + if exitErr, ok := err.(*exec.ExitError); ok { + stderr = exitErr.Stderr + } _ = os.Chdir(cwd) - return stdout.String(), stderr.String(), err + return string(stdout), string(stderr), err } // Execute (ComposerTask) runs a composer command in the web container