Skip to content

Commit

Permalink
fix: updated command options and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Danyal-Faheem committed Jan 27, 2025
1 parent 49f850e commit d32b8f8
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 40 deletions.
10 changes: 6 additions & 4 deletions docs/local.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,15 @@ Tutor makes it easy do so with this handy command::

tutor local do update-mysql-authentication-plugin USERNAME

The password will be required to be entered interactively. Optionally, the password can also be provided as part of the command. To update the openedx mysql user::
The password will not be required for official plugins that have database users as tutor can infer it from the config. If the password cannot be found by tutor, you will be prompted to enter the password interactively. Alternatively, the password can also be provided as an option::

tutor local do update-mysql-authentication-plugin $(tutor config printvalue OPENEDX_MYSQL_USERNAME) --password=$(tutor config printvalue OPENEDX_MYSQL_PASSWORD)
tutor local do update-mysql-authentication-plugin USERNAME --password=PASSWORD

Tutor may prompt you with some warnings if the entered password is suspected to be wrong. To avoid these prompts, the non-interactive option should be used. To update the root mysql user non-interactively::
To update the database users for a vanilla tutor installation::

tutor local do update-mysql-authentication-plugin $(tutor config printvalue OPENEDX_MYSQL_USERNAME)
tutor local do update-mysql-authentication-plugin $(tutor config printvalue MYSQL_ROOT_USERNAME)

tutor local do update-mysql-authentication-plugin $(tutor config printvalue MYSQL_ROOT_USERNAME) --password=$(tutor config printvalue MYSQL_ROOT_PASSWORD) --non-interactive

Running arbitrary ``manage.py`` commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
6 changes: 3 additions & 3 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ The detailed steps are mentioned in `tutor-mfe <https://github.com/overhangio/tu
"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.
This issue can occur when Tutor is upgraded from v15 (Olive) or earlier to v18 (Redwood) or later because the users created in Tutor v15 and earlier 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 and onwards.

The handy :ref:`update-mysql-authentication-plugin <update_mysql_authentication_plugin>` do command in tutor can be used to fix this issue. To update the database users for a vanilla tutor installation::

tutor local do update-mysql-authentication-plugin $(tutor config printvalue OPENEDX_MYSQL_USERNAME) --password=$(tutor config printvalue OPENEDX_MYSQL_PASSWORD) --non-interactive
tutor local do update-mysql-authentication-plugin $(tutor config printvalue MYSQL_ROOT_USERNAME) --password=$(tutor config printvalue MYSQL_ROOT_PASSWORD) --non-interactive
tutor local do update-mysql-authentication-plugin $(tutor config printvalue OPENEDX_MYSQL_USERNAME)
tutor local do update-mysql-authentication-plugin $(tutor config printvalue MYSQL_ROOT_USERNAME)
28 changes: 24 additions & 4 deletions tests/commands/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def test_convert_mysql_utf8mb4_charset_exclude_tables(self) -> None:
self.assertIn("course", dc_args[-1])
self.assertIn("auth", dc_args[-1])

def test_update_mysql_authentication_plugin(self) -> None:
def test_update_mysql_authentication_plugin_official_plugin(self) -> None:
with temporary_root() as root:
self.invoke_in_root(root, ["config", "save"])
with patch("tutor.utils.docker_compose") as mock_docker_compose:
Expand All @@ -177,8 +177,6 @@ def test_update_mysql_authentication_plugin(self) -> None:
"do",
"update-mysql-authentication-plugin",
"openedx",
"--password=password",
"--non-interactive",
],
)
dc_args, _dc_kwargs = mock_docker_compose.call_args
Expand All @@ -188,4 +186,26 @@ def test_update_mysql_authentication_plugin(self) -> None:
self.assertIn("lms-job", dc_args)
self.assertIn("caching_sha2_password", dc_args[-1])
self.assertIn("openedx", dc_args[-1])
self.assertIn("password", dc_args[-1])

def test_update_mysql_authentication_plugin_custom_plugin(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",
"mypluginuser",
"--password=mypluginpassword",
],
)
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("mypluginuser", dc_args[-1])
self.assertIn("mypluginpassword", dc_args[-1])
64 changes: 35 additions & 29 deletions tutor/commands/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
set_theme_template,
)
from tutor.hooks import priorities
from tutor.types import get_typed


class DoGroup(click.Group):
Expand Down Expand Up @@ -387,17 +386,14 @@ def generate_query_to_append(tables: list[str], exclude: bool = False) -> str:
@click.option(
"-p",
"--password",
help="Specify password from the command line. If undefined, you will be prompted to input a password",
prompt=True,
hide_input=True,
help="Specify password from the command line.",
)
@click.argument(
"user",
)
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
@click.pass_obj
def update_mysql_authentication_plugin(
context: Context, user: str, password: str, non_interactive: bool
context: Context, user: str, password: str
) -> t.Iterable[tuple[str, str]]:
"""
Update the authentication plugin of MySQL users from mysql_native_password to caching_sha2_password
Expand All @@ -413,30 +409,40 @@ def update_mysql_authentication_plugin(
)
return

conventional_password_key = f"{user.upper()}_MYSQL_PASSWORD"
# Official plugins that have their own mysql user
known_plugins_with_mysql_keys = [
"credentials",
"discovery",
"ecommerce",
"jupyter",
"notes",
"openedx",
"xqueue",
]

# Prompt for confirmation to move forward if password not present in config with the conventional format USER_MYSQL_PASSWORD
if not non_interactive and not conventional_password_key in config:
if not click.confirm(
fmt.question(
f"""Password for user {user} could not be verified. The entered password is: {password}
Would you still like to continue with the upgrade process? Note: a wrong password would update the password for the user."""
)
):
return
# Prompt for confirmation to move forward is password is present in config with the conventional format USER_MYSQL_PASSWORD
# but it is not the same as the value of that config variable
elif (
not non_interactive
and get_typed(config, conventional_password_key, str, "") != password
):
if not click.confirm(
fmt.question(
f"""Password for user {user} is suspected to be wrong. The entered password is: {password} while the password suspected to be the correct one is {config[conventional_password_key]}
Would you still like to continue with the upgrade process? Note: a wrong password would update the password for the user."""
)
):
return
# Create a list of the usernames and passwords
known_mysql_credentials_keys = [
(f"{plugin.upper()}_MYSQL_USERNAME", f"{plugin.upper()}_MYSQL_PASSWORD")
for plugin in known_plugins_with_mysql_keys
]
# Add the root user as it is the only one that is different from the rest
known_mysql_credentials_keys.append(("MYSQL_ROOT_USERNAME", "MYSQL_ROOT_PASSWORD"))

known_mysql_credentials = {}
# Build the dictionary of known credentials
for k, v in known_mysql_credentials_keys:
if username := config.get(k):
known_mysql_credentials[username] = config[v]

if not password:
password = known_mysql_credentials.get(user) # type: ignore

# Prompt the user if password was not found in config
if not password:
password = click.prompt(
f"Please enter the password for the user {user}",
type=str,
)

host = "%"

Expand Down

0 comments on commit d32b8f8

Please sign in to comment.