cmd

package
v1.21.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 15, 2022 License: Apache-2.0 Imports: 49 Imported by: 0

Documentation

Index

Constants

View Source
const (
	FtBool           = "bool"
	FtBoolSlice      = "boolSlice"
	FtBytesHex       = "bytesHex"
	FtBytesBase64    = "bytesBase64"
	FtCount          = "count"
	FtDuration       = "duration"
	FtDurationSlice  = "durationSlice"
	FtFloat32        = "float32"
	FtFloat32Slice   = "float32Slice"
	FtFloat64        = "float64"
	FtFloat64Slice   = "float64Slice"
	FtInt            = "int"
	FtIntSlice       = "intSlice"
	FtInt8           = "int8"
	FtInt16          = "int16"
	FtInt32          = "int32"
	FtInt32Slice     = "int32Slice"
	FtInt64          = "int64"
	FtInt64Slice     = "int64Slice"
	FtIP             = "ip"
	FtIPSlice        = "ipSlice"
	FtIPMask         = "ipMask"
	FtIPNet          = "ipNet"
	FtString         = "string"
	FtStringArray    = "stringArray"
	FtStringSlice    = "stringSlice"
	FtStringToInt    = "stringToInt"
	FtStringToInt64  = "stringToInt64"
	FtStringToString = "stringToString"
	FtUint           = "uint"
	FtUintSlice      = "uintSlice"
	FtUint8          = "uint8"
	FtUint16         = "uint16"
	FtUint32         = "uint32"
	FtUint64         = "uint64"
)

Defines the constants for the valid types which always should used in the source code.

Variables

View Source
var AuthCmd = &cobra.Command{
	Use:     "auth [command]",
	Short:   "A collection of authentication commands",
	Example: `ddev auth ssh`,
	Run: func(cmd *cobra.Command, args []string) {
		err := cmd.Usage()
		util.CheckErr(err)
	},
}

AuthCmd is the top-level "ddev auth" command

View Source
var AuthSSHCommand = &cobra.Command{
	Use:     "ssh",
	Short:   "Add ssh key authentication to the ddev-ssh-auth container",
	Long:    `Use this command to provide the password to your ssh key to the ddev-ssh-agent container, where it can be used by other containers. Normal usage is just "ddev auth ssh", or if your key is not in ~/.ssh, ddev auth ssh --ssh-key-path=/some/path/.ssh"`,
	Example: `ddev auth ssh`,
	Run: func(cmd *cobra.Command, args []string) {
		var err error
		if len(args) > 0 {
			util.Failed("This command takes no arguments.")
		}

		uidStr, _, _ := util.GetContainerUIDGid()

		if sshKeyPath == "" {
			homeDir, err := os.UserHomeDir()
			if err != nil {
				util.Failed("Unable to determine home directory: %v", err)
			}
			sshKeyPath = filepath.Join(homeDir, ".ssh")
		}
		if !filepath.IsAbs(sshKeyPath) {
			sshKeyPath, err = filepath.Abs(sshKeyPath)
			if err != nil {
				util.Failed("Failed to derive absolute path for ssh key path %s: %v", sshKeyPath, err)
			}
		}
		fi, err := os.Stat(sshKeyPath)
		if os.IsNotExist(err) {
			util.Failed("The ssh key directory %s was not found", sshKeyPath)
		}
		if err != nil {
			util.Failed("Failed to check status of ssh key directory %s: %v", sshKeyPath, err)
		}
		if !fi.IsDir() {
			util.Failed("The ssh key directory (%s) must be a directory", sshKeyPath)
		}

		app, err := ddevapp.GetActiveApp("")
		if err != nil || app == nil {

			app = &ddevapp.DdevApp{OmitContainersGlobal: globalconfig.DdevGlobalConfig.OmitContainersGlobal}
		}
		omitted := app.GetOmittedContainers()
		if nodeps.ArrayContainsString(omitted, nodeps.DdevSSHAgentContainer) {
			util.Failed("ddev-ssh-agent is omitted in your configuration so ssh auth cannot be used")
		}

		err = app.EnsureSSHAgentContainer()
		if err != nil {
			util.Failed("Failed to start ddev-ssh-agent container: %v", err)
		}
		sshKeyPath = dockerutil.MassageWindowsHostMountpoint(sshKeyPath)

		dockerCmd := []string{"run", "-it", "--rm", "--volumes-from=" + ddevapp.SSHAuthName, "--user=" + uidStr, "--entrypoint=", "--mount=type=bind,src=" + sshKeyPath + ",dst=/tmp/sshtmp", versionconstants.SSHAuthImage + ":" + versionconstants.SSHAuthTag + "-built", "bash", "-c", `cp -r /tmp/sshtmp ~/.ssh && chmod -R go-rwx ~/.ssh && cd ~/.ssh && ssh-add $(file * | awk -F: "/private key/ { print \$1 }")`}

		err = exec.RunInteractiveCommand("docker", dockerCmd)

		if err != nil {
			util.Failed("Docker command 'docker %v' failed: %v", dockerCmd, err)
		}
	},
}

AuthSSHCommand implements the "ddev auth ssh" command

View Source
var CleanCmd = &cobra.Command{
	Use:   "clean [projectname ...]",
	Short: "Removes items ddev has created",
	Long: `Stops all running projects and then removes downloads and snapshots
for the selected projects. Then clean will remove "drud/ddev-*" images.

Warning - This command will permanently delete your snapshots for the named project[s].

Additional commands that can help clean up resources:
  ddev delete --omit-snapshot
  docker rmi -f $(docker images -q)
  docker system prune --volumes
	`,
	Example: `ddev clean
	ddev clean project1 project2
	ddev clean --all`,
	Run: func(cmd *cobra.Command, args []string) {

		cleanAll, _ := cmd.Flags().GetBool("all")
		if len(args) == 0 && !cleanAll {
			util.Failed("No project provided. See ddev clean --help for usage")
		}

		projects, err := getRequestedProjects(args, cleanAll)
		if err != nil {
			util.Failed("Failed to get project(s) '%v': %v", args, err)
		}

		util.Warning("Warning - Snapshots for the following project[s] will be permanently deleted")

		for _, project := range projects {
			snapshots, err := project.ListSnapshots()
			if err != nil {
				util.Failed("Failed to get project %s snapshots: %v", project.Name, err)
			}
			if len(snapshots) > 0 {
				output.UserOut.Printf("%v Snapshots for project %s will be deleted", len(snapshots), project.Name)
			} else {
				output.UserOut.Printf("No snapshots found for project %s", project.Name)
			}
		}

		dryRun, _ := cmd.Flags().GetBool("dry-run")
		if dryRun {
			util.Warning("Dry run terminated without removing items")
			return
		}

		confirm := util.Prompt("Are you sure you want to continue? y/n", "n")
		if strings.ToLower(confirm) != "y" {
			return
		}

		util.Success("Powering off ddev to avoid conflicts.")
		ddevapp.PowerOff()

		globalDdevDir := globalconfig.GetGlobalDdevDir()
		_ = os.RemoveAll(filepath.Join(globalDdevDir, "testcache"))
		_ = os.RemoveAll(filepath.Join(globalDdevDir, "bin"))

		output.UserOut.Print("Deleting snapshots and downloads for selected projects...")
		for _, project := range projects {

			err = os.RemoveAll(project.GetConfigPath(".downloads"))
			if err != nil {
				util.Warning("There was an error removing .downloads for project %v", project)
			}
			err = os.RemoveAll(project.GetConfigPath("db_snapshots"))
			if err != nil {
				util.Warning("There was an error removing db_snapshots for project %v", project)
			}
		}

		output.UserOut.Print("Deleting Docker images that ddev created...")
		err = deleteDdevImages(true)
		if err != nil {
			util.Failed("Failed to delete image tag", err)
		}
		util.Success("Finished cleaning ddev projects")
	},
}
View Source
var ComposerCmd = &cobra.Command{
	DisableFlagParsing: true,
	Use:                "composer [command]",
	Short:              "Executes a composer command within the web container",
	Long: `Executes a composer command at the composer root in the web container. Generally,
any composer command can be forwarded to the container context by prepending
the command with 'ddev'.`,
	Example: `ddev composer install
ddev composer require <package>
ddev composer outdated --minor-only
ddev composer create drupal/recommended-project`,
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed(err.Error())
		}

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			if err = app.Start(); err != nil {
				util.Failed("Failed to start %s: %v", app.Name, err)
			}
		}

		stdout, stderr, err := app.Composer(args)
		if err != nil {
			util.Failed("composer %v failed, %v. stderr=%v", args, err, stderr)
		}
		_, _ = fmt.Fprint(os.Stderr, stderr)
		_, _ = fmt.Fprint(os.Stdout, stdout)
	},
}

ComposerCmd handles ddev composer

