diff --git a/single_kernel_postgresql/charms/k8s_charm.py b/single_kernel_postgresql/charms/k8s_charm.py index 70405db4..7d6ed17c 100644 --- a/single_kernel_postgresql/charms/k8s_charm.py +++ b/single_kernel_postgresql/charms/k8s_charm.py @@ -10,7 +10,7 @@ import sys from functools import cached_property from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast import psycopg2 from lightkube import Client @@ -62,6 +62,9 @@ class PostgreSQLK8sCharm(AbstractPostgreSQLCharm): """PostgreSQL K8s Charm.""" + # COS requirers are constructed charm-side and assigned by the consuming charm. + metrics_endpoint: Any = None + # Built by build_k8s_manager() while the abstract __init__ wires the # async-replication subsystem; never None on the K8s substrate. k8s_manager: K8sManager @@ -284,9 +287,7 @@ def _restart_metrics_service(self) -> None: Layer( LayerDict({ "services": { - K8S_METRICS_SERVER_SERVICE_NAME: cast( - "ServiceDict", self._generate_metrics_service() - ) + K8S_METRICS_SERVER_SERVICE_NAME: self._generate_metrics_service() } }) ), @@ -341,7 +342,7 @@ def _generate_metrics_jobs(self, enable_tls: bool) -> list[dict]: }, ] - def _generate_metrics_service(self) -> dict: + def _generate_metrics_service(self) -> "ServiceDict": """Generate the metrics exporter service definition (port).""" workload = cast("K8sWorkload", self.workload) monitoring_password = self.state.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY) diff --git a/single_kernel_postgresql/managers/k8s.py b/single_kernel_postgresql/managers/k8s.py index 09301240..0c870ab4 100644 --- a/single_kernel_postgresql/managers/k8s.py +++ b/single_kernel_postgresql/managers/k8s.py @@ -315,6 +315,7 @@ def reparent_services_on_stop(self) -> None: name=resource.metadata.name, namespace=resource.metadata.namespace, force=True, + field_manager=app_name, ) except ApiError: logger.exception( diff --git a/single_kernel_postgresql/workload/k8s.py b/single_kernel_postgresql/workload/k8s.py index fef77b73..84e939d3 100644 --- a/single_kernel_postgresql/workload/k8s.py +++ b/single_kernel_postgresql/workload/k8s.py @@ -807,7 +807,7 @@ def generate_ldap_service( def generate_metrics_service( self, monitoring_password: str | None, postgresql_service: str - ) -> dict: + ) -> ServiceDict: """Generate the metrics exporter service definition (port).""" return { "override": "replace", diff --git a/tests/unit/test_k8s_resources.py b/tests/unit/test_k8s_resources.py index ca93888f..6399938a 100644 --- a/tests/unit/test_k8s_resources.py +++ b/tests/unit/test_k8s_resources.py @@ -118,6 +118,9 @@ def test_reparent_services_on_stop(harness, k8s_manager): # The same Patroni-created resource is listed twice (Service + Endpoints) and # reparented on each sighting — faithful to the charm's double-listing flow. assert client.apply.call_count == 4 + # The charm builds its stop-flow Client with field_manager=self.model.app.name: + # lightkube's APPLY patch requires one (bare Client() + apply() raises ValueError). + assert all(call.kwargs.get("field_manager") == app for call in client.apply.call_args_list) def test_cleanup_old_cluster_resources_deletes_suffixes(harness, k8s_manager):