Skip to content

Commit

Permalink
Allow ddev stop/rm to act on multiple/all sites, fixes #952 (#967)
Browse files Browse the repository at this point in the history
* Add utils file for functions shared across command files.

* Add --all flag to stop and remove commands.

* Add tests for ddev stop.

* Add test case to ensure a user can't combine --all and --remove-data.

* Add --all to start, add test.
  • Loading branch information
andrewfrench committed Jul 9, 2018
1 parent fae2e47 commit 4e5d0c8
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 64 deletions.
35 changes: 17 additions & 18 deletions cmd/ddev/cmd/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

var removeData bool
var removeAll bool

// DdevRemoveCmd represents the remove command
var DdevRemoveCmd = &cobra.Command{
Expand All @@ -20,35 +21,33 @@ leave database contents intact. Remove never touches your code or files director
To remove database contents, you may use the --remove-data flag with remove.`,
Run: func(cmd *cobra.Command, args []string) {
var siteName string

if len(args) > 1 {
util.Failed("Too many arguments provided. Please use 'ddev remove' or 'ddev remove [appname]'")
}

if len(args) == 1 {
siteName = args[0]
// Prevent users from destroying everything
if removeAll && removeData {
util.Failed("Illegal option combination: --all and --remove-data")
}

app, err := ddevapp.GetActiveApp(siteName)
apps, err := getRequestedApps(args, removeAll)
if err != nil {
util.Failed("Failed to remove: %v", err)
util.Failed("Unable to get project(s): %v", err)
}

if app.SiteStatus() == ddevapp.SiteNotFound {
util.Failed("Project is not currently running. Try 'ddev start'.")
}
// Iterate through the list of apps built above, removing each one.
for _, app := range apps {
if app.SiteStatus() == ddevapp.SiteNotFound {
util.Warning("Project %s is not currently running. Try 'ddev start'.", app.GetName())
}

err = app.Down(removeData)
if err != nil {
util.Failed("Failed to remove %s: %s", app.GetName(), err)
}
if err := app.Down(removeData); err != nil {
util.Failed("Failed to remove %s: %v", app.GetName(), err)
}

util.Success("Successfully removed the %s project.", app.GetName())
util.Success("Project %s has been removed.", app.GetName())
}
},
}

func init() {
DdevRemoveCmd.Flags().BoolVarP(&removeData, "remove-data", "R", false, "Remove stored project data (MySQL, logs, etc.)")
DdevRemoveCmd.Flags().BoolVarP(&removeAll, "all", "a", false, "Remove all running and stopped sites")
RootCmd.AddCommand(DdevRemoveCmd)
}
28 changes: 27 additions & 1 deletion cmd/ddev/cmd/remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"testing"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
)
Expand All @@ -19,10 +20,35 @@ func TestDevRemove(t *testing.T) {

out, err := exec.RunCommand(DdevBin, []string{"remove"})
assert.NoError(err, "ddev remove should succeed but failed, err: %v, output: %s", err, out)
assert.Contains(out, "Successfully removed")
assert.Contains(out, "has been removed")

// Ensure the site that was just stopped does not appear in the list of sites
apps := ddevapp.GetApps()
for _, app := range apps {
assert.True(app.GetName() != site.Name)
}

cleanup()
}

// Re-create running sites.
addSites()

// Ensure a user can't accidentally wipe out everything.
appsBefore := len(ddevapp.GetApps())
out, err := exec.RunCommand(DdevBin, []string{"remove", "--remove-data", "--all"})
assert.Error(err, "ddev remove --all --remove-data should error, but succeeded")
assert.Contains(out, "Illegal option")
assert.EqualValues(appsBefore, len(ddevapp.GetApps()), "No apps should be removed or added after ddev remove --all --remove-data")

// Ensure the --all option can remove all active apps
out, err = exec.RunCommand(DdevBin, []string{"remove", "--all"})
assert.NoError(err, "ddev remove --all should succeed but failed, err: %v, output: %s", err, out)
out, err = exec.RunCommand(DdevBin, []string{"list"})
assert.NoError(err)
assert.Contains(out, "no running ddev projects")
assert.Equal(0, len(ddevapp.GetApps()), "Not all apps were removed after ddev remove --all")

// Now put the sites back together so other tests can use them.
addSites()
}
39 changes: 21 additions & 18 deletions cmd/ddev/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"os"
"strings"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/output"
"github.com/drud/ddev/pkg/util"
"github.com/spf13/cobra"
)

var startAll bool

// StartCmd represents the add command
var StartCmd = &cobra.Command{
Use: "start",
Expand All @@ -28,28 +29,30 @@ provide a local development environment.`,
dockerutil.EnsureDdevNetwork()
},
Run: func(cmd *cobra.Command, args []string) {
appStart()
},
}

