Skip to content

Commit

Permalink
fix: handle postgresql relation departed (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
amandahla authored Jan 7, 2025
1 parent d634d96 commit 8408140
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 3 deletions.
1 change: 1 addition & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ CVE-2024-34156
CVE-2024-24790
CVE-2024-24788
CVE-2024-45337
# Pebble
CVE-2024-45338
5 changes: 3 additions & 2 deletions src-docs/charm.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Maubot charm service.
- **MAUBOT_CONFIGURATION_PATH**
- **MAUBOT_NAME**
- **NGINX_NAME**
- **POSTGRESQL_RELATION_NAME**


---
Expand All @@ -28,7 +29,7 @@ Exception raised when an event fails.
## <kbd>class</kbd> `MaubotCharm`
Maubot charm.

<a href="../lib/charms/loki_k8s/v0/charm_logging.py#L73"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../lib/charms/loki_k8s/v0/charm_logging.py#L74"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand Down Expand Up @@ -89,7 +90,7 @@ Unit that this execution is responsible for.
## <kbd>class</kbd> `MissingRelationDataError`
Custom exception to be raised in case of malformed/missing relation data.

<a href="../src/charm.py#L54"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm.py#L55"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand Down
11 changes: 10 additions & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
MAUBOT_CONFIGURATION_PATH = "/data/config.yaml"
MAUBOT_NAME = "maubot"
NGINX_NAME = "nginx"
POSTGRESQL_RELATION_NAME = "postgresql"


class MissingRelationDataError(Exception):
Expand Down Expand Up @@ -91,7 +92,7 @@ def __init__(self, *args: Any):
jobs=self._probes_scraping_job,
)
self.postgresql = DatabaseRequires(
self, relation_name="postgresql", database_name=self.app.name
self, relation_name=POSTGRESQL_RELATION_NAME, database_name=self.app.name
)
self.matrix_auth = MatrixAuthRequires(self)
self.framework.observe(self.on.maubot_pebble_ready, self._on_maubot_pebble_ready)
Expand All @@ -104,6 +105,10 @@ def __init__(self, *args: Any):
# Integrations events handlers
self.framework.observe(self.postgresql.on.database_created, self._on_database_created)
self.framework.observe(self.postgresql.on.endpoints_changed, self._on_endpoints_changed)
self.framework.observe(
self.on[POSTGRESQL_RELATION_NAME].relation_departed,
self._on_postgresql_relation_departed,
)
self.framework.observe(self.ingress.on.ready, self._on_ingress_ready)
self.framework.observe(self.ingress.on.revoked, self._on_ingress_revoked)
self.framework.observe(
Expand Down Expand Up @@ -188,6 +193,10 @@ def _on_endpoints_changed(self, _: DatabaseEndpointsChangedEvent) -> None:
"""Handle endpoints changed event."""
self._reconcile()

def _on_postgresql_relation_departed(self, _: ops.RelationDepartedEvent) -> None:
"""Handle postgresql relation departed event."""
self._reconcile()

def _on_ingress_ready(self, _: IngressPerAppReadyEvent) -> None:
"""Handle ingress ready event."""
self._reconcile()
Expand Down
125 changes: 125 additions & 0 deletions tests/unit/test_charm_scenario.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright 2025 Canonical Ltd.
# See LICENSE file for licensing details.

# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

"""Unit tests for the Maubot module using Scenario."""

import textwrap
from pathlib import Path

import ops
import pytest
import scenario
from scenario.context import _Event # needed for custom events for now

from charm import MaubotCharm


@pytest.fixture(scope="function", name="base_state")
def base_state_fixture(tmp_path: Path):
"""State with container and config file set."""
config_file_path = tmp_path / "config.yaml"
config_file_path.write_text(
textwrap.dedent(
"""
databases: null
server:
public_url: maubot.local
"""
),
encoding="utf-8",
)
yield {
"leader": True,
"containers": {
scenario.Container(
name="maubot",
can_connect=True,
execs={
scenario.Exec(
command_prefix=["cp"],
return_code=0,
),
scenario.Exec(
command_prefix=["mkdir"],
return_code=0,
),
},
mounts={
"data": scenario.Mount(location="/data/config.yaml", source=config_file_path)
},
)
},
}


def test_config_changed_no_postgresql(base_state: dict):
"""
arrange: prepare maubot container.
act: run config_changed.
assert: status is blocked because there is no postgresql integration.
"""
state = ops.testing.State(**base_state)
context = ops.testing.Context(
charm_type=MaubotCharm,
)
out = context.run(context.on.config_changed(), state)
assert out.unit_status == ops.testing.BlockedStatus("postgresql integration is required")


def test_config_changed_with_postgresql(base_state: dict):
"""
arrange: prepare maubot container.
act: run config_changed.
assert: status is blocked because there is no postgresql integration.
"""
endpoints = "1.2.3.4:5432"
username = "user"
password = "pass" # nosec
database = "maubot"
postgresql_relation = scenario.Relation(
endpoint="postgresql",
interface="postgresql_client",
remote_app_name="postgresql",
remote_app_data={
"endpoints": endpoints,
"username": username,
"password": password,
"database": database,
},
)
base_state["relations"] = [postgresql_relation]
state = ops.testing.State(**base_state)
context = ops.testing.Context(
charm_type=MaubotCharm,
)
out = context.run(context.on.config_changed(), state)
assert out.unit_status == ops.testing.ActiveStatus()
container_root_fs = list(base_state["containers"])[0].get_filesystem(context)
config_file = container_root_fs / "data" / "config.yaml"
assert f"postgresql://{username}:{password}@{endpoints}/{database}" in config_file.read_text()


def test_postgresql_relation_departed(base_state: dict):
"""
arrange: prepare maubot container.
act: run config_changed.
assert: status is blocked because there is no postgresql integration.
"""
postgresql_relation = scenario.Relation(
endpoint="postgresql",
interface="postgresql_client",
remote_app_name="postgresql",
)
base_state["relations"] = [postgresql_relation]
state = ops.testing.State(**base_state)
context = ops.testing.Context(
charm_type=MaubotCharm,
)
postgresql_relation_departed_event = _Event(
"postgresql_relation_departed", relation=postgresql_relation
)
out = context.run(postgresql_relation_departed_event, state)
assert out.unit_status == ops.testing.BlockedStatus("postgresql integration is required")
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ deps =
flake8-test-docs>=1.0
isort
mypy
ops[testing]
pep8-naming
pydocstyle>=2.10
pylint
Expand Down Expand Up @@ -77,6 +78,7 @@ deps =
pytest
pytest_asyncio
pytest_operator
ops[testing]
-r{toxinidir}/requirements.txt
commands =
coverage run --source={[vars]src_path} \
Expand Down

0 comments on commit 8408140

Please sign in to comment.