Skip to content

Commit

Permalink
Improve import-db/export-db with --target-db and --no-drop flags, fixes
Browse files Browse the repository at this point in the history
#1961, fixes #1652 (#2037)

* Add import-db --target-db and --no-drop flags, fixes #1961
* Change to drop-by-default on import
* Add targetDB to export-db
* Allow import-db and export-db to take project arg
* Use -f as shorthand for --src in import-db
  • Loading branch information
rfay committed Jan 28, 2020
1 parent 4f46f7f commit 08720f9
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 140 deletions.
30 changes: 16 additions & 14 deletions cmd/ddev/cmd/export-db.go
@@ -1,8 +1,6 @@
package cmd

import (
"os"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/util"
Expand All @@ -11,32 +9,35 @@ import (

var outFileName string
var gzipOption bool
var exportTargetDB string

// ExportDBCmd is the `ddev export-db` command.
var ExportDBCmd = &cobra.Command{
Use: "export-db",
Short: "Dump a database to stdout or to a file",
Long: `Dump a database to stdout or to a file.`,
Example: "ddev export-db >/tmp/db.sql.gz\nddev export-db --gzip=false >/tmp/db.sql\nddev export-db -f /tmp/db.sql.gz",
Use: "export-db [project]",
Short: "Dump a database to stdout or to a file",
Long: `Dump a database to stdout or to a file.`,
Example: `ddev export-db > ~/tmp/db.sql.gz
ddev export-db --gzip=false > ~/tmp/db.sql
ddev export-db -f ~/tmp/db.sql.gz
ddev export-db --gzip=false myproject > ~/tmp/myproject.sql
ddev export-db someproject > ~/tmp/someproject.sql`,
Args: cobra.RangeArgs(0, 1),
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) {
app, err := ddevapp.GetActiveApp("")
projects, err := getRequestedProjects(args, false)
if err != nil {
util.Failed("Failed to find project from which to export database: %v", err)
util.Failed("Unable to get project(s): %v", err)
}

app := projects[0]

if app.SiteStatus() != ddevapp.SiteRunning {
util.Failed("ddev can't export-db until the project is started, please start it first.")
}

err = app.ExportDB(outFileName, gzipOption)
err = app.ExportDB(outFileName, gzipOption, targetDB)
if err != nil {
util.Failed("Failed to export database for %s: %v", app.GetName(), err)
}
Expand All @@ -46,5 +47,6 @@ var ExportDBCmd = &cobra.Command{
func init() {
ExportDBCmd.Flags().StringVarP(&outFileName, "file", "f", "", "Provide the path to output the dump")
ExportDBCmd.Flags().BoolVarP(&gzipOption, "gzip", "z", true, "If provided asset is an archive, provide the path to extract within the archive.")
ExportDBCmd.Flags().StringVarP(&exportTargetDB, "target-db", "d", "db", "If provided, target-db is alternate database to export")
RootCmd.AddCommand(ExportDBCmd)
}
69 changes: 69 additions & 0 deletions cmd/ddev/cmd/export-db_test.go
@@ -0,0 +1,69 @@
package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/fileutil"
"github.com/drud/ddev/pkg/testcommon"
asrt "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"os/exec"
"path/filepath"
"testing"
)

// TestCmdExportDB does an export-db
func TestCmdExportDB(t *testing.T) {
assert := asrt.New(t)

testDir, _ := os.Getwd()
site := TestSites[0]
cleanup := site.Chdir()

app, err := ddevapp.NewApp(site.Dir, false, "")
assert.NoError(err)

defer func() {
// Make sure all databases are back to default empty
_ = app.Stop(true, false)
_ = app.Start()
cleanup()
}()

// Read in a database
inputFileName := filepath.Join(testDir, "testdata", t.Name(), "users.sql")
_ = os.MkdirAll("tmp", 0755)
// Run the import-db command with stdin coming from users.sql
command := exec.Command(DdevBin, "import-db", "-f="+inputFileName)
importDBOutput, err := command.CombinedOutput()
require.NoError(t, err, "failed import-db from %s: %v", inputFileName, importDBOutput)

_, _, err = app.Exec(&ddevapp.ExecOpts{
Service: "db",
Cmd: "mysql -e 'SHOW TABLES;'",
})
assert.NoError(err)

// Export the database and verify content of output
_ = os.MkdirAll(filepath.Join("tmp", t.Name()), 0755)
outputFileName := filepath.Join(site.Dir, "tmp", t.Name(), "output.sql")
_ = os.RemoveAll(outputFileName)
command = exec.Command(DdevBin, "export-db", "-f="+outputFileName, "--gzip=false")
byteout, err := command.CombinedOutput()
require.NoError(t, err, "byteout=%s", string(byteout))
assert.FileExists(outputFileName)
assert.True(fileutil.FgrepStringInFile(outputFileName, "13751eca-19cf-41c2-90d4-9363f3a07c45"))

// Test with named project (outside project directory)
tmpDir := testcommon.CreateTmpDir("TestCmdExportDB")
err = os.Chdir(tmpDir)
assert.NoError(err)

err = os.RemoveAll(outputFileName)
assert.NoError(err)
command = exec.Command(DdevBin, "export-db", site.Name, "-f="+outputFileName, "--gzip=false")
byteout, err = command.CombinedOutput()
assert.NoError(err, "export-db failure output=%s", string(byteout))
assert.FileExists(outputFileName)
assert.True(fileutil.FgrepStringInFile(outputFileName, "13751eca-19cf-41c2-90d4-9363f3a07c45"))
}
39 changes: 24 additions & 15 deletions cmd/ddev/cmd/import-db.go
@@ -1,8 +1,6 @@
package cmd

import (
"os"

"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/util"
Expand All @@ -11,54 +9,65 @@ import (

var dbSource string
var dbExtPath string
var targetDB string
var noDrop bool
var progressOption bool

// ImportDBCmd represents the `ddev import-db` command.
var ImportDBCmd = &cobra.Command{
Use: "import-db",
Use: "import-db [project]",
Args: cobra.RangeArgs(0, 1),
Short: "Import a sql archive into the project.",
Long: `Import a sql archive into the project.
The database can be provided as a SQL dump in a .sql, .sql.gz, .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. Note the related "ddev mysql" command`,
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/junk.sql.gz
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) {
if len(args) > 0 {
err := cmd.Usage()
util.CheckErr(err)
os.Exit(0)
}
dockerutil.EnsureDdevNetwork()
},
Run: func(cmd *cobra.Command, args []string) {
app, err := ddevapp.GetActiveApp("")
projects, err := getRequestedProjects(args, false)
if err != nil {
util.Failed("Failed to import database: %v", err)
util.Failed("Unable to get project(s): %v", err)
}

app := projects[0]

if app.SiteStatus() != 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)
err = app.ImportDB(dbSource, dbExtPath, progressOption, noDrop, targetDB)
if err != nil {
util.Failed("Failed to import database for %s: %v", app.GetName(), err)
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)
}
util.Success("Successfully imported database for %v", app.GetName())
},
}

func init() {
ImportDBCmd.Flags().StringVarP(&dbSource, "src", "", "", "Provide the path to a sql dump in .sql or tar/tar.gz/tgz/zip format")
ImportDBCmd.Flags().StringVarP(&dbSource, "src", "f", "", "Provide the path to a sql dump in .sql or tar/tar.gz/tgz/zip format")
ImportDBCmd.Flags().StringVarP(&dbExtPath, "extract-path", "", "", "If provided asset is an archive, provide the path to extract within the archive.")
ImportDBCmd.Flags().StringVarP(&targetDB, "target-db", "d", "db", "If provided asset is an archive, provide the path to extract within the archive.")
ImportDBCmd.Flags().BoolVarP(&noDrop, "no-drop", "", false, "Set if you do NOT want to drop the db before importing")
ImportDBCmd.Flags().BoolVarP(&progressOption, "progress", "p", true, "Display a progress bar during import")
RootCmd.AddCommand(ImportDBCmd)
}
29 changes: 26 additions & 3 deletions cmd/ddev/cmd/import-db_test.go
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/testcommon"
asrt "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
Expand All @@ -18,16 +19,21 @@ func TestCmdImportDB(t *testing.T) {
testDir, _ := os.Getwd()
site := TestSites[0]
cleanup := site.Chdir()
defer cleanup()
app, err := ddevapp.NewApp(site.Dir, false, "")
assert.NoError(err)
defer func() {
// Make sure all databases are back to default empty
_ = app.Stop(true, false)
_ = app.Start()
cleanup()
}()

// Make sure we start with nothing in db
out, _, err := app.Exec(&ddevapp.ExecOpts{
Service: "web",
Cmd: "mysql -e 'SHOW TABLES;'",
Cmd: "mysql -N -e 'SHOW TABLES;'",
})
assert.NoError(err)
assert.NoError(err, "mysql exec output=%s", out)
require.Equal(t, "", out)

// Set up to read from the sql import file
Expand All @@ -52,4 +58,21 @@ func TestCmdImportDB(t *testing.T) {
})
assert.NoError(err)
assert.Equal("Tables_in_db\nusers\n", out)

// Test with named project (outside project directory)
// Test with named project (outside project directory)
tmpDir := testcommon.CreateTmpDir("TestCmdExportDB")
err = os.Chdir(tmpDir)
assert.NoError(err)

// Run the import-db command with stdin coming from users.sql
byteout, err := exec.Command(DdevBin, "import-db", app.Name, "--target-db=sparedb", "-f="+inputFile).CombinedOutput()
assert.NoError(err, "failed import-db: %v (%s)", err, string(byteout))
out, _, err = app.Exec(&ddevapp.ExecOpts{
Service: "db",
Cmd: `echo "SELECT COUNT(*) FROM users;" | mysql -N sparedb`,
})
assert.NoError(err)
assert.Equal("2\n", out)

}
3 changes: 1 addition & 2 deletions cmd/ddev/cmd/sequelpro_test.go
Expand Up @@ -50,6 +50,5 @@ func TestSequelproBadApp(t *testing.T) {
// Ensure it fails if we run outside of an application root.
_, err := handleSequelProCommand(SequelproLoc)
assert.Error(err)
assert.Contains(err.Error(), "unable to determine the project")

assert.Contains(err.Error(), "Could not find a project in")
}
55 changes: 55 additions & 0 deletions cmd/ddev/cmd/testdata/TestCmdExportDB/users.sql
@@ -0,0 +1,55 @@
-- MySQL dump 10.13 Distrib 5.5.54, for debian-linux-gnu (x86_64)
--
-- Host: db Database: data
-- ------------------------------------------------------
-- Server version 5.7.17-log

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`uid` int(10) unsigned NOT NULL,
`uuid` varchar(128) CHARACTER SET ascii NOT NULL,
`langcode` varchar(12) CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`uid`),
UNIQUE KEY `user_field__uuid__value` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The base table for user entities.';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `users`
--

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
set autocommit=0;
INSERT INTO `users` VALUES (0,'13751eca-19cf-41c2-90d4-9363f3a07c45','en'),(1,'186efa0a-8aa3-4eeb-90ce-6302fb9c4e07','en');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
commit;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2017-05-31 14:03:34
4 changes: 3 additions & 1 deletion docs/users/cli-usage.md
Expand Up @@ -451,7 +451,7 @@ An important aspect of local web development is the ability to have a precise re