// appStart is a convenience function to encapsulate startup functionality
func appStart() {
app, err := ddevapp.GetActiveApp("")
if err != nil {
util.Failed("Failed to start project: %v", err)
}
apps, err := getRequestedApps(args, startAll)
if err != nil {
util.Failed("Unable to get project(s): %v", err)
}

output.UserOut.Printf("Starting environment for %s...", app.GetName())
if len(apps) == 0 {
output.UserOut.Printf("There are no projects to start.")
}

err = app.Start()
if err != nil {
util.Failed("Failed to start %s: %v", app.GetName(), err)
}
for _, app := range apps {
output.UserOut.Printf("Starting %s...", app.GetName())

util.Success("Successfully started %s", app.GetName())
util.Success("Your project can be reached at %s", strings.Join(app.GetAllURLs(), ", "))
if err := app.Start(); err != nil {
util.Warning("Failed to start %s: %v", app.GetName(), err)
continue
}

util.Success("Successfully started %s", app.GetName())
util.Success("Project can be reached at %s", strings.Join(app.GetAllURLs(), ", "))
}
},
}

func init() {
StartCmd.Flags().BoolVarP(&startAll, "all", "a", false, "Start all stopped sites")
RootCmd.AddCommand(StartCmd)
}
31 changes: 31 additions & 0 deletions cmd/ddev/cmd/start_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmd

import (
"testing"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
)

// TestDdevStart runs `ddev start` on the test apps
func TestDdevStart(t *testing.T) {
assert := asrt.New(t)

// Make sure we have running sites.
addSites()

// Stop all sites.
_, err := exec.RunCommand(DdevBin, []string{"stop", "--all"})
assert.NoError(err)

// Ensure all sites are started after ddev start --all.
out, err := exec.RunCommand(DdevBin, []string{"start", "--all"})
assert.NoError(err, "ddev start --all should succeed but failed, err: %v, output: %s", err, out)

// Confirm all sites are running.
apps := ddevapp.GetApps()
for _, app := range apps {
assert.True(app.SiteStatus() == ddevapp.SiteRunning, "All sites should be running, but %s status: %s", app.GetName(), app.SiteStatus())
}
}
29 changes: 11 additions & 18 deletions cmd/ddev/cmd/stop.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/util"
"github.com/spf13/cobra"
)

var stopAll bool

// DdevStopCmd represents the stop command
var DdevStopCmd = &cobra.Command{
Use: "stop [projectname]",
Expand All @@ -14,30 +15,22 @@ var DdevStopCmd = &cobra.Command{
from a project directory to stop that project, or you can specify a running project
to stop by running 'ddev stop <projectname>.`,
Run: func(cmd *cobra.Command, args []string) {
var siteName string

if len(args) > 1 {
util.Failed("Too many arguments provided. Please use 'ddev stop' or 'ddev stop [projectname]'")
}

if len(args) == 1 {
siteName = args[0]
}

app, err := ddevapp.GetActiveApp(siteName)
apps, err := getRequestedApps(args, stopAll)
if err != nil {
util.Failed("Failed to stop: %v", err)
util.Failed("Unable to get project(s): %v", err)
}

err = app.Stop()
if err != nil {
util.Failed("Failed to stop containers for %s: %v", app.GetName(), err)
}
for _, app := range apps {
if err := app.Stop(); err != nil {
util.Failed("Failed to stop %s: %v", app.GetName(), err)
}

util.Success("Project has been stopped.")
util.Success("Project %s has been stopped.", app.GetName())
}
},
}

func init() {
DdevStopCmd.Flags().BoolVarP(&stopAll, "all", "a", false, "Stop all running sites")
RootCmd.AddCommand(DdevStopCmd)
}
50 changes: 50 additions & 0 deletions cmd/ddev/cmd/stop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"testing"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
)