View Source
var ComposerCreateCmd = &cobra.Command{
	Use: "create [args] [flags]",
	FParseErrWhitelist: cobra.FParseErrWhitelist{
		UnknownFlags: true,
	},
	Short: "Executes 'composer create-project' within the web container with the arguments and flags provided",
	Long: `Directs basic invocations of 'composer create-project' within the context of the
web container. Projects will be installed to a temporary directory and moved to
the composer root directory after install. Any existing files in the
composer root will be deleted when creating a project.`,
	Example: `ddev composer create drupal/recommended-project
ddev composer create -y drupal/recommended-project
ddev composer create "typo3/cms-base-distribution:^10"
ddev composer create drupal/recommended-project --no-install
ddev composer create --repository=https://repo.magento.com/ magento/project-community-edition
ddev composer create --prefer-dist --no-interaction --no-dev psr/log
`,
	Run: func(cmd *cobra.Command, args []string) {

		osargs := []string{}
		if len(os.Args) > 3 {
			osargs = os.Args[3:]
			osargs = nodeps.RemoveItemFromSlice(osargs, "--yes")
			osargs = nodeps.RemoveItemFromSlice(osargs, "-y")
		}
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed(err.Error())
		}

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			err = app.Start()
			if err != nil {
				util.Failed("Failed to start app %s to run create-project: %v", app.Name, err)
			}
		}

		composerRoot := app.GetComposerRoot(false, false)

		util.Warning("Warning: MOST EXISTING CONTENT in the composer root (%s) will be deleted by the composer create-project operation. Only .ddev, .git and .tarballs will be preserved.", composerRoot)
		if !composerCreateYesFlag {
			if !util.Confirm("Would you like to continue?") {
				util.Failed("create-project cancelled")
			}
		}

		util.Warning("Removing any existing files in composer root")
		objs, err := fileutil.ListFilesInDir(composerRoot)
		if err != nil {
			util.Failed("Failed to create project: %v", err)
		}

		for _, o := range objs {

			if o == ".ddev" || o == ".git" || o == ".tarballs" {
				continue
			}

			if err = os.RemoveAll(filepath.Join(composerRoot, o)); err != nil {
				util.Failed("Failed to create project: %v", err)
			}
		}

		err = app.MutagenSyncFlush()
		if err != nil {
			util.Failed("Failed to sync mutagen contents: %v", err)
		}

		tmpDir := util.RandString(6)
		containerInstallPath := path.Join("/tmp", tmpDir)

		noInstallPresent := nodeps.ArrayContainsString(osargs, "--no-install")
		if !noInstallPresent {

			osargs = append(osargs, "--no-install")
		}

		composerCmd := []string{
			"composer",
			"create-project",
		}
		composerCmd = append(composerCmd, osargs...)
		composerCmd = append(composerCmd, containerInstallPath)

		output.UserOut.Printf("Executing composer command: %v\n", composerCmd)
		stdout, stderr, err := app.Exec(&ddevapp.ExecOpts{
			Service: "web",
			RawCmd:  composerCmd,
			Dir:     "/var/www/html",
			Tty:     isatty.IsTerminal(os.Stdin.Fd()),
		})
		if err != nil {
			util.Failed("Failed to create project:%v, stderr=%v", err, stderr)
		}

		if len(stdout) > 0 {
			fmt.Println(strings.TrimSpace(stdout))
		}

		output.UserOut.Printf("Moving install to composer root")

		rsyncArgs := "-rltgopD"
		if runtime.GOOS == "windows" {
			rsyncArgs = "-rlD"
		}
		_, _, err = app.Exec(&ddevapp.ExecOpts{
			Service: "web",
			Cmd:     fmt.Sprintf(`rsync %s "%s/" "%s/"`, rsyncArgs, containerInstallPath, app.GetComposerRoot(true, false)),
			Dir:     "/var/www/html",
		})

		if err != nil {
			util.Failed("Failed to create project: %v", err)
		}

		if !noInstallPresent {
			composerCmd = []string{
				"composer",
				"install",
			}

			supportedArgs := []string{
				"--prefer-source",
				"--prefer-dist",
				"--prefer-install",
				"--no-dev",
				"--no-progress",
				"--ignore-platform-req",
				"--ignore-platform-reqs",
				"-q",
				"--quiet",
				"--ansi",
				"--no-ansi",
				"-n",
				"--no-interaction",
				"--profile",
				"--no-plugins",
				"--no-scripts",
				"-d",
				"--working-dir",
				"--no-cache",
				"-v",
				"-vv",
				"-vvv",
				"--verbose",
			}

			for _, osarg := range osargs {
				for _, supportedArg := range supportedArgs {
					if strings.HasPrefix(osarg, supportedArg) {
						composerCmd = append(composerCmd, osarg)
					}
				}
			}

			output.UserOut.Printf("Executing composer command: %v\n", composerCmd)
			stdout, stderr, err := app.Exec(&ddevapp.ExecOpts{
				Service: "web",
				RawCmd:  composerCmd,
				Dir:     "/var/www/html",
				Tty:     isatty.IsTerminal(os.Stdin.Fd()),
			})
			if err != nil {
				util.Failed("Failed to install project:%v, stderr=%v", err, stderr)
			}

			if len(stdout) > 0 {
				fmt.Println(strings.TrimSpace(stdout))
			}
		}

		err = app.Restart()
		if err != nil {
			util.Warning("Failed to restart project after composer create: %v", err)
		}

		if runtime.GOOS == "windows" {
			fileutil.ReplaceSimulatedLinks(app.AppRoot)
		}
	},
}

ComposerCreateCmd handles ddev composer create

View Source
var ComposerCreateProjectCmd = &cobra.Command{
	Use:                "create-project",
	Short:              "Unsupported, use `ddev composer create` instead",
	DisableFlagParsing: true,
	Run: func(cmd *cobra.Command, args []string) {
		util.Failed(`'ddev composer create-project' is unsupported. Please use 'ddev composer create'
for basic project creation or 'ddev ssh' into the web container and execute
'composer create-project' directly.`)
	},
}

ComposerCreateProjectCmd just sends people to the right thing when they try ddev composer create-project

View Source
var ConfigCommand = &cobra.Command{
	Use:     "config [provider or 'global']",
	Short:   "Create or modify a ddev project configuration in the current directory",
	Example: `"ddev config" or "ddev config --docroot=web  --project-type=drupal8"`,
	Args:    cobra.ExactArgs(0),
	Run:     handleConfigRun,
}

ConfigCommand represents the `ddev config` command

View Source
var DdevExecCmd = &cobra.Command{
	Use:     "exec <command>",
	Aliases: []string{"."},
	Short:   "Execute a shell command in the container for a service. Uses the web service by default.",
	Long:    `Execute a shell command in the container for a service. Uses the web service by default. To run your command in the container for another service, run "ddev exec --service <service> <cmd>". If you want to use raw, uninterpreted command inside container use --raw as in example.`,
	Example: `ddev exec ls /var/www/html
ddev exec --service db\nddev exec -s db
ddev exec -s solr (assuming an add-on service named 'solr')
ddev exec --raw -- ls -lR`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) == 0 {
			err := cmd.Usage()
			util.CheckErr(err)
			os.Exit(1)
		}

		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Failed to exec command: %v", err)
		}

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			util.Failed("Project is not currently running. Try 'ddev start'.")
		}

		app.DockerEnv()

		opts := &ddevapp.ExecOpts{
			Service: serviceType,
			Dir:     execDirArg,
			Cmd:     strings.Join(args, " "),
			Tty:     true,
		}

		if cmd.Flag("raw").Changed {
			if useRaw, _ := cmd.Flags().GetBool("raw"); useRaw {
				opts.RawCmd = args
			}
		}

		_, _, err = app.Exec(opts)

		if err != nil {
			util.Failed("Failed to execute command %s: %v", strings.Join(args, " "), err)
		}
	},
}

DdevExecCmd allows users to execute arbitrary sh commands within a container.

View Source
var DdevLogsCmd = &cobra.Command{
	Use:   "logs [projectname]",
	Short: "Get the logs from your running services.",
	Long:  `Uses 'docker logs' to display stdout from the running services.`,
	Example: `ddev logs
ddev logs -f
ddev logs -s db
ddev logs -s db [projectname]`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) > 1 {
			util.Failed("Too many arguments provided. Please use 'ddev logs' or 'ddev logs [projectname]'")
		}

		projects, err := getRequestedProjects(args, false)
		if err != nil {
			util.Failed("GetRequestedProjects() failed:  %v", err)
		}
		project := projects[0]

		err = project.Logs(serviceType, follow, timestamp, tail)
		if err != nil {
			util.Failed("Failed to retrieve logs for %s: %v", project.GetName(), err)
		}
	},
}

DdevLogsCmd contains the "ddev logs" command

View Source
var DdevPauseCommand = &cobra.Command{
	Use:   "pause [projectname ...]",
	Short: "uses 'docker stop' to pause/stop the containers belonging to a project.",
	Long: `Uses "docker-compose stop" to pause/stop the containers belonging to a project. This leaves the containers instantiated instead of removing them like ddev stop does. You can run 'ddev pause'
from a project directory to stop the containers of that project, or you can stop running projects
in any directory by running 'ddev pause projectname [projectname ...]' or pause all with 'ddev pause --all'`,
	Aliases: []string{"sc", "stop-containers"},
	Run: func(cmd *cobra.Command, args []string) {
		projects, err := getRequestedProjects(args, pauseAllProjects)
		if err != nil {
			util.Failed("Unable to get project(s): %v", err)
		}
		if len(projects) > 0 {
			instrumentationApp = projects[0]
		}

		for _, project := range projects {
			if err := ddevapp.CheckForMissingProjectFiles(project); err != nil {
				util.Failed("Failed to pause/stop-containers %s: %v", project.GetName(), err)
			}

			if err := project.Pause(); err != nil {
				util.Failed("Failed to pause/stop-containers %s: %v", project.GetName(), err)
			}

			util.Success("Project %s has been paused.", project.GetName())
		}
	},
}

DdevPauseCommand represents the stop command

View Source
var DdevRestoreSnapshotCommand = &cobra.Command{
	Hidden: true,
	Use:    "restore-snapshot [snapshot_name]",
	Short:  "Restore a project's database to the provided snapshot version.",
	Long:   "Please use \"snapshot restore\" command",
	Run: func(cmd *cobra.Command, args []string) {
		util.Failed("Please use \"ddev snapshot restore\".")
		os.Exit(1)
	},
}

DdevRestoreSnapshotCommand provides the ability to revert to a database snapshot

View Source
var DdevSSHCmd = &cobra.Command{
	Use:   "ssh [projectname]",
	Short: "Starts a shell session in the container for a service. Uses web service by default.",
	Long:  `Starts a shell session in the container for a service. Uses web service by default. To start a shell session for another service, run "ddev ssh --service <service>`,
	Example: `ddev ssh
ddev ssh -s sb
ddev ssh <projectname>
ddev ssh -d /var/www/html`,
	Args: cobra.MaximumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		projects, err := getRequestedProjects(args, false)
		if err != nil || len(projects) == 0 {
			util.Failed("Failed to ddev ssh: %v", err)
		}
		app := projects[0]
		instrumentationApp = app

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			util.Failed("Project is not currently running. Try 'ddev start'.")
		}

		app.DockerEnv()

		shell := "bash"
		if !nodeps.ArrayContainsString([]string{"web", "db", "dba", "solr"}, serviceType) {
			shell = "sh"
		}
		err = app.ExecWithTty(&ddevapp.ExecOpts{
			Service: serviceType,
			Cmd:     shell + " -l",
			Dir:     sshDirArg,
		})
		if err != nil {
			util.Failed("Failed to ddev ssh %s: %v", serviceType, err)
		}
	},
}

DdevSSHCmd represents the ssh command.

View Source
var DdevShareCommand = &cobra.Command{
	Use:   "share [project]",
	Short: "Share project on the internet via ngrok.",
	Long:  `Use "ddev share" or add on extra ngrok commands, like "ddev share --basic-auth username:pass1234". Although a few ngrok commands are supported directly, any ngrok flag can be added in the ngrok_args section of .ddev/config.yaml. Requires a free or paid account on ngrok.com; use the "ngrok authtoken" command to set up ngrok.`,
	Example: `ddev share
ddev share --basic-auth username:pass1234
ddev share myproject`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) > 1 {
			util.Failed("Too many arguments provided. Please use 'ddev share' or 'ddev share [projectname]'")
		}
		apps, err := getRequestedProjects(args, false)
		if err != nil {
			util.Failed("Failed to describe project(s): %v", err)
		}
		app := apps[0]

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			util.Failed("Project is not yet running. Use 'ddev start' first.")
		}

		ngrokLoc, err := exec.LookPath("ngrok")
		if ngrokLoc == "" || err != nil {
			util.Failed("ngrok not found in path, please install it, see https://ngrok.com/download")
		}
		urls := []string{app.GetWebContainerDirectHTTPURL()}

		var ngrokErr error
		for _, url := range urls {
			ngrokArgs := []string{"http"}
			ngrokArgs = append(ngrokArgs, url)
			if app.NgrokArgs != "" {
				ngrokArgs = append(ngrokArgs, strings.Split(app.NgrokArgs, " ")...)
			}
			if cmd.Flags().Changed("subdomain") {
				sub, err := cmd.Flags().GetString("subdomain")
				if err != nil {
					util.Failed("unable to get --subdomain flag: %v", err)
				}
				ngrokArgs = append(ngrokArgs, "--subdomain="+sub)
			}

			ngrokCmd := exec.Command(ngrokLoc, ngrokArgs...)
			ngrokCmd.Stdout = os.Stdout
			ngrokCmd.Stderr = os.Stderr
			err = ngrokCmd.Start()
			if err != nil {
				util.Failed("Failed to run %s %s: %v", ngrokLoc, strings.Join(ngrokArgs, " "), err)
			}
			util.Success("Running %s %s", ngrokLoc, strings.Join(ngrokArgs, " "))

			ngrokErr = ngrokCmd.Wait()

			if ngrokErr == nil {
				break
			}

			exitErr, ok := ngrokErr.(*exec.ExitError)
			if !ok {

				util.Error("ngrok exited: %v", ngrokErr)
				break
			}

			exitCode := exitErr.ExitCode()

			if exitCode != 1 {
				util.Error("ngrok exited: %v", exitErr)
				break
			}

		}
		os.Exit(0)
	},
}

