結論

同一 MySQL サーバに共存させた複数データベースに存在するユーザーに対して任意の権限を付与するには、/ を使う必要があります。
Ansible の mysql_user モジュールでは、同じユーザーに対して複数回 priv を指定すると、後から指定した値が優先されるからです。

この挙動を整理した時の経緯を紹介します。

何で困っていたか

同一サーバに複数のデータベースを作成し、 Ansible を使ったユーザー作成と権限管理を行なっていましたが、この際の挙動を捉えるのに苦労しました。

例えば、user1 に対する以下のような設定を想定しているとします。

  • database_a の全てのテーブルに対する SELECT 権限
  • database_b の全てのテーブルに対する ALL 権限

この想定で playbook.yml を次のように記述した場合

      - { name: user1, state: present, host: 'localhost', priv: 'database_a.*:SELECT', password: password1 }
      - { name: user1, state: present, host: 'localhost', priv: 'database_b.*:ALL', password: password1 }

Ansible による実行結果は以下のようになり、user1 に関する database_a への権限を付与できませんでした。

mysql> SELECT Host, User, Db, Select_priv, Update_priv FROM mysql.db;
+-----------+---------------+--------------------+-------------+-------------+
| Host      | User          | Db                 | Select_priv | Update_priv |
+-----------+---------------+--------------------+-------------+-------------+
| localhost | mysql.session | performance_schema | Y           | N           |
| localhost | mysql.sys     | sys                | N           | N           |
| localhost | user1         | database_b         | Y           | Y           |
+-----------+---------------+--------------------+-------------+-------------+

実行順序を入れ替えても

      - { name: user1, state: present, host: 'localhost', priv: 'database_b.*:ALL', password: password1 }
      - { name: user1, state: present, host: 'localhost', priv: 'database_a.*:SELECT', password: password1 }

以下のような結果となり、やはり user1 に関してデータベースごとに異なる権限を付与できませんでした。

mysql> SELECT Host, User, Db, Select_priv, Update_priv FROM mysql.db;
+-----------+---------------+--------------------+-------------+-------------+
| Host      | User          | Db                 | Select_priv | Update_priv |
+-----------+---------------+--------------------+-------------+-------------+
| localhost | mysql.session | performance_schema | Y           | N           |
| localhost | mysql.sys     | sys                | N           | N           |
| localhost | user1         | database_a         | Y           | N           |
+-----------+---------------+--------------------+-------------+-------------+

上記以外にも、カンマやリストを使った記述方法も試しましたが、いずれも望んだ結果を得られませんでした。

どうやって解決したか

公式情報をちゃんと読み、簡単な検証ツールを作って検証しました。 最終的に以下のような記述により、期待した結果を得ました。

      - { name: user1, state: present, host: 'localhost', priv: 'database_a.*:SELECT/database_b.*:ALL', password: password1 }
mysql> SELECT Host, User, Db, Select_priv, Update_priv FROM mysql.db;
+-----------+---------------+--------------------+-------------+-------------+
| Host      | User          | Db                 | Select_priv | Update_priv |
+-----------+---------------+--------------------+-------------+-------------+
| localhost | mysql.session | performance_schema | Y           | N           |
| localhost | mysql.sys     | sys                | N           | N           |
| localhost | user1         | database_a         | Y           | N           |
| localhost | user1         | database_b         | Y           | Y           |
+-----------+---------------+--------------------+-------------+-------------+

公式ドキュメント

Multiple privileges can be specified by separating each one using a forward slash: db.table1:priv/db.table2:priv.

https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html#parameter-priv

検証ツール

こちらは GitHub に公開しましたので、興味のある方はご活用ください。 このツールは MySQL に対する Ansible の挙動を Docker を使って確認するのに役立ちます。

https://github.com/Tatsumi-I/ansible-mysql_user-test-tool

まとめ

  • 同一ユーザーへ複数回 priv を指定した際の挙動を理解していないと、想定した権限を付与できないことがあります。
  • 同一サーバに共存する複数データベースにおけるユーザーへ任意の権限を付与するには、/ を使う必要があります。

また、Ansible における変数の扱いやその優先度も併せて把握することが重要です。

In general, Ansible gives precedence to variables that were defined more recently, more actively, and with more explicit scope. Variables in the defaults folder inside a role are easily overridden. Anything in the vars directory of the role overrides previous versions of that variable in the namespace.

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence

どんなツールでもとっかかりは難しいですが、使えると開発者体験が大きく向上しますね。 これがその一助となりましたら幸いです。

(参考)検証環境

$ docker --version
Docker version 27.4.0, build bde2b89
# mysql --version
mysql  Ver 8.0.37
# ansible --version
ansible [core 2.13.13]