Skip to content

Commit f1ed8e5

Browse files
Support predefined roles
1 parent 5da2b8b commit f1ed8e5

File tree

6 files changed

+61
-11
lines changed

6 files changed

+61
-11
lines changed

common/common/mysql_shell/__init__.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
if typing.TYPE_CHECKING:
2020
from ..relations import database_requires
2121

22+
_ROLE_DML = "charmed_dml"
23+
_ROLE_READ = "charmed_read"
24+
2225
logger = logging.getLogger(__name__)
2326

2427

@@ -123,17 +126,51 @@ def _get_attributes(self, additional_attributes: dict = None) -> str:
123126
attributes.update(additional_attributes)
124127
return json.dumps(attributes)
125128

126-
def create_application_database_and_user(self, *, username: str, database: str) -> str:
127-
"""Create database and user for related database_provides application."""
129+
# TODO python3.10 min version: Use `set` instead of `typing.Set`
130+
def _get_mysql_roles(self, name_pattern: str) -> typing.Set[str]:
131+
"""Returns a set with the MySQL roles."""
132+
logger.debug(f"Getting MySQL roles with {name_pattern=}")
133+
output_file = self._container.path("/tmp/mysqlsh_output.json")
134+
self._run_code(
135+
_jinja_env.get_template("get_mysql_roles_with_pattern.py.jinja").render(
136+
name_pattern=name_pattern,
137+
output_filepath=output_file.relative_to_container,
138+
)
139+
)
140+
with output_file.open("r") as file:
141+
rows = json.load(file)
142+
output_file.unlink()
143+
logger.debug(f"MySQL roles found for {name_pattern=}: {len(rows)}")
144+
return set(rows)
145+
146+
def create_application_database(self, *, database: str) -> str:
147+
"""Create database for related database_provides application."""
148+
mysql_roles = self._get_mysql_roles("charmed_%")
149+
statements = [f"CREATE DATABASE IF NOT EXISTS `{database}`"]
150+
if _ROLE_READ in mysql_roles:
151+
statements.append(
152+
f"GRANT SELECT ON `{database}`.* TO {_ROLE_READ}",
153+
)
154+
if _ROLE_DML in mysql_roles:
155+
statements.append(
156+
f"GRANT SELECT, INSERT, DELETE, UPDATE ON `{database}`.* TO {_ROLE_DML}",
157+
)
158+
159+
logger.debug(f"Creating {database=}")
160+
self._run_sql(statements)
161+
logger.debug(f"Created {database=}")
162+
return database
163+
164+
def create_application_user(self, *, database: str, username: str) -> str:
165+
"""Create database user for related database_provides application."""
128166
attributes = self._get_attributes()
129-
logger.debug(f"Creating {database=} and {username=} with {attributes=}")
130167
password = utils.generate_password()
168+
logger.debug(f"Creating {username=} with {attributes=}")
131169
self._run_sql([
132-
f"CREATE DATABASE IF NOT EXISTS `{database}`",
133170
f"CREATE USER `{username}` IDENTIFIED BY '{password}' ATTRIBUTE '{attributes}'",
134171
f"GRANT ALL PRIVILEGES ON `{database}`.* TO `{username}`",
135172
])
136-
logger.debug(f"Created {database=} and {username=} with {attributes=}")
173+
logger.debug(f"Created {username=} with {attributes=}")
137174
return password
138175

139176
def add_attributes_to_mysql_router_user(
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import json
2+
3+
result = session.run_sql(
4+
"SELECT user FROM mysql.user WHERE user LIKE '{{ name_pattern }}'"
5+
)
6+
rows = result.fetch_all()
7+
# mysqlsh objects are weird—they quack (i.e. duck typing) like standard Python objects (e.g. list,
8+
# dict), but do not serialize to JSON correctly.
9+
# Cast to str & load from JSON str before serializing
10+
rows = json.loads(str(rows))
11+
with open("{{ output_filepath }}", "w") as file:
12+
json.dump(rows, file)

common/common/relations/database_provides.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,8 @@ def create_database_and_user(
121121
shell.delete_user(username, must_exist=False)
122122
logger.debug("Deleted user if exists before creating user")
123123

124-
password = shell.create_application_database_and_user(
125-
username=username, database=self._database
126-
)
124+
________ = shell.create_application_database(database=self._database)
125+
password = shell.create_application_user(database=self._database, username=username)
127126

128127
self._set_databag(
129128
username=username,

kubernetes/tests/unit/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def patch(monkeypatch):
4747
)
4848
monkeypatch.setattr("common.workload.RunningWorkload._router_username", "")
4949
monkeypatch.setattr("common.mysql_shell.Shell._run_code", lambda *args, **kwargs: None)
50+
monkeypatch.setattr("common.mysql_shell.Shell._get_mysql_roles", lambda *args, **kwargs: set())
5051
monkeypatch.setattr(
5152
"common.mysql_shell.Shell.get_mysql_router_user_for_unit", lambda *args, **kwargs: None
5253
)

machines/src/relations/deprecated_shared_db_database_provides.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ def create_database_and_user(
147147
shell.delete_user(self._username, must_exist=False)
148148
logger.debug("Deleted user if exists before creating user")
149149

150-
password = shell.create_application_database_and_user(
151-
username=self._username, database=self._database
152-
)
150+
________ = shell.create_application_database(database=self._database)
151+
password = shell.create_application_user(database=self._database, username=self._username)
152+
153153
self._peer_app_databag[self.peer_databag_password_key] = password
154154
self.set_databag(password=password)
155155

machines/tests/unit/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def patch(monkeypatch):
6060
)
6161
monkeypatch.setattr("common.workload.RunningWorkload._router_username", "")
6262
monkeypatch.setattr("common.mysql_shell.Shell._run_code", lambda *args, **kwargs: None)
63+
monkeypatch.setattr("common.mysql_shell.Shell._get_mysql_roles", lambda *args, **kwargs: set())
6364
monkeypatch.setattr(
6465
"common.mysql_shell.Shell.get_mysql_router_user_for_unit", lambda *args, **kwargs: None
6566
)

0 commit comments

Comments
 (0)