### Importing a database

The `ddev import-db` command is provided for importing the MySQL database for a project. Running this command will provide a prompt for you to specify the location of your database import.
The `ddev import-db` command is provided for importing the MySQL database for a project. Running this command will provide a prompt for you to specify the location of your database import. By default `ddev import-db` empties the default "db" database and then loads the provided dumpfile.

```
Expand Down Expand Up @@ -507,6 +507,8 @@ ddev import-db <mydb.sql
<h4>Database import notes</h4>

* Importing from a dumpfile via stdin will not show progress because there's no way the import can know how far along through the import it has progressed.
* Use `ddev import-db --target-db <some_database>` to import to a non-default database (other than the default "db" database). This will create the database if it doesn't exist already.
* Use `ddev import-db --no-drop` to import without first emptying the database.
* If a database already exists and the import does not specify dropping tables, the contents of the imported dumpfile will be *added* to the database. Most full database dumps do a table drop and create before loading, but if yours does not, you can drop all tables with `ddev stop --remove-data` before importing.

### Exporting a Database
Expand Down
2 changes: 1 addition & 1 deletion docs/users/faq.md
Expand Up @@ -11,7 +11,7 @@
* **Can I run ddev and also other Docker or non-Docker development environments at the same time?** Yes, you can, as long as they're configured with different ports. But it's easiest to shut down one before using the other. For example, if you use Lando for one project, do a `lando poweroff` before using ddev, and then do a `ddev poweroff` before using Lando again. If you run nginx or apache locally, just stop them before using ddev. More information is in the [troubleshooting](troubleshooting.md) section.
* **How can I contribute to DDEV-Local?** We love contributions of knowledge, support, docs, and code, and invite you to all of them. Make an issue or PR to the [main repo](https://github.com/drud/ddev). Add your external resource to [awesome-ddev](https://github.com/drud/awesome-ddev). Add your recipe or HOWTO to [ddev-contrib](https://github.com/drud/ddev-contrib). Help others in [Stack Overflow](https://stackoverflow.com/tags/ddev) or [Slack](../index.md#support) or [gitter](https://gitter.im/drud/ddev).
* **How can I show my local project to someone else?** It's often the case that we want a customer or coworker to be able to view our local environment, even if they're on a different machine, network, etc. `ddev share` (requires [ngrok](https://ngrok.com)) provides a link that anyone can view and so they can interact with your local project while you allow it. See `ddev share -h` for more information.
* **Can I use additional databases with ddev?** Yes, you can create additional databases and manually do whatever you need on them. `echo "CREATE DATABASE seconddb; GRANT ALL on seconddb.* TO 'db'@'%';" | ddev mysql -uroot -proot`. You can use `ddev mysql` for random queries, or also use the mysql client within either `ddev ssh` or `ddev ssh -s db` as well.
* **Can I use additional databases with ddev?** Yes, you can create additional databases and manually do whatever you need on them. They are automatically created if you use `ddev import-db` with `--target-db`, for example `ddev import-db --target-db=extradb --source=.tarballs/extradb.sql.gz`. You can use `ddev mysql` for random queries, or also use the mysql client within `ddev ssh` or `ddev ssh -s db` as well.
* **Can different projects communicate with each other?** Yes, this is commonly required for situations like Drupal migrations. For the web container to access the db container of another project, use `ddev-<projectname>-db` as the hostname of the other project. For example, in project1, use `mysql ddev-project2-db` to access the db server of project2. For HTTP/S communication you can 1) access the web container of project2 directly with the hostname `ddev-<project2>-web` and port 80 or 443: `curl https://ddev-project2-web` or 2) Access via the ddev router with the official hostname: `curl https://ddev-router -H Host:d7git.ddev.site`.
* **How do I make ddev match my production webserver environment?** You can change the PHP major version (currently 5.6 through 7.4) and choose between nginx+fpm (default and apache+fpm and choose the MariaDB version add [extra services like solr and memcached](extend/additional-services.md). You will not be able to make every detail match your production server, but with PHP version and webserver type you'll be close.
* **How do I completely destroy a project?** Use `ddev delete <project>` to destroy a project. (Also, `ddev stop --remove-data` will do the same thing.) By default, a `ddev snapshot` of your database is taken, but you can skip this, see `ddev delete -h` for options.
Expand Down

0 comments on commit 08720f9

Please sign in to comment.