DdevShareCommand contains the "ddev share" command

View Source
var DdevSnapshotCommand = &cobra.Command{
	Use:   "snapshot [projectname projectname...]",
	Short: "Create a database snapshot for one or more projects.",
	Long:  `Uses mariabackup or xtrabackup command to create a database snapshot in the .ddev/db_snapshots folder. These are compatible with server backups using the same tools and can be restored with "ddev snapshot restore".`,
	Example: `ddev snapshot
ddev snapshot --name some_descriptive_name
ddev snapshot --cleanup
ddev snapshot --cleanup -y
ddev snapshot --list
ddev snapshot --all`,
	Run: func(cmd *cobra.Command, args []string) {
		apps, err := getRequestedProjects(args, snapshotAll)
		if err != nil {
			util.Failed("Unable to get project(s) %v: %v", args, err)
		}
		if len(apps) > 0 {
			instrumentationApp = apps[0]
		}

		for _, app := range apps {
			if app.Database.Type == nodeps.Postgres && app.Database.Version == nodeps.Postgres9 {
				util.Failed("Snapshots are not supported for postgres:9")
			}
			switch {
			case snapshotList:
				listAppSnapshots(app)
			case snapshotCleanup:
				deleteAppSnapshot(app)
			default:
				createAppSnapshot(app)
			}
		}
	},
}

DdevSnapshotCommand provides the snapshot command

View Source
var DdevSnapshotRestoreCommand = &cobra.Command{
	Use:   "restore [snapshot_name]",
	Short: "Restore a project's database to the provided snapshot version.",
	Long: `Uses mariabackup command to restore a project database to a particular snapshot from the .ddev/db_snapshots folder.
Example: "ddev snapshot restore d8git_20180717203845"`,
	Run: func(cmd *cobra.Command, args []string) {
		var snapshotName string

		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Failed to find active project: %v", err)
		}
		if app.Database.Type == nodeps.Postgres && app.Database.Version == nodeps.Postgres9 {
			util.Failed("Snapshots are not supported for postgres:9")
		}

		if snapshotRestoreLatest {
			if snapshotName, err = app.GetLatestSnapshot(); err != nil {
				util.Failed("Failed to get latest snapshot of project %s: %v", app.GetName(), err)
			}
		} else {
			if len(args) != 1 {
				snapshots, err := app.ListSnapshots()
				if err != nil {
					util.Failed("Cannot list snapshots of project %s: %v", app.GetName(), err)
				}

				if len(snapshots) == 0 {
					util.Failed("No snapshots found for project %s", app.GetName())
				}

				templates := &promptui.SelectTemplates{
					Label: "{{ . | cyan }}:",
				}

				prompt := promptui.Select{
					Label:     "Snapshot",
					Items:     snapshots,
					Templates: templates,
				}

				_, snapshotName, err = prompt.Run()

				if err != nil {
					util.Failed("Prompt failed %v", err)
				}
			} else {
				snapshotName = args[0]
			}
		}

		if err := app.RestoreSnapshot(snapshotName); err != nil {
			util.Failed("Failed to restore snapshot %s for project %s: %v", snapshotName, app.GetName(), err)
		}
	},
}

DdevSnapshotRestoreCommand handles ddev snapshot restore

View Source
var DdevStopCmd = &cobra.Command{
	Use:     "stop [projectname ...]",
	Aliases: []string{"rm", "remove"},
	Short:   "Stop and remove the containers of a project. Does not lose or harm anything unless you add --remove-data.",
	Long: `Stop and remove the containers of a project. You can run 'ddev stop'
from a project directory to stop/remove that project, or you can stop/remove projects in
any directory by running 'ddev stop projectname [projectname ...]' or 'ddev stop -a'.

By default, stop is a non-destructive operation and will leave database
contents intact. It never touches your code or files directories.

To remove database contents and global listing, 
use "ddev delete" or "ddev stop --remove-data".

To snapshot the database on stop, use "ddev stop --snapshot"; A snapshot is automatically created on
"ddev stop --remove-data" unless you use "ddev stop --remove-data --omit-snapshot".
`,
	Example: `ddev stop
ddev stop proj1 proj2 proj3
ddev stop --all
ddev stop --all --stop-ssh-agent
ddev stop --remove-data`,
	Run: func(cmd *cobra.Command, args []string) {
		if createSnapshot && omitSnapshot {
			util.Failed("Illegal option combination: --snapshot and --omit-snapshot:")
		}

		selectFlag, err := cmd.Flags().GetBool("select")

		if err != nil {
			util.Failed(err.Error())
		}

		if selectFlag {
			activeProjects := ddevapp.GetActiveProjects()

			if len(activeProjects) == 0 {
				util.Warning("No project is currently running")
				os.Exit(0)
			}

			activeProjectNames := ddevapp.ExtractProjectNames(activeProjects)

			prompt := promptui.Select{
				Label: "Running projects",
				Items: activeProjectNames,
				Templates: &promptui.SelectTemplates{
					Label: "{{ . | cyan }}:",
				},
			}

			_, projectName, err := prompt.Run()

			if err != nil {
				util.Failed(err.Error())
			}

			args = append(args, projectName)
		}

		projects, err := getRequestedProjects(args, stopAll)
		if err != nil {
			util.Failed("Failed to get project(s): %v", err)
		}
		if len(projects) > 0 {
			instrumentationApp = projects[0]
		}

		for _, project := range projects {
			status, _ := project.SiteStatus()
			if status == ddevapp.SiteStopped {
				util.Success("Project %s is already stopped.", project.GetName())
			}

			doSnapshot := (createSnapshot || removeData) && !omitSnapshot
			if err := project.Stop(removeData, doSnapshot); err != nil {
				util.Failed("Failed to stop project %s: \n%v", project.GetName(), err)
			}
			if unlist {
				project.RemoveGlobalProjectInfo()
				err = ddevapp.TerminateMutagenSync(project)
				if err != nil {
					util.Warning("Unable to terminate mutagen sync for project %s", project.Name)
				}
			}

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

		if stopSSHAgent {
			if err := ddevapp.RemoveSSHAgentContainer(); err != nil {
				util.Error("Failed to remove ddev-ssh-agent: %v", err)
			}
		}
	},
}

DdevStopCmd represents the remove command

View Source
var DebugCapabilitiesCmd = &cobra.Command{
	Use:   "capabilities",
	Short: "Show capabilities of this version of ddev",
	Run: func(cmd *cobra.Command, args []string) {
		capabilities := []string{
			"multiple-dockerfiles",
			"interactive-project-selection",
			"ddev-get-yaml-interpolation",
			"config-star-yaml-merging",
			"pre-dockerfile-insertion",
			"user-env-var",
			"npm-yarn-caching",
			"exposed-ports-configuration",
			"daemon-run-configuration",
			"get-volume-db-version",
			"migrate-database",
		}
		output.UserOut.WithField("raw", capabilities).Print(strings.Join(capabilities, "\n"))
	},
}

DebugCapabilitiesCmd implements the ddev debug capabilities command

View Source
var DebugCheckDBMatch = &cobra.Command{
	Use:   "check-db-match",
	Short: "Verify that the database in the ddev-db server matches the configured type/version",
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Can't find active project: %v", err)
		}

		dbType, err := app.GetExistingDBType()
		if err != nil {
			util.Failed("Failed to check existing DB type: %v", err)
		}
		expected := app.Database.Type + ":" + app.Database.Version
		if expected != dbType {
			util.Failed("configured database type=%s but database type in volume is %s", expected, dbType)
		}
		util.Success("database in volume matches configured database: %s", expected)
	},
}

DebugCheckDBMatch verified that the DB Type/Version in container matches configured

View Source
var DebugCmd = &cobra.Command{
	Use:     "debug [command]",
	Short:   "A collection of debugging commands",
	Aliases: []string{"d", "dbg"},
	Example: `ddev debug
ddev debug mutagen sync list
ddev d mutagen sync list
ddev d capabilities`,
	Run: func(cmd *cobra.Command, args []string) {
		err := cmd.Usage()
		util.CheckErr(err)
	},
}

DebugCmd is the top-level "ddev debug" command

View Source
var DebugComposeConfigCmd = &cobra.Command{
	Use:   "compose-config [project]",
	Short: "Prints the docker-compose configuration of the current project",
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""

		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project name")
		}

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

		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get compose-config: %v", err)
		}

		app.DockerEnv()
		if err = app.WriteDockerComposeYAML(); err != nil {
			util.Failed("Failed to get compose-config: %v", err)
		}

		out, err := fileutil.ReadFileIntoString(app.DockerComposeFullRenderedYAMLPath())
		if err != nil {
			util.Failed("unable to read rendered file %s: %v", app.DockerComposeFullRenderedYAMLPath(), err)
		}
		output.UserOut.Print(strings.TrimSpace(out))
	},
}

DebugComposeConfigCmd implements the ddev debug compose-config command

View Source
var DebugConfigYamlCmd = &cobra.Command{
	Use:     "configyaml [project]",
	Short:   "Prints the project config.*.yaml usage",
	Example: "ddev debug configyaml, ddev debug configyaml <projectname>",
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""

		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project-name")
		}

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

		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get active project: %v", err)
		}
		configFiles, err := app.ReadConfig(true)
		if err != nil {
			util.Error("failed reading config for project %s: %v", app.Name, err)
		}
		output.UserOut.Printf("These config files were loaded for project %s: %v", app.Name, configFiles)

		fields := reflect.TypeOf(*app)
		values := reflect.ValueOf(*app)

		num := fields.NumField()

		for i := 0; i < num; i++ {
			field := fields.Field(i)
			v := values.Field(i)

			yaml := field.Tag.Get("yaml")
			key := strings.Split(yaml, ",")
			if v.CanInterface() && key[0] != "-" && !isZero(v) {
				output.UserOut.Printf("%s: %v", key[0], v)
			}
		}

	},
}

DebugConfigYamlCmd implements the ddev debug configyaml command

