Repository: ansible/ansible

Issue:
The mysql_db module needs to use the --one-database flag for imports
 - Bug Report

##### COMPONENT NAME

mysql_db

##### ANSIBLE VERSION

This issue was discovered with 2.4.

```
ansible 2.6.0 (devel 45b5a486c6) last updated 2018/04/04 15:11:32 (GMT -400)
  config file = /cygdrive/c/git/projects/project-bitbucket/ansible.cfg
  configured module search path = [u'/opt/ansible/library']
  ansible python module location = /opt/ansible/lib/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.14 (default, Oct 31 2017, 21:12:13) [GCC 6.4.0]
```

##### CONFIGURATION

```
ANSIBLE_PIPELINING(env: ANSIBLE_PIPELINING) = True
ANSIBLE_SSH_ARGS(env: ANSIBLE_SSH_ARGS) = -o ControlMaster=no -o ForwardAgent=yes -o IdentitiesOnly=yes
DEFAULT_LOG_PATH(env: ANSIBLE_LOG_PATH) = /cygdrive/c/git/projects/project-test/logs/2018-04-04_14-31-22_ansible.log
DEFAULT_MODULE_PATH(env: ANSIBLE_LIBRARY) = [u'/opt/ansible/library']
DEFAULT_ROLES_PATH(env: ANSIBLE_ROLES_PATH) = [u'/cygdrive/c/git/roles']
```
##### OS / ENVIRONMENT

Most likely not relevant, but full disclosure:

Ansible source:

Window 10 with Cygwin

The Linux target, a REHL AMI on AWS:

```
$ uname -a
Linux test 3.10.0-693.21.1.el7.x86_64 #1 SMP Fri Feb 23 18:54:16 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
```

Using the MySQL variant MariaDB

```
$ mysql --version
mysql  Ver 15.1 Distrib 5.5.56-MariaDB, for Linux (x86_64) using readline 5.1
```

##### SUMMARY

Our MySQL backups dump all the databases on the server into a single backup file

```
mysqldump --single-transaction --flush-logs --master-data=2 -u $USER -p$PASSWORD --max_allowed_packet=256M --all-databases | bzip2 -c > $BACKUP_PATH/mysql_backup_${DATE}.bz2
```

This results in a backup file that contains no CREATE DATABASE statements, but there is a USE <database> for each of the databases on the MySQL instance along with all of the schema and data needed to recover the database.  The Ansible module mysql_db allows you to specify the database for imports, but instead of only recovering the database specified, it recovers the entire MySQL backup, over writing any other matching database on the server, including:

- 

##### STEPS TO REPRODUCE

Dump your MySQL server using the --all-databases flag

```
mysqldump --single-transaction --flush-logs --master-data=2 -u $USER -p$PASSWORD --max_allowed_packet=256M --all-databases | bzip2 -c > $BACKUP_PATH/mysql_backup_${DATE}.bz2
```

- Create a new MySQL server, and add a couple users that do not exist and use a different root password.
- Verify the password and that the users exist, for example:

```
MariaDB [stash]> SELECT User FROM mysql.user;
+-----------+
| User      |
+-----------+
| root      |
| root      |
| root      |
| test      |
+-----------+
4 rows in set (0.00 sec)

```

- Import the database dump into that new MySQL database, making sure to specify the database

```yaml
- name: "[restore] Restore the DB"
  mysql_db:
    login_user: "{{ mysql_backup_user }}"
    login_password: "{{ mysql_backup_user_password }}"
    target: "{{ mysql_backup_archive_name }}"
    state: import
    name: "{{ restore_db_name }}"
```

##### EXPECTED RESULTS

The single database specified would be restored and the rest of the databases on the server would be untouched.

##### ACTUAL RESULTS

All of the databases in the backup have been restored.  The MySQL users have been overwritten and including the root password.

```
MariaDB [stash]> SELECT User FROM mysql.user;
+-----------+
| User      |
+-----------+
| root      |
| root      |
| root      |
| produser1 |
| produser2 |
| produser3 |
+-----------+
6 rows in set (0.00 sec)

```

##### THE REASON

The mysql_db module is using the mysql ```-D``` flag (```--database```), which only specifies the database to target:

https://dev.mysql.com/doc/refman/5.7/en/mysql-command-options.html#option_mysql_database

> 
> --database=db_name, -D db_name
> 
> The database to use. This is useful primarily in an option file.

Instead of the ```--one-database``` option:

https://dev.mysql.com/doc/refman/5.7/en/mysql-command-options.html#option_mysql_one-database

> --one-database, -o
> 
> Ignore statements except those that occur while the default database is the one named on the command line. This option is rudimentary and should be used with care. Statement filtering is based only on USE statements.
> 
> Initially, mysql executes statements in the input because specifying a database db_name on the command line is equivalent to inserting USE db_name at the beginning of the input. Then, for each USE statement encountered, mysql accepts or rejects following statements depending on whether the database named is the one on the command line. The content of the statements is immaterial.
> 

So if you modify the lib/ansible/databases/mysql_db.py file from:

```
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
    if not os.path.exists(target):
        return module.fail_json(msg="target %s does not exist on the host" % target)

    cmd = [module.get_bin_path('mysql', True)]
    # --defaults-file must go first, or errors out
...
    if not all_databases:
        cmd.append("-D")
        cmd.append(pipes.quote(db_name))

```

To:

```
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
    if not os.path.exists(target):
        return module.fail_json(msg="target %s does not exist on the host" % target)

    cmd = [module.get_bin_path('mysql', True)]
    # --defaults-file must go first, or errors out
...
    if not all_databases:
        cmd.append("--one-database %s" % pipes.quote(db_name))
```

This causes the expected behavior where only the specified database is created and modified.  The ```--one-database``` db_name format implies the ```--database``` option as well, so if you use a SQL file with no ```USE db_name;``` lines, it will still be run against the named database.


Discussion:
Files identified in the description:
* [lib/ansible/modules/database/mysql/mysql_db.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/database/mysql/mysql_db.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Xyon @bmalynovytch @michaelcoburn @oneiroi @tolland
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @bmildren
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @Alexander198961
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @Andersson007
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @kurtdavis
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/database/mysql/mysql_db.py b/lib/ansible/modules/database/mysql/mysql_db.py
--- a/lib/ansible/modules/database/mysql/mysql_db.py
+++ b/lib/ansible/modules/database/mysql/mysql_db.py
@@ -265,7 +265,7 @@ def db_import(module, host, user, password, db_name, target, all_databases, port
         cmd.append("--host=%s" % shlex_quote(host))
         cmd.append("--port=%i" % port)
     if not all_databases:
-        cmd.append("-D")
+        cmd.append("--one-database")
         cmd.append(shlex_quote(''.join(db_name)))
 
     comp_prog_path = None


Test patch: