Skip to content

Commit

Permalink
feat: add do command to update the authentication plugin of MySQL use…
Browse files Browse the repository at this point in the history
…rs to caching_sha2_password
  • Loading branch information
Danyal-Faheem committed Nov 29, 2024
1 parent f8a47d7 commit 98d6753
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- [Improvement] Add a do command to update the authentication plugin of existing MySQL users from mysql_native_password to caching_sha2_password for compatibility with MySQL v8.4.0 and above. (by @Danyal-Faheem)
20 changes: 20 additions & 0 deletions docs/local.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,26 @@ By default, only the tables in the openedx database are changed. For upgrading t

tutor local do convert-mysql-utf8mb4-charset --database=discovery

.. _update_mysql_authentication_plugin:

Updating the authentication plugin of MySQL users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As of MySQL v8.4.0, the ``mysql_native_password`` authentication plugin has been deprecated. Users created with this authentication plugin should ideally be updated to use the latest ``caching_sha2_password`` authentication plugin.

Tutor makes it easy do so with this handy command::

tutor local do update-mysql-authentication-plugin all

The above command will update all the database users created by Tutor. If you only want to update the authentication plugin of specific users, you can use the ``--users`` option. This option takes comma seperated names of users to upgrade::

tutor local do update-mysql-authentication-plugin discovery ecommerce

For this command, Tutor expects specific entries in the configuration for the mysql username and password of a database user. For example, if you are trying to update the user ``myuser``, the following case sensitive entries need to be present in the configuration::

MYUSER_MYSQL_USERNAME
MYUSER_MYSQL_PASSWORD

Running arbitrary ``manage.py`` commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 7 additions & 0 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,10 @@ NPM Dependency Conflict When overriding ``@edx/frontend-component-header`` or ``
----------------------------------------------------------------------------------------------------------------

The detailed steps are mentioned in `tutor-mfe <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#npm-dependency-conflict-when-overriding-edxfrontend-component-header-or-edxfrontend-component-footer>`__ documentation.

"Plugin 'mysql_native_password' is not loaded"
----------------------------------------------

This issue can occur when Tutor is upgraded from v15 (Olive) or earlier to v18 (Redwood) because the users created in Tutor v15 utilize the mysql_native_password authentication plugin by default. This plugin has been deprecated as of MySQL v8.4.0 which is the default MySQL server used in Tutor v18.

The handy :ref:`update-mysql-authentication-plugin <update_mysql_authentication_plugin>` do command in tutor can be used to fix this issue.
33 changes: 33 additions & 0 deletions tests/commands/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,36 @@ def test_convert_mysql_utf8mb4_charset_exclude_tables(self) -> None:
self.assertIn("NOT", dc_args[-1])
self.assertIn("course", dc_args[-1])
self.assertIn("auth", dc_args[-1])

def test_update_mysql_authentication_plugin_all_users(self) -> None:
with temporary_root() as root:
self.invoke_in_root(root, ["config", "save"])
with patch("tutor.utils.docker_compose") as mock_docker_compose:
result = self.invoke_in_root(
root,
["local", "do", "update-mysql-authentication-plugin", "all"],
)
dc_args, _dc_kwargs = mock_docker_compose.call_args

self.assertIsNone(result.exception)
self.assertEqual(0, result.exit_code)
self.assertIn("lms-job", dc_args)
self.assertIn("caching_sha2_password", dc_args[-1])
self.assertIn("openedx", dc_args[-1])
self.assertIn("root", dc_args[-1])

def test_update_mysql_authentication_plugin_one_user(self) -> None:
with temporary_root() as root:
self.invoke_in_root(root, ["config", "save"])
with patch("tutor.utils.docker_compose") as mock_docker_compose:
result = self.invoke_in_root(
root,
["local", "do", "update-mysql-authentication-plugin", "openedx"],
)
dc_args, _dc_kwargs = mock_docker_compose.call_args

self.assertIsNone(result.exception)
self.assertEqual(0, result.exit_code)
self.assertIn("lms-job", dc_args)
self.assertIn("caching_sha2_password", dc_args[-1])
self.assertIn("openedx", dc_args[-1])
62 changes: 60 additions & 2 deletions tutor/commands/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
from typing_extensions import ParamSpec

from tutor import config as tutor_config
from tutor import env, fmt, hooks
from tutor import env, fmt, hooks, plugins
from tutor.commands.context import Context
from tutor.commands.jobs_utils import get_mysql_change_charset_query
from tutor.commands.jobs_utils import (
get_mysql_change_authentication_plugin_query,
get_mysql_change_charset_query,
)
from tutor.hooks import priorities


Expand Down Expand Up @@ -420,6 +423,60 @@ def generate_query_to_append(tables: list[str], exclude: bool = False) -> str:
fmt.echo_info("MySQL charset and collation successfully upgraded")