View Source
var DebugDockercheckCmd = &cobra.Command{
	Use:     "dockercheck",
	Short:   "Diagnose DDEV docker/colima setup",
	Example: "ddev debug dockercheck",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) != 0 {
			util.Failed("This command takes no additional arguments")
		}

		versionInfo := version.GetVersionInfo()
		util.Success("Docker platform: %v", versionInfo["docker-platform"])
		switch versionInfo["docker-platform"] {
		case "colima":
			p, err := exec.LookPath("colima")
			if err == nil {
				bashPath := util.FindBashPath()
				out, err := exec2.RunHostCommand(bashPath, "-c", fmt.Sprintf("%s version | awk '/colima version/ {print $3}'", p))
				out = strings.Trim(out, "\r\n ")
				if err == nil {
					util.Success("Colima version: %v", out)
				}
			}

		case "docker desktop":
			dockerutil.IsDockerDesktop()
		}
		util.Success("Using docker context: %s (%s)", dockerutil.DockerContext, dockerutil.DockerHost)
		util.Success("docker-compose: %s", versionInfo["docker-compose"])

		dockerHost := os.Getenv("DOCKER_HOST")
		if dockerHost != "" {
			util.Success("Using DOCKER_HOST=%s", dockerHost)
		}
		dockerContext := os.Getenv("DOCKER_CONTEXT")
		if dockerContext != "" {
			util.Success("Using DOCKER_CONTEXT=%s", dockerContext)
		}

		dockerVersion, err := dockerutil.GetDockerVersion()
		if err != nil {
			util.Failed("Unable to get docker version: %v", err)
		}
		util.Success("Docker version: %s", dockerVersion)
		err = dockerutil.CheckDockerVersion(dockerutil.DockerVersionConstraint)
		if err != nil {
			if err.Error() == "no docker" {
				util.Failed("Docker is not installed or the docker client is not available in the $PATH")
			} else {
				util.Warning("The docker version currently installed does not seem to meet ddev's requirements: %v", err)
			}
		}

		client := dockerutil.GetDockerClient()
		if client == nil {
			util.Failed("Unable to get docker client")
		}

		uid, _, _ := util.GetContainerUIDGid()
		_, _, err = dockerutil.RunSimpleContainer(versionconstants.GetWebImage(), "", []string{"ls", "/mnt/ddev-global-cache"}, []string{}, []string{}, []string{"ddev-global-cache" + ":/mnt/ddev-globa-cache"}, uid, true, false, nil)
		if err != nil {
			util.Warning("Unable to run simple container: %v", err)
		} else {
			util.Success("Able to run simple container that mounts a volume.")
		}

		_, _, err = dockerutil.RunSimpleContainer(versionconstants.GetWebImage(), "", []string{"curl", "-sfLI", "https://google.com"}, []string{}, []string{}, []string{"ddev-global-cache" + ":/mnt/ddev-globa-cache"}, uid, true, false, nil)
		if err != nil {
			util.Warning("Unable to run use internet inside container, many things will fail: %v", err)
		} else {
			util.Success("Able to use internet inside container.")
		}

		dockerutil.CheckAvailableSpace()
	},
}

DebugDockercheckCmd implements the ddev debug dockercheck command

View Source
var DebugDownloadImagesCmd = &cobra.Command{
	Use:     "download-images",
	Short:   "Download all images required by ddev",
	Example: "ddev debug download-images",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) != 0 {
			util.Failed("This command takes no additional arguments")
		}

		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("No active project was found: %v", err)
		}

		_, err = dockerutil.DownloadDockerComposeIfNeeded()
		if err != nil {
			util.Warning("Unable to download docker-compose: %v", err)
		}
		err = ddevapp.DownloadMutagenIfNeeded(app)
		if err != nil {
			util.Warning("Unable to download mutagen: %v", err)
		}

		app.DockerEnv()
		err = app.WriteDockerComposeYAML()
		if err != nil {
			util.Failed("Unable to WriteDockerComposeYAML(): %v", err)
		}
		err = app.PullContainerImages()
		if err != nil {
			util.Failed("Failed to debug download-images: %v", err)
		}

		util.Success("Successfully downloaded ddev images")
	},
}

DebugDownloadImagesCmd implements the ddev debug download-images command

View Source
var DebugFixCommandsCmd = &cobra.Command{
	Use:   "fix-commands",
	Short: "Fix up custom/shell commands without running ddev start",
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Can't find active project: %v", err)
		}
		err = ddevapp.PopulateCustomCommandFiles(app)
		if err != nil {
			util.Warning("Failed to populate custom command files: %v", err)
		}

		if globalconfig.DdevGlobalConfig.NoBindMounts {
			err = app.Start()
			if err != nil {
				util.Failed("Failed to restart with NoBindMounts set: %v", err)
			}
		}
	},
}

DebugFixCommandsCmd fixes up custom/shell commands without having to do a ddev start

View Source
var DebugGetVolumeDBVersion = &cobra.Command{
	Use:   "get-volume-db-version",
	Short: "Get the database type/version found in the ddev-dbserver's database volume, which may not be the same as the configured database type/version",
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Can't find active project: %v", err)
		}

		dbType, err := app.GetExistingDBType()
		if err != nil {
			util.Failed("Failed to get existing DB type/version: %v", err)
		}
		util.Success(dbType)
	},
}

DebugGetVolumeDBVersion gets the DB Type/Version in the ddev-dbserver

View Source
var DebugMigrateDatabase = &cobra.Command{
	Use:   "migrate-database",
	Short: "Migrate a mysql or mariadb database to a different dbtype:dbversion; works only with mysql and mariadb, not with postgres",
	Example: `ddev debug migrate-database mysql:8.0
ddev debug migrate-database mariadb:10.7`,
	Args: cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Can't find active project: %v", err)
		}

		newDBVersionType := args[0]
		existingDBType, err := app.GetExistingDBType()
		if err != nil {
			util.Failed("Failed to get existing DB type/version: %v", err)
		}
		if newDBVersionType == existingDBType {
			util.Success("database type in the docker volume is already %s", newDBVersionType)
			return
		}
		if !strings.HasPrefix(newDBVersionType, nodeps.MariaDB) && !strings.HasPrefix(newDBVersionType, nodeps.MySQL) {
			util.Failed("this command can only convert between mariadb and mysql")
		}
		if !(nodeps.IsValidMariaDBVersion(newDBVersionType) || nodeps.IsValidMySQLVersion(newDBVersionType)) && !(nodeps.IsValidMariaDBVersion(existingDBType) || nodeps.IsValidMySQLVersion(existingDBType)) {
			if !util.Confirm(fmt.Sprintf("Is it OK to attempt conversion from %s to %s?\nThis will export your database, create a snapshot,\nthen destroy your current database and import into the new database type.\nIt only migrates the 'db' database", existingDBType, newDBVersionType)) {
				util.Failed("migrate-database cancelled")
			}
			err = os.MkdirAll(app.GetConfigPath(".downloads"), 0755)
			if err != nil {
				util.Failed("failed to create %s: %v", app.GetConfigPath(".downloads"), err)
			}

			status, _ := app.SiteStatus()
			if status != ddevapp.SiteRunning {
				err = app.Start()
				if err != nil {
					util.Failed("failed to start %s: %v", app.Name, err)
				}
			}

			err = app.ExportDB(app.GetConfigPath(".downloads/db.sql.gz"), "gzip", "db")
			if err != nil {
				util.Failed("failed to export-db to %s: %v", app.GetConfigPath(".downloads/db.sql.gz"), err)
			}
			err = app.Stop(true, true)
			if err != nil {
				util.Failed("failed to stop and delete %s with snapshot: %v", app.Name, err)
			}

			typeVals := strings.Split(newDBVersionType, ":")
			app.Database.Type = typeVals[0]
			app.Database.Version = typeVals[1]
			err = app.WriteConfig()
			if err != nil {
				util.Failed("failed to WriteConfig: %v", err)
			}
			err = app.Start()
			if err != nil {
				util.Failed("failed to start %s: %v", app.Name, err)
			}
			err = app.ImportDB(app.GetConfigPath(".downloads/db.sql.gz"), "", true, false, "")
			if err != nil {
				util.Failed("failed to import-db %s: %v", app.GetConfigPath(".downloads/db.sql.gz"), err)
			}
			util.Success("database was converted to %s", newDBVersionType)
			return
		}
		util.Failed("Invalid target source database type (%s) or target database type (%s)", existingDBType, newDBVersionType)
	},
}

DebugMigrateDatabase Migrates a database to a new type

View Source
var DebugMutagenCmd = &cobra.Command{
	Use:   "mutagen",
	Short: "Allows access to any mutagen command",
	FParseErrWhitelist: cobra.FParseErrWhitelist{
		UnknownFlags: true,
	},

	Long: "This simply passes through any mutagen command to the embedded mutagen itself. See Mutagen docs at https://mutagen.io/documentation/introduction",
	Example: `ddev debug mutagen sync list
ddev debug mutagen daemon stop
ddev debug mutagen
ddev d mutagen sync list
`,
	Run: func(cmd *cobra.Command, args []string) {
		mutagenPath := globalconfig.GetMutagenPath()
		_, err := os.Stat(mutagenPath)
		if err != nil {
			util.Warning("mutagen does not seem to be set up in %s, not executing command", mutagenPath)
			return
		}
		out, err := exec.RunHostCommand(mutagenPath, os.Args[3:]...)
		output.UserOut.Printf(out)
		if err != nil {
			util.Failed("Error running '%s %v': %v", globalconfig.GetMutagenPath(), args, err)
		}
	},
}

DebugMutagenCmd implements the ddev debug mutagen command

View Source
var DebugNFSMountCmd = &cobra.Command{
	Use:     "nfsmount",
	Short:   "Checks to see if nfs mounting works for current project",
	Example: "ddev debug nfsmount",
	Run: func(cmd *cobra.Command, args []string) {
		testVolume := "testnfsmount"
		containerName := "testnfscontainer"

		if len(args) != 0 {
			util.Failed("This command takes no additional arguments")
		}

		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Failed to debug nfsmount: %v", err)
		}
		oldContainer, err := dockerutil.FindContainerByName(containerName)
		if err == nil && oldContainer != nil {
			err = dockerutil.RemoveContainer(oldContainer.ID, 20)
			if err != nil {
				util.Failed("Failed to remove existing test container %s: %v", containerName, err)
			}
		}

		dockerutil.RemoveVolume(testVolume)

		nfsServerAddr, err := dockerutil.GetNFSServerAddr()
		if err != nil {
			util.Failed("failed to GetNFSServerAddr(): %v", err)
		}
		shareDir := app.AppRoot

		if runtime.GOOS == "darwin" && fileutil.IsDirectory(filepath.Join("/System/Volumes/Data", app.AppRoot)) {
			shareDir = filepath.Join("/System/Volumes/Data", app.AppRoot)
		}
		volume, err := dockerutil.CreateVolume(testVolume, "local", map[string]string{"type": "nfs", "o": fmt.Sprintf("addr=%s,hard,nolock,rw,wsize=32768,rsize=32768", nfsServerAddr), "device": ":" + dockerutil.MassageWindowsNFSMount(shareDir)}, nil)

		defer dockerutil.RemoveVolume(testVolume)
		if err != nil {
			util.Failed("Failed to create volume %s: %v", testVolume, err)
		}
		_ = volume
		uidStr, _, _ := util.GetContainerUIDGid()

		_, out, err := dockerutil.RunSimpleContainer(versionconstants.GetWebImage(), containerName, []string{"sh", "-c", "findmnt -T /nfsmount && ls -d /nfsmount/.ddev"}, []string{}, []string{}, []string{"testnfsmount" + ":/nfsmount"}, uidStr, true, false, nil)
		if err != nil {
			util.Warning("NFS does not seem to be set up yet, see debugging instructions at https://ddev.readthedocs.io/en/stable/users/install/performance/#debugging-ddev-start-failures-with-nfs_mount_enabled-true")
			util.Failed("Details: error=%v\noutput=%v", err, out)
		}
		output.UserOut.Printf(strings.TrimSpace(out))
		util.Success("")
		util.Success("Successfully accessed NFS mount of %s", app.AppRoot)
		switch {
		case app.NFSMountEnabledGlobal:
			util.Success("nfs_mount_enabled is set globally")
		case app.NFSMountEnabled:
			util.Success("nfs_mount_enabled is true in this project (%s), but is not set globally", app.Name)
		default:
			util.Warning("nfs_mount_enabled is not set either globally or in this project. \nUse `ddev config global --nfs-mount-enabled` to enable it.")
		}
	},
}

