Skip to content

Commit 3d1f6a2

Browse files
[DRAFT]
1 parent 7ae8a58 commit 3d1f6a2

File tree

5 files changed

+60
-11
lines changed

5 files changed

+60
-11
lines changed

src/mysql_shell/__init__.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
if typing.TYPE_CHECKING:
2222
import relations.database_requires
2323

24+
ROLE_DML = "charmed_dml"
25+
ROLE_READ = "charmed_read"
26+
2427
logger = logging.getLogger(__name__)
2528

2629

@@ -125,17 +128,34 @@ def _get_attributes(self, additional_attributes: dict = None) -> str:
125128
attributes.update(additional_attributes)
126129
return json.dumps(attributes)
127130

128-
def create_application_database_and_user(self, *, username: str, database: str) -> str:
129-
"""Create database and user for related database_provides application."""
131+
def create_application_database(self, *, database: str) -> str:
132+
"""Create database for related database_provides application."""
133+
mysql_roles = self.get_mysql_roles("charmed_%")
134+
statements = [f"CREATE DATABASE IF NOT EXISTS `{database}`"]
135+
if ROLE_READ in mysql_roles:
136+
statements.append(
137+
f"GRANT SELECT ON `{database}`.* TO {ROLE_READ}",
138+
)
139+
if ROLE_DML in mysql_roles:
140+
statements.append(
141+
f"GRANT SELECT, INSERT, DELETE, UPDATE ON `{database}`.* TO {ROLE_DML}",
142+
)
143+
144+
logger.debug(f"Creating {database=}")
145+
self._run_sql(statements)
146+
logger.debug(f"Created {database=}")
147+
return database
148+
149+
def create_application_user(self, *, database: str, username: str) -> str:
150+
"""Create database user for related database_provides application."""
130151
attributes = self._get_attributes()
131-
logger.debug(f"Creating {database=} and {username=} with {attributes=}")
132152
password = utils.generate_password()
153+
logger.debug(f"Creating {username=} with {attributes=}")
133154
self._run_sql([
134-
f"CREATE DATABASE IF NOT EXISTS `{database}`",
135155
f"CREATE USER `{username}` IDENTIFIED BY '{password}' ATTRIBUTE '{attributes}'",
136156
f"GRANT ALL PRIVILEGES ON `{database}`.* TO `{username}`",
137157
])
138-
logger.debug(f"Created {database=} and {username=} with {attributes=}")
158+
logger.debug(f"Created {username=} with {attributes=}")
139159
return password
140160

141161
def add_attributes_to_mysql_router_user(
@@ -150,6 +170,23 @@ def add_attributes_to_mysql_router_user(
150170
self._run_sql([f"ALTER USER `{username}` ATTRIBUTE '{attributes}'"])
151171
logger.debug(f"Added {attributes=} to {username=}")
152172

173+
# TODO python3.10 min version: Use `set` instead of `typing.Set`
174+
def get_mysql_roles(self, name_pattern: str) -> typing.Set[str]:
175+
"""Returns a set with the MySQL roles."""
176+
logger.debug(f"Getting MySQL roles with {name_pattern=}")
177+
output_file = self._container.path("/tmp/mysqlsh_output.json")
178+
self._run_code(
179+
_jinja_env.get_template("get_mysql_roles_with_pattern.py.jinja").render(
180+
name_pattern=name_pattern,
181+
output_filepath=output_file.relative_to_container,
182+
)
183+
)
184+
with output_file.open("r") as file:
185+
rows = json.load(file)
186+
output_file.unlink()
187+
logger.debug(f"MySQL roles found for {name_pattern=}: {len(rows)}")
188+
return set(rows)
189+
153190
def get_mysql_router_user_for_unit(
154191
self, unit_name: str
155192
) -> typing.Optional[RouterUserInformation]:
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)

src/relations/database_provides.py

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

126-
password = shell.create_application_database_and_user(
127-
username=username, database=self._database
128-
)
126+
________ = shell.create_application_database(database=self._database)
127+
password = shell.create_application_user(database=self._database, username=username)
129128

130129
rw_endpoint = (
131130
exposed_read_write_endpoints

src/relations/deprecated_shared_db_database_provides.py

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

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

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("workload.RunningWorkload._router_username", "")
6262
monkeypatch.setattr("mysql_shell.Shell._run_code", lambda *args, **kwargs: None)
63+
monkeypatch.setattr("mysql_shell.Shell.get_mysql_roles", lambda *args, **kwargs: set())
6364
monkeypatch.setattr(
6465
"mysql_shell.Shell.get_mysql_router_user_for_unit", lambda *args, **kwargs: None
6566
)

0 commit comments

Comments
 (0)