Skip to content

Commit dab4766

Browse files
[DPE-8473] Handle invalid database name (databases) (#1108)
* Put container in a cached property, and update lib to handle new invalid database name and to handle storage ownership and permissions Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update data directory permission to 0o700 Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> * Update lib Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com> --------- Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
1 parent 9e0c39f commit dab4766

File tree

6 files changed

+59
-55
lines changed

6 files changed

+59
-55
lines changed

poetry.lock

Lines changed: 5 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jinja2 = "^3.1.6"
1717
lightkube = "^0.17.2"
1818
lightkube-models = "^1.28.1.4"
1919
psycopg2 = "^2.9.10"
20-
postgresql-charms-single-kernel = "0.0.1"
20+
postgresql-charms-single-kernel = "16.1.0"
2121

2222
[tool.poetry.group.charm-libs.dependencies]
2323
# data_platform_libs/v0/data_interfaces.py

src/charm.py

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
ServiceStatus,
8585
)
8686
from requests import ConnectionError as RequestsConnectionError
87+
from single_kernel_postgresql.config.literals import (
88+
Substrates,
89+
)
8790
from single_kernel_postgresql.utils.postgresql import (
8891
ACCESS_GROUP_IDENTITY,
8992
ACCESS_GROUPS,
@@ -452,10 +455,16 @@ def is_unit_stopped(self) -> bool:
452455
"""Returns whether the unit is stopped."""
453456
return "stopped" in self.unit_peer_data
454457

458+
@cached_property
459+
def _container(self) -> Container:
460+
"""Returns the postgresql container."""
461+
return self.unit.get_container("postgresql")
462+
455463
@property
456464
def postgresql(self) -> PostgreSQL:
457465
"""Returns an instance of the object used to interact with the database."""
458466
return PostgreSQL(
467+
substrate=Substrates.K8S,
459468
primary_host=self.primary_endpoint,
460469
current_host=self.endpoint,
461470
user=USER,
@@ -623,8 +632,7 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
623632

624633
# Update the list of the cluster members in the replicas to make them know each other.
625634
# Update the cluster members in this unit (updating patroni configuration).
626-
container = self.unit.get_container("postgresql")
627-
if not container.can_connect():
635+
if not self._container.can_connect():
628636
logger.debug(
629637
"Early exit on_peer_relation_changed: Waiting for container to become available"
630638
)
@@ -641,11 +649,11 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
641649
logger.debug("on_peer_relation_changed early exit: Unit in blocked status")
642650
return
643651

644-
services = container.pebble.get_services(names=[self.postgresql_service])
652+
services = self._container.pebble.get_services(names=[self.postgresql_service])
645653
if (
646654
(self.is_cluster_restoring_backup or self.is_cluster_restoring_to_time)
647655
and len(services) > 0
648-
and not self._was_restore_successful(container, services[0])
656+
and not self._was_restore_successful(self._container, services[0])
649657
):
650658
logger.debug("on_peer_relation_changed early exit: Backup restore check failed")
651659
return
@@ -1004,7 +1012,7 @@ def _create_pgdata(self, container: Container):
10041012
"""Create the PostgreSQL data directory."""
10051013
if not container.exists(self.pgdata_path):
10061014
container.make_dir(
1007-
self.pgdata_path, permissions=0o750, user=WORKLOAD_OS_USER, group=WORKLOAD_OS_GROUP
1015+
self.pgdata_path, permissions=0o700, user=WORKLOAD_OS_USER, group=WORKLOAD_OS_GROUP
10081016
)
10091017
# Also, fix the permissions from the parent directory.
10101018
container.exec([
@@ -1609,11 +1617,10 @@ def _check_pgdata_storage_size(self) -> None:
16091617

16101618
def _on_update_status(self, _) -> None:
16111619
"""Update the unit status message."""
1612-
container = self.unit.get_container("postgresql")
1613-
if not self._on_update_status_early_exit_checks(container):
1620+
if not self._on_update_status_early_exit_checks(self._container):
16141621
return
16151622

1616-
services = container.pebble.get_services(names=[self.postgresql_service])
1623+
services = self._container.pebble.get_services(names=[self.postgresql_service])
16171624
if len(services) == 0:
16181625
# Service has not been added nor started yet, so don't try to check Patroni API.
16191626
logger.debug("on_update_status early exit: Service has not been added nor started yet")
@@ -1629,7 +1636,7 @@ def _on_update_status(self, _) -> None:
16291636
f"{self.postgresql_service} pebble service inactive, restarting service"
16301637
)
16311638
try:
1632-
container.restart(self.postgresql_service)
1639+
self._container.restart(self.postgresql_service)
16331640
except ChangeError:
16341641
logger.exception("Failed to restart patroni")
16351642
# If service doesn't recover fast, exit and wait for next hook run to re-check
@@ -1639,7 +1646,7 @@ def _on_update_status(self, _) -> None:
16391646

16401647
if (
16411648
self.is_cluster_restoring_backup or self.is_cluster_restoring_to_time
1642-
) and not self._was_restore_successful(container, services[0]):
1649+
) and not self._was_restore_successful(self._container, services[0]):
16431650
return
16441651

16451652
# Update the sync-standby endpoint in the async replication data.
@@ -1970,28 +1977,32 @@ def _push_file_to_workload(self, container: Container, file_path: str, file_data
19701977

19711978
def push_tls_files_to_workload(self) -> bool:
19721979
"""Uploads TLS files to the workload container."""
1973-
container = self.unit.get_container("postgresql")
1974-
19751980
key, ca, cert = self.tls.get_client_tls_files()
19761981
if key is not None:
1977-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_KEY_FILE}", key)
1982+
self._push_file_to_workload(
1983+
self._container, f"{self._storage_path}/{TLS_KEY_FILE}", key
1984+
)
19781985
if ca is not None:
1979-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CA_FILE}", ca)
1980-
self._push_file_to_workload(container, f"{self._certs_path}/ca.crt", ca)
1981-
container.exec(["update-ca-certificates"]).wait()
1986+
self._push_file_to_workload(self._container, f"{self._storage_path}/{TLS_CA_FILE}", ca)
1987+
self._push_file_to_workload(self._container, f"{self._certs_path}/ca.crt", ca)
1988+
self._container.exec(["update-ca-certificates"]).wait()
19821989
if cert is not None:
1983-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CERT_FILE}", cert)
1990+
self._push_file_to_workload(
1991+
self._container, f"{self._storage_path}/{TLS_CERT_FILE}", cert
1992+
)
19841993