// TestDdevStop runs `ddev stop` on the test apps
func TestDdevStop(t *testing.T) {
assert := asrt.New(t)

// Make sure we have running sites.
addSites()

for _, site := range DevTestSites {
cleanup := site.Chdir()

out, err := exec.RunCommand(DdevBin, []string{"stop"})
assert.NoError(err, "ddev stop should succeed but failed, err: %v, output: %s", err, out)
assert.Contains(out, "has been stopped")

apps := ddevapp.GetApps()
for _, app := range apps {
if app.GetName() != site.Name {
continue
}

assert.True(app.SiteStatus() == ddevapp.SiteStopped)
}

cleanup()
}

// Re-create running sites.
addSites()
out, err := exec.RunCommand(DdevBin, []string{"stop", "--all"})
assert.NoError(err, "ddev stop --all should succeed but failed, err: %v, output: %s", err, out)

// Confirm all sites are stopped.
apps := ddevapp.GetApps()
for _, app := range apps {
assert.True(app.SiteStatus() == ddevapp.SiteStopped, "All sites should be stopped, but %s status: %s", app.GetName(), app.SiteStatus())
}

// Now put the sites back together so other tests can use them.
addSites()
}
39 changes: 39 additions & 0 deletions cmd/ddev/cmd/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cmd

import (
"fmt"

"github.com/drud/ddev/pkg/ddevapp"
)

// getRequestedApps will collect and return the requested apps from command line arguments and flags.
func getRequestedApps(args []string, all bool) ([]*ddevapp.DdevApp, error) {
// If all is true, all active apps will be returned.
if all {
return ddevapp.GetApps(), nil
}

// If multiple apps are requested by their site name, collect and return them.
if len(args) > 0 {
var apps []*ddevapp.DdevApp

for _, siteName := range args {
app, err := ddevapp.GetActiveApp(siteName)
if err != nil {
return []*ddevapp.DdevApp{}, fmt.Errorf("failed to get %s: %v", siteName, err)
}

apps = append(apps, app)
}

return apps, nil
}

// If all is false and no specific apps are requested, return the current app.
app, err := ddevapp.GetActiveApp("")
if err != nil {
return []*ddevapp.DdevApp{}, err
}

return []*ddevapp.DdevApp{app}, nil
}
14 changes: 5 additions & 9 deletions pkg/ddevapp/ddevapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,14 +798,11 @@ func (app *DdevApp) DockerEnv() {
// TODO: Warn people about additional names in use.
envVars["DDEV_HOSTNAME"] = strings.Join(app.GetHostnames(), ",")
}

// Only set values if they don't already exist in env.
for k, v := range envVars {
if os.Getenv(k) == "" {

err := os.Setenv(k, v)
if err != nil {
util.Error("Failed to set the environment variable %s=%s: %v", k, v, err)
}
if err := os.Setenv(k, v); err != nil {
util.Error("Failed to set the environment variable %s=%s: %v", k, v, err)
}
}
}
Expand All @@ -826,9 +823,8 @@ func (app *DdevApp) Stop() error {
if err != nil {
return err
}
_, _, err = dockerutil.ComposeCmd(files, "stop")

if err != nil {
if _, _, err := dockerutil.ComposeCmd(files, "stop"); err != nil {
return err
}

Expand Down Expand Up @@ -886,7 +882,7 @@ func (app *DdevApp) Down(removeData bool) error {
// Remove all the containers and volumes for app.
err = Cleanup(app)
if err != nil {
return fmt.Errorf("Failed to remove %s: %s", app.GetName(), err)
return fmt.Errorf("failed to remove %s: %v", app.GetName(), err)
}

// Remove data/database if we need to.
Expand Down

0 comments on commit 4e5d0c8

Please sign in to comment.