Skip to content

Commit

Permalink
Add configuration flags to ddev pull, fixes #405 (#1138)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewfrench committed Oct 10, 2018
1 parent bdfd80d commit c9e3cb5
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 96 deletions.
4 changes: 2 additions & 2 deletions cmd/ddev/cmd/import-db.go
Expand Up @@ -15,8 +15,8 @@ var dbExtPath string
// ImportDBCmd represents the `ddev import-db` command.
var ImportDBCmd = &cobra.Command{
Use: "import-db",
Short: "Import the database of an existing project to the dev environment.",
Long: `Import the database of an existing project to the development environment.
Short: "Pull the database of an existing project to the dev environment.",
Long: `Pull the database of an existing project to the development environment.
The database can be provided as a SQL dump in a .sql, .sql.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.`,
Expand Down
6 changes: 3 additions & 3 deletions cmd/ddev/cmd/import-files.go
Expand Up @@ -17,8 +17,8 @@ var extPath string
// ImportFileCmd represents the `ddev import-db` command.
var ImportFileCmd = &cobra.Command{
Use: "import-files",
Short: "Import the uploaded files directory of an existing project to the default public upload directory of your project.",
Long: `Import the uploaded files directory of an existing project to the default
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, .tgz, or .zip format. For the
.zip and tar formats, the path to a directory within the archive can be
Expand Down Expand Up @@ -82,7 +82,7 @@ func promptForFileSource(val *string) {
// An empty string isn't acceptable here, keep
// prompting until something is entered
for {
fmt.Print("Import path: ")
fmt.Print("Pull path: ")
*val = util.GetInput("")
if len(*val) > 0 {
break
Expand Down
84 changes: 60 additions & 24 deletions cmd/ddev/cmd/pull.go
Expand Up @@ -7,60 +7,96 @@ import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/util"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

var skipConfirmation bool
var (
// skipConfirmationArg allows a user to skip the confirmation prompt.
skipConfirmationArg bool

// skipDbArg allows a user to skip pulling the remote database.
skipDbArg bool

// skipFilesArg allows a user to skip pulling the remote file archive.
skipFilesArg bool

// skipImportArg allows a user to pull remote assets, but not import them into the project.
skipImportArg bool

// envArg allows a user to override the provider environment being pulled.
envArg string
)

// PullCmd represents the `ddev pull` command.
var PullCmd = &cobra.Command{
Use: "pull",
Short: "Import files and database using a configured provider plugin.",
Long: `Import files and database using a configured provider plugin.
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
latest backups.`,
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
err := cmd.Usage()
util.CheckErr(err)
os.Exit(0)
}
dockerutil.EnsureDdevNetwork()
},
Run: func(cmd *cobra.Command, args []string) {
appImport(skipConfirmation)
appPull(skipConfirmationArg)
},
}

func appImport(skipConfirmation bool) {
func appPull(skipConfirmation bool) {
app, err := ddevapp.GetActiveApp("")

if err != nil {
util.Failed("appImport failed: %v", err)
util.Failed("Pull failed: %v", err)
}

if !skipConfirmation {
// Unfortunately we cannot use util.Warning here as it automatically adds a newline, which is awkward when dealing with prompts.
d := color.New(color.FgYellow)
_, err = d.Printf("You're about to delete the current database and files and replace with a fresh import. Would you like to continue (y/N): ")
util.CheckErr(err)
if !util.AskForConfirmation() {
util.Warning("Import cancelled.")
// If we're not performing the import step, we won't be deleting the existing db or files.
if !skipConfirmation && !skipImportArg && os.Getenv("DRUD_NONINTERACTIVE") == "" {
// Only warn the user about relevant risks.
var message string
if skipDbArg && skipFilesArg {
util.Warning("Both database and files import steps skipped.")
return
} else if !skipDbArg && skipFilesArg {
message = "database"
} else if !skipFilesArg && skipDbArg {
message = "files"
} else {
message = "database and files"
}

util.Warning("You're about to delete the current %s and replace with the results of a fresh pull.", message)
resp := strings.ToLower(util.Prompt("Would you like to continue? [Y/n]", "yes"))
if resp != "y" && resp != "yes" {
util.Warning("Pull cancelled.")
os.Exit(2)
}
}

err = app.Import()
provider, err := app.GetProvider()
if err != nil {
util.Failed("Could not perform import: %v", err)
util.Failed("Failed to get provider: %v", err)
}

pullOpts := &ddevapp.PullOptions{
SkipDb: skipDbArg,
SkipFiles: skipFilesArg,
SkipImport: skipImportArg,
Environment: envArg,
}

if err := app.Pull(provider, pullOpts); err != nil {
util.Failed("Pull failed: %v", err)
}

util.Success("Successfully Imported.")
util.Success("Your project can be reached at %s", strings.Join(app.GetAllURLs(), ", "))
util.Success("Pull succeeded.")
}

func init() {
PullCmd.Flags().BoolVarP(&skipConfirmation, "skip-confirmation", "y", false, "Skip confirmation step.")
PullCmd.Flags().BoolVarP(&skipConfirmationArg, "skip-confirmation", "y", false, "Skip confirmation step")
PullCmd.Flags().BoolVar(&skipDbArg, "skip-db", false, "Skip pulling database archive")
PullCmd.Flags().BoolVar(&skipFilesArg, "skip-files", false, "Skip pulling file archive")
PullCmd.Flags().BoolVar(&skipImportArg, "skip-import", false, "Downloads file and/or database archives, but does not import them")
PullCmd.Flags().StringVar(&envArg, "env", "", "Overrides the default provider environment being pulled")
RootCmd.AddCommand(PullCmd)
}
4 changes: 4 additions & 0 deletions docs/users/providers/drud-s3.md
Expand Up @@ -22,4 +22,8 @@ If you have ddev installed, and have an active DDEV-Live account, you can follow

4. Run `ddev pull`. The ddev environment will spin up, download the latest backup, and import the database and files into the ddev environment. You should now be able to access the project locally.

### Imports

Running `ddev pull` will connect to Drud-S3 to find the latest versions of the database and files backups from the specified environment. If new versions are available, they are downloaded and stored in ~/.ddev/drud-s3. If the stored copies there are the latest copies, ddev will use these cached copies instead of downloading them again. To skip downloading and importing either file or database assets, use the `--skip-files` and `--skip-db` flags. Use the `--env` flag to specify the Drud-S3 environment being pulled from.

_**Note for WordPress Users:** In order for your local project to load file assets from your local environment rather than the Drud-S3 environment it was pulled from, the URL of the project must be changed in the database by performing a search and replace. ddev provides an example `wp search-replace` as a post-pull hook in the config.yaml for your project. It is recommended to populate and uncomment this example so that replacement is done any time a backup is pulled from Drud-S3._
2 changes: 1 addition & 1 deletion docs/users/providers/pantheon.md
Expand Up @@ -50,7 +50,7 @@ After you copy your Pantheon project’s codebase locally, you can use `ddev con

### Imports

Running `ddev pull` will connect to Pantheon through their API to find the latest versions of the database and files backups from the specified environment. If new versions are available on Pantheon, they are downloaded and stored in ~/.ddev/pantheon. If the stored copies there are the latest copies, ddev will use these cached copies instead of downloading them again.
Running `ddev pull` will connect to Pantheon through their API to find the latest versions of the database and files backups from the specified environment. If new versions are available on Pantheon, they are downloaded and stored in ~/.ddev/pantheon. If the stored copies there are the latest copies, ddev will use these cached copies instead of downloading them again. To skip downloading and importing either file or database assets, use the `--skip-files` and `--skip-db` flags. Use the `--env` flag to specify the Pantheon environment being pulled from.

_**Note for WordPress Users:** In order for your local project to load file assets from your local environment rather than the Pantheon environment it was pulled from, the URL of the project must be changed in the database by performing a search and replace. ddev provides an example `wp search-replace` as a post-pull hook in the config.yaml for your project. It is recommended to populate and uncomment this example so that replacement is done any time a backup is pulled from Pantheon._

Expand Down
2 changes: 1 addition & 1 deletion pkg/ddevapp/config.go
Expand Up @@ -56,7 +56,7 @@ type Provider interface {
Write(string) error
Read(string) error
Validate() error
GetBackup(string) (fileLocation string, importPath string, err error)
GetBackup(string, string) (fileLocation string, importPath string, err error)
}

// init() is for testing situations only, allowing us to override the default webserver type
Expand Down
75 changes: 51 additions & 24 deletions pkg/ddevapp/ddevapp.go
Expand Up @@ -264,7 +264,7 @@ func (app *DdevApp) ImportDB(imPath string, extPath string) error {
extPathPrompt = true
}
output.UserOut.Println("Provide the path to the database you wish to import.")
fmt.Print("Import path: ")
fmt.Print("Pull path: ")

imPath = util.GetInput("")
}
Expand Down Expand Up @@ -401,46 +401,73 @@ func (app *DdevApp) SiteStatus() string {
return siteStatus
}

// Import performs an import from the a configured provider plugin, if one exists.
func (app *DdevApp) Import() error {
provider, err := app.GetProvider()
if err != nil {
return err
}
// PullOptions allows for customization of the pull process.
type PullOptions struct {
SkipDb bool
SkipFiles bool
SkipImport bool
Environment string
}

// Pull performs an import from the a configured provider plugin, if one exists.
func (app *DdevApp) Pull(provider Provider, opts *PullOptions) error {
var err error

err = provider.Validate()
if err != nil {
return err
}

if app.SiteStatus() != SiteRunning {
output.UserOut.Println("Site is not currently running. Starting site before performing import.")
util.Warning("Project is not currently running. Starting project before performing pull.")
err = app.Start()
if err != nil {
return err
}
}

fileLocation, importPath, err := provider.GetBackup("database")
if err != nil {
return err
}
if opts.SkipDb {
output.UserOut.Println("Skipping database pull.")
} else {
output.UserOut.Println("Downloading database...")
fileLocation, importPath, err := provider.GetBackup("database", opts.Environment)
if err != nil {
return err
}

output.UserOut.Println("Importing database...")
err = app.ImportDB(fileLocation, importPath)
if err != nil {
return err
}
output.UserOut.Printf("Database downloaded to: %s", fileLocation)

fileLocation, importPath, err = provider.GetBackup("files")
if err != nil {
return err
if opts.SkipImport {
output.UserOut.Println("Skipping database import.")
} else {
output.UserOut.Println("Importing database...")
err = app.ImportDB(fileLocation, importPath)
if err != nil {
return err
}
}
}

output.UserOut.Println("Importing files...")
err = app.ImportFiles(fileLocation, importPath)
if err != nil {
return err
if opts.SkipFiles {
output.UserOut.Println("Skipping files pull.")
} else {
output.UserOut.Println("Downloading file archive...")
fileLocation, importPath, err := provider.GetBackup("files", opts.Environment)
if err != nil {
return err
}

output.UserOut.Printf("File archive downloaded to: %s", fileLocation)

if opts.SkipImport {
output.UserOut.Println("Skipping files import.")
} else {
output.UserOut.Println("Importing files...")
err = app.ImportFiles(fileLocation, importPath)
if err != nil {
return err
}
}
}

return nil
Expand Down
6 changes: 3 additions & 3 deletions pkg/ddevapp/pantheon_test.go
Expand Up @@ -118,11 +118,11 @@ func TestPantheonBackupLinks(t *testing.T) {
provider.EnvironmentName = pantheonTestEnvName

// Ensure GetBackup triggers an error for unknown backup types.
_, _, err = provider.GetBackup(util.RandString(8))
_, _, err = provider.GetBackup(util.RandString(8), "")
assert.Error(err)

// Ensure we can get a
backupLink, importPath, err := provider.GetBackup("database")
backupLink, importPath, err := provider.GetBackup("database", "")

assert.Equal(importPath, "")
assert.Contains(backupLink, "database.sql.gz")
Expand Down Expand Up @@ -171,7 +171,7 @@ func TestPantheonPull(t *testing.T) {
// Ensure we can do a pull on the configured site.
app, err = GetActiveApp("")
assert.NoError(err)
err = app.Import()
err = app.Pull(&provider, &PullOptions{})
assert.NoError(err)
err = app.Down(true, false)
assert.NoError(err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/ddevapp/providerDefault.go
Expand Up @@ -54,6 +54,6 @@ func (p *DefaultProvider) Validate() error {
}

// GetBackup provides a no-op for the GetBackup operation.
func (p *DefaultProvider) GetBackup(backupType string) (fileLocation string, importPath string, err error) {
func (p *DefaultProvider) GetBackup(backupType, environment string) (fileLocation string, importPath string, err error) {
return "", "", nil
}

0 comments on commit c9e3cb5

Please sign in to comment.