DebugNFSMountCmd implements the ddev debug nfsmount command

View Source
var DebugRefreshCmd = &cobra.Command{
	Use:   "refresh",
	Short: "Refreshes docker cache for project",
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""

		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project name")
		}

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

		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get project: %v", err)
		}

		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			if err = app.Start(); err != nil {
				util.Failed("Failed to start %s: %v", app.Name, err)
			}
		}

		app.DockerEnv()
		if err = app.WriteDockerComposeYAML(); err != nil {
			util.Failed("Failed to get compose-config: %v", err)
		}

		util.Debug("Executing docker-compose -f %s build --no-cache", app.DockerComposeFullRenderedYAMLPath())
		_, _, err = dockerutil.ComposeCmd([]string{app.DockerComposeFullRenderedYAMLPath()}, "build", "--no-cache")

		if err != nil {
			util.Failed("Failed to execute docker-compose -f %s build --no-cache: %v", err)
		}
		err = app.Restart()
		if err != nil {
			util.Failed("Failed to restart project: %v", err)
		}

		util.Success("Refreshed docker cache for project %s", app.Name)
	},
}

DebugRefreshCmd implements the ddev debug refresh command

View Source
var DebugRouterNginxConfigCmd = &cobra.Command{
	Use:     "router-nginx-config",
	Short:   "Prints the nginx config of the router",
	Example: "ddev debug router-nginx-config",
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Failed to debug router-config : %v", err)
		}

		if ddevapp.IsRouterDisabled(app) {
			util.Warning("Router is disabled by config")
			os.Exit(0)
		}

		container, _ := ddevapp.FindDdevRouter()

		if container == nil {
			util.Failed("Router is not running")
			return
		}

		stdout, _, err := dockerutil.Exec(container.ID, "cat /etc/nginx/conf.d/ddev.conf", "")

		if err != nil {
			util.Failed("Failed to run docker-gen command in ddev-router container: %v", err)
		}

		output.UserOut.Print(strings.TrimSpace(stdout))
	},
}

DebugRouterNginxConfigCmd implements the ddev debug router-config command

View Source
var DebugTestCmdCmd = &cobra.Command{
	Use:     "test",
	Short:   "Run diagnostics on ddev using the embedded test_ddev.sh script",
	Example: "ddev debug test",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) != 0 {
			util.Failed("This command takes no additional arguments")
		}
		tmpDir := os.TempDir()
		bashPath := util.FindBashPath()
		err := fileutil.CopyEmbedAssets(bundledAssets, "scripts", tmpDir)
		if err != nil {
			util.Failed("Failed to copy test_ddev.sh to %s: %v", tmpDir, err)
		}
		p := dockerutil.MassageWindowsHostMountpoint(tmpDir)
		c := []string{"-c", path.Join(p, "test_ddev.sh")}
		util.Success("Running %s %v", bashPath, c)
		err = exec.RunInteractiveCommand(bashPath, c)
		if err != nil {
			util.Failed("Failed running test_ddev.sh: %v\n. You can run it manually with `curl -sL -O https://raw.githubusercontent.com/drud/ddev/master/cmd/ddev/cmd/scripts/test_ddev.sh && bash test_ddev.sh`", err)
		}
	},
}

DebugTestCmdCmd implements the ddev debug test command

View Source
var DeleteCmd = &cobra.Command{
	Use:   "delete [projectname ...]",
	Short: "Remove all project information (including database) for an existing project",
	Long:  `Removes all ddev project information (including database) for an existing project, but does not touch the project codebase or the codebase's .ddev folder.'.`,
	Example: `ddev delete
ddev delete proj1 proj2 proj3
ddev delete --omit-snapshot proj1
ddev delete --omit-snapshot --yes proj1 proj2
ddev delete -Oy
ddev delete --all`,
	Run: func(cmd *cobra.Command, args []string) {
		if noConfirm && deleteAll {
			util.Failed("Sorry, it's not possible to use flags --all and --yes together")
		}
		projects, err := getRequestedProjects(args, deleteAll)
		if err != nil {
			util.Failed("Failed to get project(s): %v", err)
		}
		if len(projects) > 0 {
			instrumentationApp = projects[0]
		}

		for _, project := range projects {
			if !noConfirm {
				prompt := "OK to delete this project and its database?\n  %s in %s\nThe code and its .ddev directory will not be touched.\n"
				if !omitSnapshot {
					prompt = prompt + "A database snapshot will be made before the database is deleted.\n"
				}
				if !util.Confirm(fmt.Sprintf(prompt+"OK to delete %s?", project.Name, project.AppRoot, project.Name)) {
					continue
				}
			}

			status, _ := project.SiteStatus()
			if status != ddevapp.SiteRunning && !omitSnapshot {
				util.Warning("project must be started to do the snapshot")
				err = project.Start()
				if err != nil {
					util.Failed("Failed to start project %s: %v", project.Name, err)
				}
			}
			if err := project.Stop(true, !omitSnapshot); err != nil {
				util.Failed("Failed to remove project %s: \n%v", project.GetName(), err)
			}
		}
	},
}

DeleteCmd provides the delete command

View Source
var DeleteImagesCmd = &cobra.Command{
	Use:   "images",
	Short: "Deletes drud/ddev-* docker images not in use by current ddev version",
	Long:  "with --all it deletes all drud/ddev-* docker images",
	Example: `ddev delete images
ddev delete images -y
ddev delete images --all`,

	Args: cobra.NoArgs,
	Run: func(cmd *cobra.Command, args []string) {

		deleteImagesNocConfirm, _ := cmd.Flags().GetBool("yes")
		if !deleteImagesNocConfirm {
			if !util.Confirm("Deleting unused ddev images. \nThis is a non-destructive operation, \nbut it may require that the images be downloaded again when you need them. \nOK to continue?") {
				os.Exit(1)
			}
		}
		util.Success("Powering off ddev to avoid conflicts")
		ddevapp.PowerOff()

		deleteAllImages, _ := cmd.Flags().GetBool("all")

		err := deleteDdevImages(deleteAllImages)
		if err != nil {
			util.Failed("Failed to delete image", err)
		}
		util.Success("All ddev images discovered were deleted.")
	},
}

DeleteImagesCmd implements the ddev delete images command

View Source
var DescribeCommand = &cobra.Command{
	Use:     "describe [projectname]",
	Aliases: []string{"status", "st", "desc"},
	Short:   "Get a detailed description of a running ddev project.",
	Long: `Get a detailed description of a running ddev project. Describe provides basic
information about a ddev project, including its name, location, url, and status.
It also provides details for MySQL connections, and connection information for
additional services like MailHog and phpMyAdmin. You can run 'ddev describe' from
a project directory to describe that project, or you can specify a project to describe by
running 'ddev describe <projectname>'.`,
	Example: "ddev describe\nddev describe <projectname>\nddev status\nddev st",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) > 1 {
			util.Failed("Too many arguments provided. Please use 'ddev describe' or 'ddev describe [projectname]'")
		}

		apps, err := getRequestedProjects(args, false)
		if err != nil {
			util.Failed("Failed to describe project(s): %v", err)
		}
		app := apps[0]

		if err := ddevapp.CheckForMissingProjectFiles(app); err != nil {
			util.Failed("Failed to describe %s: %v", app.Name, err)
		}

		desc, err := app.Describe(false)
		if err != nil {
			util.Failed("Failed to describe project %s: %v", app.Name, err)
		}

		renderedDesc, err := renderAppDescribe(app, desc)
		util.CheckErr(err)
		output.UserOut.WithField("raw", desc).Print(renderedDesc)
	},
}

DescribeCommand represents the `ddev config` command

View Source
var ExportDBCmd = &cobra.Command{
	Use:   "export-db [project]",
	Short: "Dump a database to a file or to stdout",
	Long:  `Dump a database to a file or to stdout`,
	Example: `ddev export-db --file=/tmp/db.sql.gz
ddev export-db -f /tmp/db.sql.gz
ddev export-db --gzip=false --file /tmp/db.sql
ddev export-db > /tmp/db.sql.gz
ddev export-db --gzip=false > /tmp/db.sql
ddev export-db myproject --gzip=false --file=/tmp/myproject.sql
ddev export-db someproject --gzip=false --file=/tmp/someproject.sql `,
	Args: cobra.RangeArgs(0, 1),
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
	Run: func(cmd *cobra.Command, args []string) {
		projects, err := getRequestedProjects(args, false)
		if err != nil {
			util.Failed("Unable to get project(s): %v", err)
		}

		app := projects[0]
		status, _ := app.SiteStatus()
		if status != ddevapp.SiteRunning {
			err = app.Start()
			if err != nil {
				util.Failed("Failed to start app %s to import-db: %v", app.Name, err)
			}
		}

		compressionType := ""
		if doGzip {
			compressionType = "gzip"
		}
		if doBzip2 {
			compressionType = "bzip2"
		}

		if doXz {
			compressionType = "xz"
		}

		err = app.ExportDB(outFileName, compressionType, exportTargetDB)
		if err != nil {
			util.Failed("Failed to export database for %s: %v", app.GetName(), err)
		}
	},
}

ExportDBCmd is the `ddev export-db` command.