@click.command(
short_help="Update the authentication plugin of mysql users to caching_sha2_password.",
help=(
"Update the authentication plugin of mysql users to caching_sha2_password from mysql_native_password. You can specify either specific users to update or all to update all users."
),
)
@click.argument(
"users",
nargs=-1,
)
@click.pass_obj
def update_mysql_authentication_plugin(
context: Context, users: tuple[str]
) -> t.Iterable[tuple[str, str]]:
"""
Update the authentication plugin of MySQL users from mysql_native_password to caching_sha2_password
Handy command utilized when upgrading to v8.4 of MySQL which deprecates mysql_native_password
"""

config = tutor_config.load(context.root)

if not config["RUN_MYSQL"]:
fmt.echo_info(
f"You are not running MySQL (RUN_MYSQL=False). It is your "
f"responsibility to update the authentication plugin of mysql users."
)
return

if not users:
fmt.echo_error(
f"Please specify a list of users to update the authentication plugin of.\n"
f"Or, specify 'all' to update all database users."
)
return

update_all = "all" in users
users_to_update = list(plugins.iter_loaded()) if update_all else users

query = get_mysql_change_authentication_plugin_query(
config, users_to_update, update_all
)

# In case there is no user to update the authentication plugin of
if not query:
return

mysql_command = (
"mysql --user={{ MYSQL_ROOT_USERNAME }} --password={{ MYSQL_ROOT_PASSWORD }} --host={{ MYSQL_HOST }} --port={{ MYSQL_PORT }} --database={{ OPENEDX_MYSQL_DATABASE }} --show-warnings "
+ shlex.join(["-e", query])
)

yield ("lms", mysql_command)


def add_job_commands(do_command_group: click.Group) -> None:
"""
This is meant to be called with the `local/dev/k8s do` group commands, to add the
Expand Down Expand Up @@ -503,5 +560,6 @@ def do_callback(service_commands: t.Iterable[tuple[str, str]]) -> None:
print_edx_platform_setting,
settheme,
sqlshell,
update_mysql_authentication_plugin,
]
)
74 changes: 74 additions & 0 deletions tutor/commands/jobs_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
Methods:
- `get_mysql_change_charset_query`: Generates MySQL queries to upgrade the charset and collation of columns, tables, and databases.
- `get_mysql_change_authentication_plugin_query`: Generates MySQL queries to update the authentication plugin for MySQL users.
"""

from typing import Sequence

from tutor import fmt
from tutor.types import Config, ConfigValue


def get_mysql_change_charset_query(
database: str,
Expand Down Expand Up @@ -131,3 +137,71 @@ def get_mysql_change_charset_query(
CALL UpdateColumns();
CALL UpdateTables();
"""


def get_mysql_change_authentication_plugin_query(
config: Config, users: Sequence[str], all_users: bool
) -> str:
"""
Generates MySQL queries to update the authentication plugin for MySQL users.
This method constructs queries to change the authentication plugin to
`caching_sha2_password`. User credentials must be provided in the tutor
configuration under the keys `<user>_MYSQL_USERNAME` and `<user>_MYSQL_PASSWORD`.
Args:
config (Config): Tutor configuration object
users (List[str]): List of specific MySQL users to update.
all_users (bool): Flag indicating whether to include ROOT and OPENEDX users
in addition to those specified in the `users` list.
Returns:
str: A string containing the SQL queries to execute.
Raises:
TutorError: If any user in the `users` list does not have corresponding
username or password entries in the configuration.
"""

host = "%"
query = ""

def generate_mysql_authentication_plugin_update_query(
username: ConfigValue, password: ConfigValue, host: str
) -> str:
fmt.echo_info(
f"Authentication plugin of user {username} will be updated to caching_sha2_password"
)
return f"ALTER USER IF EXISTS '{username}'@'{host}' IDENTIFIED with caching_sha2_password BY '{password}';"

def generate_user_queries(users: Sequence[str]) -> str:
query = ""
for user in users:
user_uppercase = user.upper()
if not (
f"{user_uppercase}_MYSQL_USERNAME" in config
and f"{user_uppercase}_MYSQL_PASSWORD" in config
):
fmt.echo_alert(
f"Username or Password for User {user} not found in config. Skipping update process for User {user}."
)
continue

query += generate_mysql_authentication_plugin_update_query(
config[f"{user_uppercase}_MYSQL_USERNAME"],
config[f"{user_uppercase}_MYSQL_PASSWORD"],
host,
)
return query

if not all_users:
return generate_user_queries(users)

query += generate_mysql_authentication_plugin_update_query(
config["MYSQL_ROOT_USERNAME"], config["MYSQL_ROOT_PASSWORD"], host
)
query += generate_mysql_authentication_plugin_update_query(
config["OPENEDX_MYSQL_USERNAME"], config["OPENEDX_MYSQL_PASSWORD"], host
)

return query + generate_user_queries(users)

0 comments on commit 98d6753

Please sign in to comment.