19851994
key, ca, cert = self.tls.get_peer_tls_files()
19861995
if key is not None:
19871996
self._push_file_to_workload(
1988-
container, f"{self._storage_path}/peer_{TLS_KEY_FILE}", key
1997+
self._container, f"{self._storage_path}/peer_{TLS_KEY_FILE}", key
19891998
)
19901999
if ca is not None:
1991-
self._push_file_to_workload(container, f"{self._storage_path}/peer_{TLS_CA_FILE}", ca)
2000+
self._push_file_to_workload(
2001+
self._container, f"{self._storage_path}/peer_{TLS_CA_FILE}", ca
2002+
)
19922003
if cert is not None:
19932004
self._push_file_to_workload(
1994-
container, f"{self._storage_path}/peer_{TLS_CERT_FILE}", cert
2005+
self._container, f"{self._storage_path}/peer_{TLS_CERT_FILE}", cert
19952006
)
19962007

19972008
# CA bundle is not secret
@@ -2002,24 +2013,22 @@ def push_tls_files_to_workload(self) -> bool:
20022013

20032014
def push_ca_file_into_workload(self, secret_name: str) -> bool:
20042015
"""Uploads CA certificate into the workload container."""
2005-
container = self.unit.get_container("postgresql")
20062016
certificates = self.get_secret(UNIT_SCOPE, secret_name)
20072017

20082018
if certificates is not None:
20092019
self._push_file_to_workload(
2010-
container=container,
2020+
container=self._container,
20112021
file_path=f"{self._certs_path}/{secret_name}.crt",
20122022
file_data=certificates,
20132023
)
2014-
container.exec(["update-ca-certificates"]).wait()
2024+
self._container.exec(["update-ca-certificates"]).wait()
20152025

20162026
return self.update_config()
20172027

20182028
def clean_ca_file_from_workload(self, secret_name: str) -> bool:
20192029
"""Cleans up CA certificate from the workload container."""
2020-
container = self.unit.get_container("postgresql")
2021-
container.remove_path(f"{self._certs_path}/{secret_name}.crt")
2022-
container.exec(["update-ca-certificates"]).wait()
2030+
self._container.remove_path(f"{self._certs_path}/{secret_name}.crt")
2031+
self._container.exec(["update-ca-certificates"]).wait()
20232032

20242033
return self.update_config()
20252034

@@ -2055,57 +2064,54 @@ def _restart(self, event: RunWithLock) -> None:
20552064

20562065
def _restart_metrics_service(self) -> None:
20572066
"""Restart the monitoring service if the password was rotated."""
2058-
container = self.unit.get_container("postgresql")
2059-
current_layer = container.get_plan()
2067+
current_layer = self._container.get_plan()
20602068

20612069
metrics_service = current_layer.services[self.metrics_service]
20622070
data_source_name = metrics_service.environment.get("DATA_SOURCE_NAME", "")
20632071