View Source
var Get = &cobra.Command{
	Use:   "get <addonOrURL> [project]",
	Short: "Get/Download a 3rd party add-on (service, provider, etc.)",
	Long:  `Get/Download a 3rd party add-on (service, provider, etc.). This can be a github repo, in which case the latest release will be used, or it can be a link to a .tar.gz in the correct format (like a particular release's .tar.gz) or it can be a local directory. Use 'ddev get --list' or 'ddev get --list --all' to see a list of available add-ons. Without --all it shows only official ddev add-ons.`,
	Example: `ddev get drud/ddev-drupal9-solr
ddev get https://github.com/drud/ddev-drupal9-solr/archive/refs/tags/v0.0.5.tar.gz
ddev get /path/to/package
ddev get /path/to/tarball.tar.gz
ddev get --list
ddev get --list --all
`,
	Run: func(cmd *cobra.Command, args []string) {
		officialOnly := true
		verbose := false

		if cmd.Flag("list").Changed {
			if cmd.Flag("all").Changed {
				officialOnly = false
			}
			repos, err := listAvailable(officialOnly)
			if err != nil {
				util.Failed("Failed to list available add-ons: %v", err)
			}
			if len(repos) == 0 {
				util.Warning("No ddev add-ons found with GitHub topic 'ddev-get'.")
				return
			}
			out := renderRepositoryList(repos)
			output.UserOut.WithField("raw", repos).Print(out)
			return
		}

		if cmd.Flags().Changed("verbose") {
			verbose = true
		}

		if len(args) < 1 {
			util.Failed("You must specify an add-on to download")
		}
		bash := util.FindBashPath()
		apps, err := getRequestedProjects(args[1:], false)
		if err != nil {
			util.Failed("Unable to get project(s) %v: %v", args, err)
		}
		if len(apps) == 0 {
			util.Failed("No project(s) found")
		}
		app := apps[0]
		err = os.Chdir(app.AppRoot)
		if err != nil {
			util.Failed("Unable to change directory to project root %s: %v", app.AppRoot, err)
		}
		app.DockerEnv()
		sourceRepoArg := args[0]
		extractedDir := ""
		parts := strings.Split(sourceRepoArg, "/")
		tarballURL := ""
		var cleanup func()
		argType := ""
		owner := ""
		repo := ""
		switch {

		case fileutil.IsDirectory(sourceRepoArg):

			extractedDir = sourceRepoArg
			argType = "directory"

		case fileutil.FileExists(sourceRepoArg) && (strings.HasSuffix(filepath.Base(sourceRepoArg), "tar.gz") || strings.HasSuffix(filepath.Base(sourceRepoArg), "tar") || strings.HasSuffix(filepath.Base(sourceRepoArg), "tgz")):

			extractedDir, cleanup, err = archive.ExtractTarballWithCleanup(sourceRepoArg, true)
			if err != nil {
				util.Failed("Unable to extract %s: %v", sourceRepoArg, err)
			}
			argType = "tarball"
			defer cleanup()

		case len(parts) == 2:
			owner = parts[0]
			repo = parts[1]
			ctx := context.Background()

			client := getGithubClient(ctx)
			releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, &github.ListOptions{})
			if err != nil {
				var rate github.Rate
				if resp != nil {
					rate = resp.Rate
				}
				util.Failed("Unable to get releases for %v: %v\nresp.Rate=%v", repo, err, rate)
			}
			if len(releases) == 0 {
				util.Failed("No releases found for %v", repo)
			}
			tarballURL = releases[0].GetTarballURL()
			argType = "github"
			fallthrough

		default:
			if tarballURL == "" {
				tarballURL = sourceRepoArg
			}
			extractedDir, cleanup, err = archive.DownloadAndExtractTarball(tarballURL, true)
			if err != nil {
				util.Failed("Unable to download %v: %v", sourceRepoArg, err)
			}
			defer cleanup()
		}

		yamlFile := filepath.Join(extractedDir, "install.yaml")
		yamlContent, err := fileutil.ReadFileIntoString(yamlFile)
		if err != nil {
			util.Failed("Unable to read %v: %v", yamlFile, err)
		}
		var s installDesc
		err = yaml.Unmarshal([]byte(yamlContent), &s)
		if err != nil {
			util.Failed("Unable to parse %v: %v", yamlFile, err)
		}

		yamlMap := make(map[string]interface{})
		for name, f := range s.YamlReadFiles {
			f := os.ExpandEnv(string(f))
			fullpath := filepath.Join(app.GetAppRoot(), f)

			yamlMap[name], err = util.YamlFileToMap(fullpath)
			if err != nil {
				util.Failed("unable to import yaml file %s: %v", fullpath, err)
			}
		}
		for k, v := range map[string]string{"DdevGlobalConfig": globalconfig.GetGlobalConfigPath(), "DdevProjectConfig": app.GetConfigPath("config.yaml")} {
			yamlMap[k], err = util.YamlFileToMap(v)
			if err != nil {
				util.Failed("unable to read file %s", v)
			}
		}

		dict, err := util.YamlToDict(yamlMap)
		if err != nil {
			util.Failed("Unable to YamlToDict: %v", err)
		}
		if len(s.PreInstallActions) > 0 {
			util.Success("\nExecuting pre-install actions:")
		}
		for i, action := range s.PreInstallActions {
			err = processAction(action, dict, bash, verbose)
			if err != nil {
				desc := getDdevDescription(action)
				if err != nil {
					if !verbose {
						util.Failed("could not process pre-install action (%d) '%s'. For more detail use ddev get --verbose", i, desc)
					} else {
						util.Failed("could not process pre-install action (%d) '%s'; error=%v\n action=%s", i, desc, err, action)
					}
				}
			}
		}

		if len(s.ProjectFiles) > 0 {
			util.Success("\nInstalling project-level components:")
		}
		for _, file := range s.ProjectFiles {
			file := os.ExpandEnv(file)
			src := filepath.Join(extractedDir, file)
			dest := app.GetConfigPath(file)
			if err = fileutil.CheckSignatureOrNoFile(dest, nodeps.DdevFileSignature); err == nil {
				err = copy.Copy(src, dest)
				if err != nil {
					util.Failed("Unable to copy %v to %v: %v", src, dest, err)
				}
				util.Success("%c %s", '\U0001F44D', file)
			} else {
				util.Warning("NOT overwriting file/directory %s. The #ddev-generated signature was not found in the file, so it will not be overwritten. You can just remove the file and use ddev get again if you want it to be replaced: %v", dest, err)
			}
		}
		globalDotDdev := filepath.Join(globalconfig.GetGlobalDdevDir())
		if len(s.GlobalFiles) > 0 {
			util.Success("\nInstalling global components:")
		}
		for _, file := range s.GlobalFiles {
			file := os.ExpandEnv(file)
			src := filepath.Join(extractedDir, file)
			dest := filepath.Join(globalDotDdev, file)

			if err = fileutil.CheckSignatureOrNoFile(dest, nodeps.DdevFileSignature); err == nil {
				err = copy.Copy(src, dest)
				if err != nil {
					util.Failed("Unable to copy %v to %v: %v", src, dest, err)
				}
				util.Success("%c %s", '\U0001F44D', file)
			} else {
				util.Warning("NOT overwriting file/directory %s. The #ddev-generated signature was not found in the file, so it will not be overwritten. You can just remove the file and use ddev get again if you want it to be replaced: %v", dest, err)
			}
		}
		origDir, _ := os.Getwd()

		defer os.Chdir(origDir)
		err = os.Chdir(app.GetConfigPath(""))
		if err != nil {
			util.Failed("Unable to chdir to %v: %v", app.GetConfigPath(""), err)
		}

		if len(s.PostInstallActions) > 0 {
			util.Success("\nExecuting post-install actions:")
		}
		for i, action := range s.PostInstallActions {
			err = processAction(action, dict, bash, verbose)
			desc := getDdevDescription(action)
			if err != nil {
				if !verbose {
					util.Failed("could not process post-install action (%d) '%s'", i, desc)
				} else {
					util.Failed("could not process post-install action (%d) '%s': %v", i, desc, err)
				}
			}
		}

		util.Success("\nInstalled DDEV add-on %s, use `ddev restart` to enable.", sourceRepoArg)
		if argType == "github" {
			util.Success("Please read instructions for this addon at the source repo at\nhttps://github.com/%v/%v\nPlease file issues and create pull requests there to improve it.", owner, repo)
		}

	},
}

Get implements the ddev get command

View Source
var HostNameCmd = &cobra.Command{
	Use:     "hostname [hostname] [ip]",
	Example: "ddev hostname somesite.ddev.local 127.0.0.1",
	Short:   "Manage your hostfile entries.",
	Long: `Manage your hostfile entries. Managing host names has security and usability
implications and requires elevated privileges. You may be asked for a password
to allow ddev to modify your hosts file. If you are connected to the internet and using the domain ddev.site this is generally not necessary, because the hosts file never gets manipulated.`,
	Run: func(cmd *cobra.Command, args []string) {
		hosts, err := goodhosts.NewHosts()
		if err != nil {
			rawResult := make(map[string]interface{})
			detail := fmt.Sprintf("Could not open hosts file for reading: %v", err)
			rawResult["error"] = "READERROR"
			rawResult["full_error"] = detail
			output.UserOut.WithField("raw", rawResult).Fatal(detail)

			return
		}

		if !dockerutil.IsWSL2() {
			if hosts.Flush(); err != nil {
				rawResult := make(map[string]interface{})
				detail := fmt.Sprintf("Please use sudo or execute with administrative privileges: %v", err)
				rawResult["error"] = "WRITEERROR"
				rawResult["full_error"] = detail
				output.UserOut.WithField("raw", rawResult).Fatal(detail)

				return
			}
		}

		if removeInactive {
			if len(args) > 0 {
				util.Failed("Invalid arguments supplied. 'ddev hostname --remove-all' accepts no arguments.")
			}

			util.Warning("Attempting to remove inactive hostnames which use TLD %s", nodeps.DdevDefaultTLD)
			removeInactiveHostnames(hosts)

			return
		}

		if len(args) != 2 {
			util.Failed("Invalid arguments supplied. Please use 'ddev hostname [hostname] [ip]'")
		}

		hostname, ip := args[0], args[1]

		if removeHostName {
			removeHostname(hosts, ip, hostname)

			return
		}

		addHostname(hosts, ip, hostname)
	},
}

HostNameCmd represents the hostname command

View Source
var ImportDBCmd = &cobra.Command{
	Use:   "import-db [project]",
	Args:  cobra.RangeArgs(0, 1),
	Short: "Import a sql file into the project.",
	Long: `Import a sql file into the project.
The database dump file can be provided as a SQL dump in a .sql, .sql.gz, sql.bz2, sql.xz, .mysql, .mysql.gz, .zip, .tgz, or .tar.gz
format. For the zip and tar formats, the path to a .sql file within the archive
can be provided if it is not located at the top level of the archive. An optional target database
can also be provided; the default is the default database named "db".
Also note the related "ddev mysql" command`,
	Example: `ddev import-db
ddev import-db --src=.tarballs/junk.sql
ddev import-db --src=.tarballs/junk.sql.gz
ddev import-db --target-db=newdb --src=.tarballs/db.sql.gz
ddev import-db --src=.tarballs/db.sql.bz2
ddev import-db --src=.tarballs/db.sql.xz
ddev import-db <db.sql
ddev import-db someproject <db.sql
gzip -dc db.sql.gz | ddev import-db`,

	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
	Run: func(cmd *cobra.Command, args []string) {
		projects, err := getRequestedProjects(args, false)
		if err != nil {
			util.Failed("Unable to get project(s): %v", err)
		}

		app := projects[0]
		status, _ := app.SiteStatus()

		if status != ddevapp.SiteRunning {
			err = app.Start()
			if err != nil {
				util.Failed("Failed to start app %s to import-db: %v", app.Name, err)
			}
		}

		err = app.ImportDB(dbSource, dbExtPath, progressOption, noDrop, targetDB)
		if err != nil {
			util.Failed("Failed to import database %s for %s: %v", targetDB, app.GetName(), err)
		}
		util.Success("Successfully imported database '%s' for %v", targetDB, app.GetName())
		if noDrop {
			util.Success("Existing database '%s' was NOT dropped before importing", targetDB)
		} else {
			util.Success("Existing database '%s' was dropped before importing", targetDB)
		}
	},
}

ImportDBCmd represents the `ddev import-db` command.

