Skip to content

Commit

Permalink
Add default database "mysql" to mysql_user (ansible-collections#266)
Browse files Browse the repository at this point in the history
* Add default database "mysql" to mysql_user

Since permissions are stored in the "mysql" database anyway this should not change the behaviour of the module. But replication / binlog filters which rely on the current database will be able to filter the statements correctly afterwards. Prior to this change they were not executed in any database context and could not be filtered in any way by the existing methods in MySQL.

* Added changelog fragment

* Update changelogs/fragments/266-default-database-for-mysql-user

Thanks!

Co-authored-by: Andrew Klychkov <[email protected]>

* Update mysql_user.py

Make the change a configureable boolean

* Update 266-default-database-for-mysql-user

update changelog fragment

* Update 266-default-database-for-mysql-user

it´s not a bugfix anymore

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* renamed new option to force_context
enhanced description
added tests

* fixed changelog

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* Update plugins/modules/mysql_user.py

Co-authored-by: Andrew Klychkov <[email protected]>

* added more tests

* removed first test attempts again (from issue-28.yml)
created new tests for testing with and without replication

* added force_context: no testing

* forgot to add the new part to main.yml

* found a copy&paste issue

* fix include naming

* Made sure the tests work in local testing

* MariaDB handles online replication filters differently

* fix changelog

* Update changelogs/fragments/266-default-database-for-mysql-user.yml

Co-authored-by: Andrew Klychkov <[email protected]>

* Update changelogs/fragments/266-default-database-for-mysql-user.yml

Co-authored-by: Andrew Klychkov <[email protected]>

Co-authored-by: Andrew Klychkov <[email protected]>
  • Loading branch information
d-rupp and Andersson007 authored Jan 10, 2022
1 parent 9c575b4 commit f5e8fbb
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 0 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/266-default-database-for-mysql-user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- mysql_user - added the ``force_context`` boolean option to set the default database context for the queries to be the ``mysql`` database. This way replication/binlog filters can catch the statements (https://github.com/ansible-collections/community.mysql/issues/265).
16 changes: 16 additions & 0 deletions plugins/modules/mysql_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@
- Whether binary logging should be enabled or disabled for the connection.
type: bool
default: yes
force_context:
description:
- Sets the С(mysql) system database as context for the executed statements (it will be used
as a database to connect to). Useful if you use binlog / replication filters in MySQL as
per default the statements can not be caught by a binlog / replication filter, they require
a database to be set to work, otherwise the replication can break down.
- See U(https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#option_mysqld_binlog-ignore-db)
for a description on how binlog filters work (filtering on the primary).
- See U(https://dev.mysql.com/doc/refman/8.0/en/replication-options-replica.html#option_mysqld_replicate-ignore-db)
for a description on how replication filters work (filtering on the replica).
type: bool
default: no
version_added: '3.1.0'
state:
description:
- Whether the user should exist.
Expand Down Expand Up @@ -341,6 +354,7 @@ def main():
plugin_hash_string=dict(default=None, type='str'),
plugin_auth_string=dict(default=None, type='str'),
resource_limits=dict(type='dict'),
force_context=dict(type='bool', default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
Expand All @@ -366,6 +380,8 @@ def main():
ssl_ca = module.params["ca_cert"]
check_hostname = module.params["check_hostname"]
db = ''
if module.params["force_context"]:
db = 'mysql'
sql_log_bin = module.params["sql_log_bin"]
plugin = module.params["plugin"]
plugin_hash_string = module.params["plugin_hash_string"]
Expand Down
165 changes: 165 additions & 0 deletions tests/integration/targets/test_mysql_replication/tasks/issue-265.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
- name: alias mysql command to include default options
set_fact:
mysql_command: "mysql -u{{ mysql_user }} -p{{ mysql_password }} --protocol=tcp"

- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'
block:

# start replica so it is available for testing

- name: Start replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
register: result

- assert:
that:
- result is changed
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]

- name: Drop {{ user_name_1 }} if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
state: absent
ignore_errors: yes

# First test
# check if user creation works with force_context and is replicated
- name: create user with force_context
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
force_context: yes

- name: attempt connection on replica1 with newly created user (expect success)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: 127.0.0.1
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes

- assert:
that:
- result is succeeded

- name: Drop user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
state: absent
force_context: yes

- name: attempt connection on replica with freshly removed user (expect failure)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: 127.0.0.1
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes

- assert:
that:
- result is failed

# Prepare replica1 for testing with a replication filter in place
# Stop replication, create a filter and restart replication on replica1.
- name: Stop replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
register: result

- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]

- name: Create replication filter MySQL
shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = (mysql);\" | {{ mysql_command }} -P{{ mysql_replica1_port }}"
when: install_type == 'mysql'

- name: Create replication filter MariaDB
shell: "echo \"SET GLOBAL replicate_ignore_db = 'mysql';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}"
when: install_type == 'mariadb'

- name: Start replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: startreplica
register: result

- assert:
that:
- result is changed
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]

# Second test
# Filter in place, ready to test if user creation is filtered with force_context
- name: create user with force_context
mysql_user:
<<: *mysql_params
name: "{{ user_name_1 }}"
password: "{{ user_password_1 }}"
priv: '*.*:ALL,GRANT'
force_context: yes

- name: attempt connection on replica with newly created user (expect failure)
mysql_replication:
mode: getprimary
login_user: '{{ user_name_1 }}'
login_password: '{{ user_password_1 }}'
login_host: 127.0.0.1
login_port: '{{ mysql_replica1_port }}'
register: result
ignore_errors: yes

- assert:
that:
- result is failed

- name: Drop user
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
state: absent
force_context: yes

# restore normal replica1 operation
# Stop replication and remove the filter
- name: Stop replica
mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: stopreplica
register: result

- assert:
that:
- result is changed
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]

- name: Remove replication filter MySQL
shell: "echo \"CHANGE REPLICATION FILTER REPLICATE_IGNORE_DB = ();\" | {{ mysql_command }} -P{{ mysql_replica1_port }}"
when: install_type == 'mysql'

- name: Remove replication filter MariaDB
shell: "echo \"SET GLOBAL replicate_ignore_db = '';\" | {{ mysql_command }} -P{{ mysql_replica1_port }}"
when: install_type == 'mariadb'
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
# Initial CI tests of mysql_replication module:
- import_tasks: mysql_replication_initial.yml

# Tests of replication filters and force_context
- include: issue-265.yml

# Tests of primary_delay parameter:
- import_tasks: mysql_replication_primary_delay.yml

Expand Down
168 changes: 168 additions & 0 deletions tests/integration/targets/test_mysql_user/tasks/issue-265.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
- vars:
mysql_parameters: &mysql_params
login_user: '{{ mysql_user }}'
login_password: '{{ mysql_password }}'
login_host: 127.0.0.1
login_port: '{{ mysql_primary_port }}'

block:
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
state: absent
ignore_errors: yes

# Tests with force_context: yes
# Test user creation
- name: create mysql user {{user_name_1}}
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
force_context: yes
register: result

- name: assert output message mysql user was created
assert:
that:
- "result.changed == true"

- include: assert_user.yml user_name={{user_name_1}}

# Test user removal
- name: remove mysql user {{user_name_1}}
mysql_user:
<<: *mysql_params
name: '{{user_name_1}}'
password: '{{user_password_1}}'
state: absent
force_context: yes
register: result

- name: assert output message mysql user was removed
assert:
that:
- "result.changed == true"

# Test blank user removal
- name: create blank mysql user to be removed later
mysql_user:
<<: *mysql_params
name: ""
state: present
force_context: yes
password: 'KJFDY&D*Sfuydsgf'

- name: remove blank mysql user with hosts=all (expect changed)
mysql_user:
<<: *mysql_params
user: ""
host_all: true
state: absent
force_context: yes
register: result

- name: assert changed is true for removing all blank users
assert:
that:
- "result.changed == true"

- name: remove blank mysql user with hosts=all (expect ok)
mysql_user:
<<: *mysql_params
user: ""
host_all: true
force_context: yes
state: absent
register: result

- name: assert changed is true for removing all blank users
assert:
that:
- "result.changed == false"

- include: assert_no_user.yml user_name={{user_name_1}}

# Tests with force_context: no
# Test user creation
- name: Drop mysql user if exists
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
state: absent
ignore_errors: yes

# Tests with force_context: yes
# Test user creation
- name: create mysql user {{user_name_1}}
mysql_user:
<<: *mysql_params
name: '{{ user_name_1 }}'
password: '{{ user_password_1 }}'
state: present
force_context: yes
register: result

- name: assert output message mysql user was created
assert:
that:
- "result.changed == true"

- include: assert_user.yml user_name={{user_name_1}}

# Test user removal
- name: remove mysql user {{user_name_1}}
mysql_user:
<<: *mysql_params
name: '{{user_name_1}}'
password: '{{user_password_1}}'
state: absent
force_context: no
register: result

- name: assert output message mysql user was removed
assert:
that:
- "result.changed == true"

# Test blank user removal
- name: create blank mysql user to be removed later
mysql_user:
<<: *mysql_params
name: ""
state: present
force_context: no
password: 'KJFDY&D*Sfuydsgf'

- name: remove blank mysql user with hosts=all (expect changed)
mysql_user:
<<: *mysql_params
user: ""
host_all: true
state: absent
force_context: no
register: result

- name: assert changed is true for removing all blank users
assert:
that:
- "result.changed == true"

- name: remove blank mysql user with hosts=all (expect ok)
mysql_user:
<<: *mysql_params
user: ""
host_all: true
force_context: no
state: absent
register: result

- name: assert changed is true for removing all blank users
assert:
that:
- "result.changed == false"

- include: assert_no_user.yml user_name={{user_name_1}}
4 changes: 4 additions & 0 deletions tests/integration/targets/test_mysql_user/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,7 @@
- import_tasks: issue-64560.yaml
tags:
- issue-64560

# Test that mysql_user still works with force_context enabled (database set to "mysql")
# (https://github.com/ansible-collections/community.mysql/issues/265)
- include: issue-265.yml

0 comments on commit f5e8fbb

Please sign in to comment.