|
7 | 7 | import logging |
8 | 8 | from typing import List |
9 | 9 |
|
10 | | -from charms.postgresql_k8s.v0.postgresql import PostgreSQL |
| 10 | +from charms.postgresql_k8s.v0.postgresql import ( |
| 11 | + PostgreSQL, |
| 12 | + PostgreSQLSetUserPasswordError, |
| 13 | +) |
11 | 14 | from lightkube import ApiError, Client, codecs |
12 | 15 | from lightkube.resources.core_v1 import Pod |
13 | 16 | from ops.charm import ( |
|
30 | 33 | from requests import ConnectionError |
31 | 34 | from tenacity import RetryError |
32 | 35 |
|
33 | | -from constants import PEER, USER |
| 36 | +from constants import PEER, REPLICATION_USER, SYSTEM_USERS, USER |
34 | 37 | from patroni import NotReadyError, Patroni |
35 | 38 | from relations.db import DbProvides |
36 | 39 | from relations.postgresql_provider import PostgreSQLProvider |
@@ -61,6 +64,9 @@ def __init__(self, *args): |
61 | 64 | self.framework.observe( |
62 | 65 | self.on.get_operator_password_action, self._on_get_operator_password |
63 | 66 | ) |
| 67 | + self.framework.observe( |
| 68 | + self.on.rotate_users_passwords_action, self._on_rotate_users_passwords |
| 69 | + ) |
64 | 70 | self.framework.observe(self.on.get_primary_action, self._on_get_primary) |
65 | 71 | self.framework.observe(self.on.update_status, self._on_update_status) |
66 | 72 | self._storage_path = self.meta.storages["pgdata"].location |
@@ -391,6 +397,53 @@ def _on_get_operator_password(self, event: ActionEvent) -> None: |
391 | 397 | """Returns the password for the operator user as an action response.""" |
392 | 398 | event.set_results({"operator-password": self._get_operator_password()}) |
393 | 399 |
|
| 400 | + def _on_rotate_users_passwords(self, event: ActionEvent) -> None: |
| 401 | + """Rotate the password for all system users or the specified user.""" |
| 402 | + # Only the leader can write the new password into peer relation. |
| 403 | + if not self.unit.is_leader(): |
| 404 | + event.fail("The action can be run only on the leader unit") |
| 405 | + return |
| 406 | + |
| 407 | + if "user" in event.params: |
| 408 | + user = event.params["user"] |
| 409 | + |
| 410 | + # Fail if the user is not a system user. |
| 411 | + # One example is users created through relations. |
| 412 | + if user not in SYSTEM_USERS: |
| 413 | + event.fail(f"User {user} is not a system user") |
| 414 | + return |
| 415 | + |
| 416 | + # Generate a new password and use it if no password was provided to the action. |
| 417 | + users = { |
| 418 | + user: event.params["password"] if "password" in event.params else new_password() |
| 419 | + } |
| 420 | + else: |
| 421 | + if "password" in event.params: |
| 422 | + event.fail("The same password cannot be set for multiple users") |
| 423 | + return |
| 424 | + |
| 425 | + users = {user: new_password() for user in SYSTEM_USERS} |
| 426 | + |
| 427 | + try: |
| 428 | + self.postgresql.rotate_users_passwords(users) |
| 429 | + except PostgreSQLSetUserPasswordError as e: |
| 430 | + event.fail(f"Failed to set user password with error {e}") |
| 431 | + return |
| 432 | + |
| 433 | + # Update the password in the peer relation if the operation was successful. |
| 434 | + for user, password in users.items(): |
| 435 | + self._peers.data[self.app].update({f"{user}-password": password}) |
| 436 | + |
| 437 | + # for unit in |
| 438 | + self._patroni.reload_patroni_configuration() |
| 439 | + |
| 440 | + # Return the generated password when the user option is given. |
| 441 | + if "user" in event.params and "password" not in event.params: |
| 442 | + user = event.params["user"] |
| 443 | + event.set_results( |
| 444 | + {f"{user}-password": self._peers.data[self.app].get(f"{user}-password")} |
| 445 | + ) |
| 446 | + |
394 | 447 | def _on_get_primary(self, event: ActionEvent) -> None: |
395 | 448 | """Get primary instance.""" |
396 | 449 | try: |
@@ -499,7 +552,7 @@ def _postgresql_layer(self) -> Layer: |
499 | 552 | "PATRONI_KUBERNETES_USE_ENDPOINTS": "true", |
500 | 553 | "PATRONI_NAME": pod_name, |
501 | 554 | "PATRONI_SCOPE": f"patroni-{self._name}", |
502 | | - "PATRONI_REPLICATION_USERNAME": "replication", |
| 555 | + "PATRONI_REPLICATION_USERNAME": REPLICATION_USER, |
503 | 556 | "PATRONI_REPLICATION_PASSWORD": self._replication_password, |
504 | 557 | "PATRONI_SUPERUSER_USERNAME": USER, |
505 | 558 | "PATRONI_SUPERUSER_PASSWORD": self._get_operator_password(), |
|
0 commit comments