View Source
var ImportFileCmd = &cobra.Command{
	Use: "import-files",
	Example: `ddev import-files --src=/path/to/files.tar.gz
ddev import-files --src=/path/to/dir
ddev import-files --src=/path/to/files.tar.xz
ddev import-files --src=/path/to/files.tar.bz2`,
	Short: "Pull the uploaded files directory of an existing project to the default public upload directory of your project.",
	Long: `Pull the uploaded files directory of an existing project to the default
public upload directory of your project. The files can be provided as a
directory path or an archive in .tar, .tar.gz, .tar.xz, .tar.bz2, .tgz, or .zip format. For the
.zip and tar formats, the path to a directory within the archive can be
provided if it is not located at the top-level of the archive. If the
destination directory exists, it will be replaced with the assets being
imported.

The destination directory can be configured in your project's config.yaml
under the upload_dir key. If no custom upload directory is defined, the app
type's default upload directory will be used.`,
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
	Args: cobra.ExactArgs(0),
	Run: func(cmd *cobra.Command, args []string) {
		app, err := ddevapp.GetActiveApp("")
		if err != nil {
			util.Failed("Failed to import files: %v", err)
		}

		var showExtPathPrompt bool
		if sourcePath == "" {

			if extPath == "" {
				showExtPathPrompt = true
			}

			promptForFileSource(&sourcePath)
		}

		importPath, isArchive, err := appimport.ValidateAsset(sourcePath, "files")
		if err != nil {
			util.Failed("Failed to import files for %s: %v", app.GetName(), err)
		}

		if isArchive && showExtPathPrompt {
			promptForExtPath(&extPath)
		}

		if err = app.ImportFiles(importPath, extPath); err != nil {
			util.Failed("Failed to import files for %s: %v", app.GetName(), err)
		}

		util.Success("Successfully imported files for %v", app.GetName())
	},
}

ImportFileCmd represents the `ddev import-db` command.

View Source
var ListCmd = &cobra.Command{
	Use:     "list",
	Short:   "List projects",
	Long:    `List projects. Shows all projects by default, shows active projects only with --active-only`,
	Aliases: []string{"l", "ls"},
	Example: `ddev list
ddev list --active-only
ddev list -A`,
	Run: func(cmd *cobra.Command, args []string) {
		ddevapp.List(activeOnly, continuous, wrapListTableText, continuousSleepTime)
	},
}

ListCmd represents the list command

View Source
var MutagenCmd = &cobra.Command{
	Use:   "mutagen [command]",
	Short: "Commands for mutagen status and sync, etc.",
	Run: func(cmd *cobra.Command, args []string) {
		err := cmd.Usage()
		util.CheckErr(err)
	},
}

MutagenCmd is the top-level "ddev debug" command

View Source
var MutagenLogsCmd = &cobra.Command{
	Use:     "logs",
	Short:   "Show mutagen logs for debugging",
	Example: `"ddev mutagen logs"`,
	Run: func(cmd *cobra.Command, args []string) {

		ddevapp.StopMutagenDaemon()
		_ = os.Setenv("MUTAGEN_LOG_LEVEL", "trace")

		sigs := make(chan os.Signal, 1)
		signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
		done := make(chan bool, 1)

		go func() {
			c := exec.Command(globalconfig.GetMutagenPath(), "daemon", "run")
			c.Stdout = os.Stdout
			c.Stderr = os.Stderr
			err := c.Run()
			if err != nil {
				util.Warning("mutagen daemon run failed with %v", err)
			}
			done <- true
		}()
		<-done

		util.Success("Completed mutagen logs, now restarting normal mutagen daemon")
		ddevapp.StartMutagenDaemon()
	},
}

MutagenLogsCmd implements the ddev mutagen logs command

View Source
var MutagenMonitorCmd = &cobra.Command{
	Use:     "monitor",
	Short:   "Monitor mutagen status",
	Example: `"ddev mutagen sync", "ddev mutagen monitor <projectname>"`,
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""
		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project-name")
		}

		if len(args) == 1 {
			projectName = args[0]
		}
		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get active project: %v", err)
		}
		if !(app.IsMutagenEnabled()) {
			util.Warning("Mutagen is not enabled on project %s", app.Name)
			return
		}
		ddevapp.MutagenMonitor(app)
	},
}

MutagenMonitorCmd implements the ddev mutagen monitor command

View Source
var MutagenResetCmd = &cobra.Command{
	Use:     "reset",
	Short:   "Reset mutagen for project",
	Long:    "Stops project, removes the mutagen docker volume",
	Example: `"ddev mutagen reset", "ddev mutagen reset <projectname>"`,
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""
		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project-name")
		}

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

		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get active project: %v", err)
		}
		if !(app.IsMutagenEnabled()) {
			util.Warning("Mutagen is not enabled on project %s", app.Name)
			return
		}

		err = app.MutagenSyncFlush()
		if err != nil {

			if !strings.Contains(err.Error(), "does not exist") {
				util.Warning("Could not flush mutagen: %v", err)
			}
		}

		err = ddevapp.MutagenReset(app)
		if err != nil {
			util.Failed("Could not reset mutagen: %v", err)
		}
		util.Success("Mutagen has been reset. You may now `ddev start` with or without mutagen enabled.")
	},
}

MutagenResetCmd implements the ddev mutagen reset command

View Source
var MutagenStatusCmd = &cobra.Command{
	Use:     "status",
	Short:   "Shows mutagen sync status",
	Aliases: []string{"st"},
	Example: `"ddev mutagen status", "ddev mutagen status <projectname>"`,
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""
		verbose := false
		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project-name")
		}

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

		if cmd.Flags().Changed("verbose") {
			verbose = true
		}
		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get active project: %v", err)
		}
		if !(app.IsMutagenEnabled()) {
			util.Warning("Mutagen is not enabled on project %s", app.Name)
			return
		}
		status, shortResult, _, err := app.MutagenStatus()

		if err != nil {
			util.Failed("unable to get mutagen status for project %s, output='%s': %v", app.Name, shortResult, err)
		}
		ok := "Mutagen: " + status
		resultOut := shortResult
		if verbose {
			fullResult, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "list", "-l", ddevapp.MutagenSyncName(app.Name))
			if err != nil {
				util.Failed("unable to get mutagen status for project %s, output='%s': %v", app.Name, fullResult, err)
			}

			resultOut = "\n" + fullResult
		}
		output.UserOut.Printf("%s: %s", ok, resultOut)
	},
}

MutagenStatusCmd implements the ddev mutagen status command

View Source
var MutagenSyncCmd = &cobra.Command{
	Use:     "sync",
	Short:   "Explicit sync for mutagen",
	Example: `"ddev mutagen sync", "ddev mutagen sync <projectname>"`,
	Run: func(cmd *cobra.Command, args []string) {
		projectName := ""
		verbose := false
		if len(args) > 1 {
			util.Failed("This command only takes one optional argument: project-name")
		}

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

		if cmd.Flags().Changed("verbose") {
			verbose = true
		}
		app, err := ddevapp.GetActiveApp(projectName)
		if err != nil {
			util.Failed("Failed to get active project: %v", err)
		}
		if !(app.IsMutagenEnabled()) {
			util.Warning("Mutagen is not enabled on project %s", app.Name)
			return
		}

		err = app.MutagenSyncFlush()
		if err != nil {
			util.Failed("Failed to flush mutagen: %v", err)
		}
		if !verbose {
			return
		}

		_, _, longResult, _ := app.MutagenStatus()
		output.UserOut.Printf("%s", longResult)
	},
}

MutagenSyncCmd implements the ddev mutagen sync command

View Source
var PoweroffCommand = &cobra.Command{
	Use:     "poweroff",
	Short:   "Completely stop all projects and containers",
	Long:    `ddev poweroff stops all projects and containers, equivalent to ddev stop -a --stop-ssh-agent`,
	Example: `ddev poweroff`,
	Args:    cobra.NoArgs,
	Aliases: []string{"powerdown"},
	Run: func(cmd *cobra.Command, args []string) {
		ddevapp.PowerOff()
	},
}

PoweroffCommand contains the "ddev share" command

View Source
var PullCmd = &cobra.Command{
	Use:   "pull",
	Short: "Pull files and database using a configured provider plugin.",
	Long: `Pull files and database using a configured provider plugin.
	Running pull will connect to the configured provider and download + import the
	database and files.`,
	Example: `ddev pull pantheon
ddev pull platform
ddev pull pantheon -y
ddev pull platform --skip-files -y
ddev pull localfile --skip-db -y
ddev pull platform --environment=PLATFORM_ENVIRONMENT=main,PLATFORMSH_CLI_TOKEN=abcdef
`,

	Args: cobra.ExactArgs(1),
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
}

PullCmd represents the `ddev pull` command.

View Source
var PushCmd = &cobra.Command{
	Use:   "push",
	Short: "push files and database using a configured provider plugin.",
	Long: `push files and database using a configured provider plugin.
	Running push will connect to the configured provider and export and upload the
	database and/or files. It is not recommended for most workflows since it is extremely dangerous to your production hosting.`,
	Example: `ddev push pantheon
ddev push platform
ddev push pantheon -y
ddev push platform --skip-files -y
ddev push acquia --skip-db -y
ddev push platform --environment=PLATFORM_ENVIRONMENT=main,PLATFORMSH_CLI_TOKEN=abcdef
`,
	Args: cobra.ExactArgs(1),
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
}

PushCmd represents the `ddev push` command.

View Source
var RestartCmd = &cobra.Command{
	Use:   "restart [projects]",
	Short: "Restart a project or several projects.",
	Long:  `Stops named projects and then starts them back up again.`,
	Example: `ddev restart
ddev restart <project1> <project2>
ddev restart --all`,
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
	Run: func(cmd *cobra.Command, args []string) {
		projects, err := getRequestedProjects(args, restartAll)
		if err != nil {
			util.Failed("Failed to get project(s): %v", err)
		}
		if len(projects) > 0 {
			instrumentationApp = projects[0]
		}

		for _, app := range projects {

			output.UserOut.Printf("Restarting project %s...", app.GetName())
			err = app.Restart()
			if err != nil {
				util.Failed("Failed to restart %s: %v", app.GetName(), err)
			}

			util.Success("Restarted %s", app.GetName())
			httpURLs, urlList, _ := app.GetAllURLs()
			if globalconfig.GetCAROOT() == "" || ddevapp.IsRouterDisabled(app) {
				urlList = httpURLs
			}
			util.Success("Your project can be reached at %s", strings.Join(urlList, " "))
		}
	},
}

RestartCmd rebuilds an apps settings