20642072
if metrics_service and not data_source_name.startswith(
20652073
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
20662074
):
2067-
container.add_layer(
2075+
self._container.add_layer(
20682076
self.metrics_service,
20692077
Layer({"services": {self.metrics_service: self._generate_metrics_service()}}),
20702078
combine=True,
20712079
)
2072-
container.restart(self.metrics_service)
2080+
self._container.restart(self.metrics_service)
20732081

20742082
def _restart_ldap_sync_service(self) -> None:
20752083
"""Restart the LDAP sync service in case any configuration changed."""
20762084
if not self._patroni.member_started:
20772085
logger.debug("Restart LDAP sync early exit: Patroni has not started yet")
20782086
return
20792087

2080-
container = self.unit.get_container("postgresql")
2081-
sync_service = container.pebble.get_services(names=[self.ldap_sync_service])
2088+
sync_service = self._container.pebble.get_services(names=[self.ldap_sync_service])
20822089

20832090
if not self.is_primary and sync_service[0].is_running():
20842091
logger.debug("Stopping LDAP sync service. It must only run in the primary")
2085-
container.stop(self.ldap_sync_service)
2092+
self._container.stop(self.ldap_sync_service)
20862093

20872094
if self.is_primary and not self.is_ldap_enabled:
20882095
logger.debug("Stopping LDAP sync service")
2089-
container.stop(self.ldap_sync_service)
2096+
self._container.stop(self.ldap_sync_service)
20902097
return
20912098

20922099
if self.is_primary and self.is_ldap_enabled:
2093-
container.add_layer(
2100+
self._container.add_layer(
20942101
self.ldap_sync_service,
20952102
Layer({"services": {self.ldap_sync_service: self._generate_ldap_service()}}),
20962103
combine=True,
20972104
)
20982105
logger.debug("Starting LDAP sync service")
2099-
container.restart(self.ldap_sync_service)
2106+
self._container.restart(self.ldap_sync_service)
21002107

21012108
@property
21022109
def _is_workload_running(self) -> bool:
21032110
"""Returns whether the workload is running (in an active state)."""
2104-
container = self.unit.get_container("postgresql")
2105-
if not container.can_connect():
2111+
if not self._container.can_connect():
21062112
return False
21072113

2108-
services = container.pebble.get_services(names=[self.postgresql_service])
2114+
services = self._container.pebble.get_services(names=[self.postgresql_service])
21092115
if len(services) == 0:
21102116
return False
21112117

@@ -2279,25 +2285,23 @@ def _handle_postgresql_restart_need(self):
22792285

22802286
def _update_pebble_layers(self, replan: bool = True) -> None:
22812287
"""Update the pebble layers to keep the health check URL up-to-date."""
2282-
container = self.unit.get_container("postgresql")
2283-
22842288
# Get the current layer.
2285-
current_layer = container.get_plan()
2289+
current_layer = self._container.get_plan()
22862290

22872291
# Create a new config layer.
22882292
new_layer = self._postgresql_layer()
22892293

22902294
# Check if there are any changes to layer services.
22912295
if current_layer.services != new_layer.services:
22922296
# Changes were made, add the new layer.
2293-
container.add_layer(self.postgresql_service, new_layer, combine=True)
2297+
self._container.add_layer(self.postgresql_service, new_layer, combine=True)
22942298
logging.info("Added updated layer 'postgresql' to Pebble plan")
22952299
if replan:
2296-
container.replan()
2300+
self._container.replan()
22972301
logging.info("Restarted postgresql service")
22982302
if current_layer.checks != new_layer.checks:
22992303
# Changes were made, add the new layer.
2300-
container.add_layer(self.postgresql_service, new_layer, combine=True)
2304+
self._container.add_layer(self.postgresql_service, new_layer, combine=True)
23012305
logging.info("Updated health checks")
23022306

23032307
def _unit_name_to_pod_name(self, unit_name: str) -> str:

src/relations/postgresql_provider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ACCESS_GROUP_RELATION,
1919
ACCESS_GROUPS,
2020
INVALID_DATABASE_NAME_BLOCKING_MESSAGE,
21+
INVALID_DATABASE_NAMES,
2122
INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE,
2223
PostgreSQLCreateDatabaseError,
2324
PostgreSQLCreateUserError,
@@ -441,7 +442,7 @@ def check_for_invalid_database_name(self, relation_id: int) -> bool:
441442
for data in relation.data.values():
442443
database = data.get("database")
443444
if database is not None and (
444-
len(database) > 49 or database in ["postgres", "template0", "template1"]
445+
len(database) > 49 or database in INVALID_DATABASE_NAMES
445446
):
446447
return True
447448
return False

tests/integration/test_invalid_database.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def test_database(juju: jubilant.Juju, predefined_roles) -> None:
7272
"""Check that an invalid database name makes the database charm block."""
7373
del predefined_roles[""]
7474
invalid_database_names = [
75+
"databases",
7576
"postgres",
7677
"template0",
7778
"template1",

tests/unit/test_charm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ def test_create_pgdata(harness):
15611561
container.exists.return_value = False
15621562
harness.charm._create_pgdata(container)
15631563
container.make_dir.assert_called_once_with(
1564-
"/var/lib/postgresql/data/pgdata", permissions=488, user="postgres", group="postgres"
1564+
"/var/lib/postgresql/data/pgdata", permissions=448, user="postgres", group="postgres"
15651565
)
15661566
container.exec.assert_has_calls([
15671567
call(["chown", "postgres:postgres", "/var/lib/postgresql/archive"]),

0 commit comments

Comments
 (0)