diff --git a/.github/workflows/ansible-test-plugins.yml b/.github/workflows/ansible-test-plugins.yml index 0b6c184..5c79d2c 100644 --- a/.github/workflows/ansible-test-plugins.yml +++ b/.github/workflows/ansible-test-plugins.yml @@ -22,9 +22,9 @@ jobs: strategy: matrix: ansible: - - stable-2.16 - stable-2.17 - stable-2.18 + - stable-2.19 - devel steps: # https://github.com/ansible-community/ansible-test-gh-action @@ -44,9 +44,9 @@ jobs: fail-fast: false matrix: ansible: - - stable-2.16 - stable-2.17 - stable-2.18 + - stable-2.19 - devel db_engine_name: - mysql @@ -282,9 +282,9 @@ jobs: fail-fast: true matrix: ansible: - - stable-2.16 - stable-2.17 - stable-2.18 + - stable-2.19 - devel python: - '3.8' @@ -292,17 +292,14 @@ jobs: - '3.10' - '3.11' exclude: - - python: '3.8' - ansible: stable-2.16 - - python: '3.8' ansible: stable-2.17 - python: '3.8' ansible: devel - - python: '3.9' - ansible: stable-2.15 + - python: '3.8' + ansible: stable-2.19 - python: '3.9' ansible: stable-2.17 @@ -311,17 +308,10 @@ jobs: ansible: devel - python: '3.10' - ansible: stable-2.15 - - - python: '3.10' - ansible: stable-2.16 + ansible: stable-2.17 - python: '3.11' - ansible: stable-2.15 - - - python: '3.11' - ansible: stable-2.16 - + ansible: stable-2.17 steps: - name: >- Perform unit testing against diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 114b359..5e53c1b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,27 @@ Community MySQL and MariaDB Collection Release Notes This changelog describes changes after version 2.0.0. +v3.15.0 +======= + +Release Summary +--------------- + +This is a minor release of the ``community.mysql`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the previous release.' + +Minor Changes +------------- + +- mysql_db - Add support for ``sql_log_bin`` option (https://github.com/ansible-collections/community.mysql/issues/700). + +Bugfixes +-------- + +- mysql_query - fix a Python 2 compatibility issue caused by the addition of ``execution_time_ms`` in version 3.12 (see https://github.com/ansible-collections/community.mysql/issues/716). +- mysql_user - fix a crash (unable to parse the MySQL grant string: SET DEFAULT ROLE `somerole` FOR `someuser`@`%`) when using the ``mysql_user`` module with a DEFAULT role present in MariaDB. The DEFAULT role is now ignored by the parser (https://github.com/ansible-collections/community.mysql/issues/710). + v3.14.0 ======= diff --git a/README.md b/README.md index df2404f..89f1499 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,9 @@ Here is the table for the support timeline: ### ansible-core -- stable-2.16 - stable-2.17 - stable-2.18 +- stable-2.19 - current development version ### Python diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 79ee9cf..4b09970 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -294,6 +294,28 @@ releases: - grant_to_public.yml - release_3_14_0.yml release_date: '2025-05-23' + 3.15.0: + changes: + bugfixes: + - mysql_query - fix a Python 2 compatibility issue caused by the addition of + ``execution_time_ms`` in version 3.12 (see https://github.com/ansible-collections/community.mysql/issues/716). + - 'mysql_user - fix a crash (unable to parse the MySQL grant string: SET DEFAULT + ROLE `somerole` FOR `someuser`@`%`) when using the ``mysql_user`` module with + a DEFAULT role present in MariaDB. The DEFAULT role is now ignored by the + parser (https://github.com/ansible-collections/community.mysql/issues/710).' + minor_changes: + - mysql_db - Add support for ``sql_log_bin`` option (https://github.com/ansible-collections/community.mysql/issues/700). + release_summary: 'This is a minor release of the ``community.mysql`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the previous release.''' + fragments: + - 3.15.0.yml + - 723-myqsl_db_supports_sql_log_bin.yaml + - fix_python2_compatibility.yml + - fix_user_module_for_default_roles.yml + release_date: '2025-07-24' 3.2.0: changes: bugfixes: diff --git a/galaxy.yml b/galaxy.yml index 01e50fc..43d9aea 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: community name: mysql -version: 3.14.0 +version: 3.15.0 readme: README.md authors: - Ansible community diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index 337cc67..59f9b01 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -665,12 +665,19 @@ def privileges_get(cursor, user, host, maria_role=False): res = re.match("""GRANT (.+) ON (.+) TO .*""", grant[0]) if res is None: - # If a user has roles assigned, we'll have one of priv tuples looking like + # If a user has roles or a default role assigned, + # we'll have some of the priv tuples looking either like # GRANT `admin`@`%` TO `user1`@`localhost` + # or + # SET DEFAULT ROLE `admin`@`%` FOR `user1`@`localhost` # which will result None as res value. # As we use the mysql_role module to manipulate roles # we just ignore such privs below: - res = re.match("""GRANT (.+) TO (['`"]).*""", grant[0]) + res = re.match( + """GRANT (.+) TO (['`"]).*|SET DEFAULT ROLE (.+) FOR (['`"]).*""", + grant[0] + ) + if not maria_role and res: continue diff --git a/plugins/modules/mysql_db.py b/plugins/modules/mysql_db.py index 6ef578c..4eec625 100644 --- a/plugins/modules/mysql_db.py +++ b/plugins/modules/mysql_db.py @@ -165,6 +165,11 @@ options: type: bool default: false version_added: '3.4.0' + sql_log_bin: + description: + - Whether binary logging should be enabled or disabled for the connection. + type: bool + default: true seealso: - module: community.mysql.mysql_info @@ -633,6 +638,7 @@ def main(): config_overrides_defaults=dict(type='bool', default=False), chdir=dict(type='path'), pipefail=dict(type='bool', default=False), + sql_log_bin=dict(type='bool', default=True), ) module = AnsibleModule( @@ -683,6 +689,7 @@ def main(): config_overrides_defaults = module.params['config_overrides_defaults'] chdir = module.params['chdir'] pipefail = module.params['pipefail'] + sql_log_bin = module.params["sql_log_bin"] if chdir: try: @@ -725,6 +732,9 @@ def main(): else: module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) + if state in ['absent', 'present'] and not sql_log_bin: + cursor.execute("SET SQL_LOG_BIN=0;") + server_implementation = get_server_implementation(cursor) server_version = get_server_version(cursor) diff --git a/plugins/modules/mysql_query.py b/plugins/modules/mysql_query.py index 35beeb3..f7c900a 100644 --- a/plugins/modules/mysql_query.py +++ b/plugins/modules/mysql_query.py @@ -148,15 +148,23 @@ DDL_QUERY_KEYWORDS = ('CREATE', 'DROP', 'ALTER', 'RENAME', 'TRUNCATE') # Module execution. # +def get_time(): + try: + time_taken = time.perf_counter() + except AttributeError: + # For Python 2 compatibility, fallback to time.time() + time_taken = time.time() + return time_taken + def execute_and_return_time(cursor, query, args): # Measure query execution time in milliseconds - start_time = time.perf_counter() + start_time = get_time() cursor.execute(query, args) # Calculate the execution time rounding it to 4 decimal places - exec_time_ms = round((time.perf_counter() - start_time) * 1000, 4) + exec_time_ms = round((get_time() - start_time) * 1000, 4) return cursor, exec_time_ms diff --git a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml index 6b3c772..9bd8709 100644 --- a/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml +++ b/tests/integration/targets/test_mysql_db/tasks/state_present_absent.yml @@ -310,3 +310,99 @@ assert: that: - db_user1 not in result.stdout + +# ============================================================ +- set_fact: + show_master_status: >- + {% if db_engine == 'mariadb' and db_version is version('10.5.2', '>=') %} + SHOW BINLOG STATUS + {% elif db_engine == 'mysql' and db_version is version('8.4', '>=') %} + SHOW BINARY LOG STATUS + {% else %} + SHOW MASTER STATUS + {% endif %} + +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_1 + +- name: State Present Absent | Create database with sql_log_bin enabled + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: 'sql_bin_on_{{ db_name }}' + sql_log_bin: true + state: present + +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_2 + +- name: State Present Absent | Assert binlog events were written + assert: + that: + - bin_log_position_1.stdout_lines[2] != bin_log_position_2.stdout_lines[2] + +- name: State Present Absent | Remove database with sql_log_bin enabled + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: 'sql_bin_on_{{ db_name }}' + sql_log_bin: true + state: absent + +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_3 + +- name: State Present Absent | Assert Binlog events were written + assert: + that: + - bin_log_position_2.stdout_lines[2] != bin_log_position_3.stdout_lines[2] + +# ============================================================ +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_4 + +- name: State Present Absent | Create database with sql_log_bin disabled + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: 'sql_bin_off_{{ db_name }}' + sql_log_bin: false + state: present + +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_5 + +- name: State Present Absent | Assert binlog events were not written + assert: + that: + - bin_log_position_4.stdout_lines[2] == bin_log_position_5.stdout_lines[2] + +- name: State Present Absent | Remove database with sql_log_bin disabled + mysql_db: + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + name: 'sql_bin_off_{{ db_name }}' + sql_log_bin: false + state: absent + +- name: State Present Absent | Capture binlog position + command: "{{ mysql_command }} -e \"{{ show_master_status }}\\G\"" + register: bin_log_position_6 + +- name: State Present Absent | Assert Binlog events were not written + assert: + that: + - bin_log_position_5.stdout_lines[2] == bin_log_position_6.stdout_lines[2] diff --git a/tests/integration/targets/test_mysql_user/tasks/issue-710.yml b/tests/integration/targets/test_mysql_user/tasks/issue-710.yml new file mode 100644 index 0000000..7090f43 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/issue-710.yml @@ -0,0 +1,43 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + login_port: '{{ mysql_primary_port }}' + + block: + - name: Issue-710 | Create user with DEFAULT privileges + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + state: present + + - name: Issue-710 | Create role to use as default + community.mysql.mysql_role: + <<: *mysql_params + name: developers + state: present + priv: '*.*:ALL' + members: + - "{{ user_name_1 }}@localhost" + + - name: Issue-710 | Set default role for db_user1 + community.mysql.mysql_query: + <<: *mysql_params + query: >- + SET DEFAULT ROLE developers {{ (db_engine == 'mysql') | ternary('TO', 'FOR') }} {{ user_name_1 }}@localhost + + - name: Issue-710 | Ensure db_user1 can still be altered + community.mysql.mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + priv: '*.*:ALL' + state: present + + - name: Issue-710 | Ensure mysql_info can still be executed + community.mysql.mysql_info: + <<: *mysql_params + filter: users_info diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 7212886..c69aea3 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -309,3 +309,7 @@ - name: Mysql_user - test user_locking ansible.builtin.import_tasks: file: test_user_locking.yml + + # Test that mysql_user still works with default role set + # (https://github.com/ansible-collections/community.mysql/issues/710) + - include_tasks: issue-710.yml diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt new file mode 100644 index 0000000..152162d --- /dev/null +++ b/tests/sanity/ignore-2.20.txt @@ -0,0 +1,3 @@ +plugins/modules/mysql_db.py validate-modules:use-run-command-not-popen +plugins/module_utils/mysql.py pylint:unused-import +plugins/module_utils/version.py pylint:unused-import