View Source
var RootCmd = &cobra.Command{
	Use:   "ddev",
	Short: "DDEV local development environment",
	Long: `Create and maintain a local web development environment.
Docs: https://ddev.readthedocs.io
Support: https://ddev.readthedocs.io/en/stable/users/support`,
	Version: versionconstants.DdevVersion,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		command := os.Args[1]

		output.LogSetUp()

		if command != "start" && command != "restart" {
			return
		}

		err := dockerutil.CheckDockerVersion(dockerutil.DockerVersionConstraint)
		if err != nil {
			if err.Error() == "no docker" {
				if os.Args[1] != "version" {
					util.Failed("Could not connect to docker. Please ensure Docker is installed and running.")
				}
			} else {
				util.Warning("The docker version currently installed does not seem to meet ddev's requirements: %v", err)
			}
		}

		updateFile := filepath.Join(globalconfig.GetGlobalDdevDir(), ".update")

		timeToCheckForUpdates, err := updatecheck.IsUpdateNeeded(updateFile, updateInterval)
		if err != nil {
			util.Warning("Could not perform update check: %v", err)
		}

		if timeToCheckForUpdates && globalconfig.IsInternetActive() {

			err = updatecheck.ResetUpdateTime(updateFile)
			if err != nil {
				util.Warning("Failed to update updatecheck file %s", updateFile)
				return
			}

			updateNeeded, updateVersion, updateURL, err := updatecheck.AvailableUpdates("drud", "ddev", versionconstants.DdevVersion)

			if err != nil {
				util.Warning("Could not check for updates. This is most often caused by a networking issue.")
				return
			}

			if updateNeeded {
				output.UserOut.Printf(util.ColorizeText(fmt.Sprintf("\n\nUpgraded DDEV %s is available!\nPlease visit %s to get the upgrade.\nFor upgrade help see %s\n\n", updateVersion, updateURL, updateDocURL), "green"))
			}
		}
	},
	PersistentPostRun: func(cmd *cobra.Command, args []string) {

		ignores := map[string]bool{"describe": true, "auth": true, "blackfire": false, "clean": true, "composer": true, "debug": true, "delete": true, "drush": true, "exec": true, "export-db": true, "get": true, "help": true, "hostname": true, "import-db": true, "import-files": true, "list": true, "logs": true, "mutagen": true, "mysql": true, "npm": true, "nvm": true, "pause": true, "php": true, "poweroff": true, "pull": true, "push": true, "service": true, "share": true, "snapshot": true, "ssh": true, "stop": true, "version": true, "xdebug": true, "xhprof": true, "yarn": true}

		if _, ok := ignores[cmd.CalledAs()]; ok {
			return
		}
		instrumentationNotSetUpWarning()

		cmdCopy := cmd
		var fullCommand = make([]string, 0)
		fullCommand = append(fullCommand, util.GetFirstWord(cmdCopy.Use))
		for cmdCopy.HasParent() {
			fullCommand = append(fullCommand, util.GetFirstWord(cmdCopy.Parent().Use))
			cmdCopy = cmdCopy.Parent()
		}
		for i := 0; i < len(fullCommand)/2; i++ {
			j := len(fullCommand) - i - 1
			fullCommand[i], fullCommand[j] = fullCommand[j], fullCommand[i]
		}

		event := ""
		if len(fullCommand) > 1 {
			event = fullCommand[1]
		}

		if globalconfig.DdevGlobalConfig.InstrumentationOptIn && versionconstants.SegmentKey != "" && globalconfig.IsInternetActive() && len(fullCommand) > 1 {
			runTime := util.TimeTrack(time.Now(), "Instrumentation")

			if instrumentationApp == nil {
				app, err := ddevapp.NewApp("", false)
				if err == nil {
					instrumentationApp = app
				}
			}

			if instrumentationApp != nil {
				instrumentationApp.SetInstrumentationAppTags()
			}
			ddevapp.SetInstrumentationBaseTags()
			ddevapp.SendInstrumentationEvents(event)
			runTime()
		}
	},
}

RootCmd represents the base command when called without any subcommands

View Source
var ServiceCmd = &cobra.Command{
	Use:   "service [command]",
	Short: "Add or remove, enable or disable extra services",
	Run: func(cmd *cobra.Command, args []string) {
		err := cmd.Usage()
		util.CheckErr(err)
	},
}

ServiceCmd is the top-level "ddev service" command

View Source
var ServiceDisable = &cobra.Command{
	Use:     "disable service [project]",
	Short:   "disable a 3rd party service",
	Long:    fmt.Sprintf(`disable a 3rd party service. The docker-compose.*.yaml will be moved from .ddev into .ddev/%s.`, disabledServicesDir),
	Example: `ddev service disable solr`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) < 1 {
			util.Failed("You must specify a service to disable")
		}
		apps, err := getRequestedProjects(args[1:], false)
		if err != nil {
			util.Failed("Unable to get project(s) %v: %v", args, err)
		}
		if len(apps) == 0 {
			util.Failed("No project(s) found")
		}
		app := apps[0]
		serviceName := args[0]
		fName := fmt.Sprintf("docker-compose.%s.yaml", serviceName)
		err = os.MkdirAll(app.GetConfigPath(disabledServicesDir), 0755)
		if err != nil {
			util.Failed("Unable to create %s: %v", app.GetConfigPath(disabledServicesDir), err)
		}

		if !fileutil.FileExists(app.GetConfigPath(fName)) {
			util.Failed("No file named %s was found in %s", fName, app.GetConfigPath(""))
		}
		err = os.Remove(app.GetConfigPath(disabledServicesDir + "/" + fName))
		if err != nil {
			if _, ok := err.(*fs.PathError); !ok {
				util.Failed("Unable to remove %s: %v", app.GetConfigPath(disabledServicesDir+"/"+fName), err)
			}
		}
		err = fileutil.CopyFile(app.GetConfigPath(fName), app.GetConfigPath(disabledServicesDir+"/"+fName))
		if err != nil {
			util.Failed("Unable to disable service %s: %v", serviceName, err)
		}
		err = os.Remove(app.GetConfigPath(fName))
		if err != nil {
			util.Failed("Unable to remove former service file %s: %v", fName, err)
		}

		util.Success("disabled service %s, use `ddev restart` to see results.", serviceName)
	},
}

ServiceDisable implements the ddev service disable command

View Source
var ServiceEnable = &cobra.Command{
	Use:     "enable service [project]",
	Short:   "Enable a 3rd party service",
	Long:    fmt.Sprintf(`Enable a 3rd party service. The service must exist as .ddev/%s/docker-compose.<service>.yaml. Note that you can use "ddev get" to obtain a service not already on your system.`, disabledServicesDir),
	Example: `ddev service enable solr`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) < 1 {
			util.Failed("You must specify a service to enable")
		}
		apps, err := getRequestedProjects(args[1:], false)
		if err != nil {
			util.Failed("Unable to get project(s) %v: %v", args, err)
		}
		if len(apps) == 0 {
			util.Failed("No project(s) found")
		}
		app := apps[0]
		serviceName := args[0]
		fName := fmt.Sprintf("docker-compose.%s.yaml", serviceName)
		if fileutil.FileExists(app.GetConfigPath(fName)) {
			util.Failed("Service %s already enabled, see %s", serviceName, fName)
		}
		if !fileutil.FileExists(app.GetConfigPath(disabledServicesDir + "/" + fName)) {
			util.Failed("No %s found in %s", fName, app.GetConfigPath(disabledServicesDir))
		}
		err = fileutil.CopyFile(app.GetConfigPath(disabledServicesDir+"/"+fName), app.GetConfigPath(fName))
		if err != nil {
			util.Failed("Unable to enable service %s: %v", serviceName, err)
		}
		util.Success("Enabled service %s, use `ddev restart` to turn it on.", serviceName)
	},
}

ServiceEnable implements the ddev service enable command

View Source
var StartCmd = &cobra.Command{
	Use:     "start [projectname ...]",
	Aliases: []string{"add"},
	Short:   "Start a ddev project.",
	Long: `Start initializes and configures the web server and database containers
to provide a local development environment. You can run 'ddev start' from a
project directory to start that project, or you can start stopped projects in
any directory by running 'ddev start projectname [projectname ...]'`,
	Example: `ddev start
ddev start <project1> <project2>
ddev start --all`,
	PreRun: func(cmd *cobra.Command, args []string) {
		dockerutil.EnsureDdevNetwork()
	},
	Run: func(cmd *cobra.Command, args []string) {

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

		err = checkDdevVersionAndOptInInstrumentation(skip)
		if err != nil {
			util.Failed(err.Error())
		}

		selectFlag, err := cmd.Flags().GetBool("select")

		if err != nil {
			util.Failed(err.Error())
		}

		if selectFlag {
			inactiveProjects, err := ddevapp.GetInactiveProjects()

			if err != nil {
				util.Failed(err.Error())
			}

			if len(inactiveProjects) == 0 {
				util.Warning("No project to start available")
				os.Exit(0)
			}

			inactiveProjectNames := ddevapp.ExtractProjectNames(inactiveProjects)

			prompt := promptui.Select{
				Label: "Projects",
				Items: inactiveProjectNames,
				Templates: &promptui.SelectTemplates{
					Label: "{{ . | cyan }}:",
				},
				StartInSearchMode: true,
				Searcher: func(input string, idx int) bool {
					return strings.Contains(inactiveProjectNames[idx], input)
				},
			}

			_, projectName, err := prompt.Run()

			if err != nil {
				util.Failed(err.Error())
			}

			args = append(args, projectName)
		}

		projects, err := getRequestedProjects(args, startAll)
		if err != nil {
			util.Failed("Failed to get project(s): %v", err)
		}
		if len(projects) > 0 {
			instrumentationApp = projects[0]
		}

		for _, project := range projects {
			if err := ddevapp.CheckForMissingProjectFiles(project); err != nil {
				util.Failed("Failed to start %s: %v", project.GetName(), err)
			}

			output.UserOut.Printf("Starting %s...", project.GetName())
			if err := project.Start(); err != nil {
				util.Failed("Failed to start %s: %v", project.GetName(), err)
			}

			util.Success("Successfully started %s", project.GetName())
			httpURLs, httpsURLs, _ := project.GetAllURLs()
			if !nodeps.IsGitpod() && (globalconfig.GetCAROOT() == "" || ddevapp.IsRouterDisabled(project)) {
				httpsURLs = httpURLs
			}
			util.Success("Project can be reached at %s", strings.Join(httpsURLs, " "))
		}
	},
}

StartCmd provides the ddev start command

ValidTypes defines the valid types, a value of true indicates it's implemented. To implement a new type add the required line to the switch statement in AssignToCommand and set it here to true, that's all. If a new type is added which is not defined here just add a new constant above and here.

Functions

func Execute

func Execute()

Execute adds all child commands to the root command sets flags appropriately. This is called by main.main(). It only needs to happen once to the rootCmd.

Types

type Flag added in v1.16.0

type Flag struct {
	Name        nameValue        // name as it appears on command line
	Shorthand   shorthandValue   // one-letter abbreviated flag
	Usage       usageValue       // help message
	Type        typeValue        // type, defaults to bool
	DefValue    defValueValue    // default value (as text); for usage message
	NoOptDefVal noOptDefValValue // default value (as text); if the flag is on the command line without any options
	Annotations annotationsValue // used by cobra.Command bash autocomplete code
}

Flag is the structure for the flags, the json from the annotation is unmarshaled into this structure. For more information see also github.com/spf13/pflag/flag.

type Flags added in v1.16.0

type Flags struct {
	CommandName string
	Script      string
	Definition  FlagsDefinition
}

Flags is the main type used to access flags and methods.

func (*Flags) AssignToCommand added in v1.16.0

func (f *Flags) AssignToCommand(command *cobra.Command) error

AssignToCommand iterates the flags and assigns it to the provided command.

func (*Flags) Init added in v1.16.0

func (f *Flags) Init(commandName, script string)

Init initializes the Flags structure.

func (*Flags) LoadFromJSON added in v1.16.0

func (f *Flags) LoadFromJSON(data string) error

LoadFromJSON imports the defs provided by the custom command as json into the flags structure.

type FlagsDefinition added in v1.16.0

type FlagsDefinition []Flag

FlagsDefinition is an array of Flag holding all defined flags of a command.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL