From 54160889fcc200f2e9c132f86602e8a9444189ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 03:29:19 +0000 Subject: [PATCH 1/3] Initial plan From b69d836cf702ede424e182fe947060fdd8c4c614 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 03:33:21 +0000 Subject: [PATCH 2/3] Fix CMR support: use service-level DNS for cross-model nginx-proxy relations In a Cross-Model Relation (CMR), remote unit names follow the pattern remote-/ (Juju-internal proxy tokens), not real Kubernetes pod names. The previous code used these tokens to construct headless pod DNS entries that don't exist in the remote namespace. Detect CMR proxy units by checking for the remote- prefix on unit names, then use Kubernetes service-level DNS with the service-namespace from the relation app data (set by NginxRouteRequirer to the requirer's model name): http://{svc_name}.{svc_namespace}.svc.cluster.local:{svc_port} This lets nginx correctly resolve and proxy to the backend service in the remote Juju model on the same Kubernetes cluster. Closes #193 Co-authored-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> --- src/charm.py | 29 ++++++++++++++++----- tests/unit/test_charm.py | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/charm.py b/src/charm.py index 2913b42f..e5c1a3d3 100755 --- a/src/charm.py +++ b/src/charm.py @@ -356,13 +356,28 @@ def _make_env_config(self, domain="svc.cluster.local") -> dict | None: svc_name = relation.data[relation.app].get("service-name") svc_port = relation.data[relation.app].get("service-port") backend_site_name = relation.data[relation.app].get("service-hostname") - clients = [] - for peer in relation.units: - unit_name = peer.name.replace("/", "-") - service_url = f"{unit_name}.{svc_name}-endpoints.{self.model.name}.{domain}" - clients.append(f"http://{service_url}:{svc_port}") - # XXX: Will need to deal with multiple units at some point - backend = clients[0] + # In a Cross-Model Relation (CMR), remote unit names follow the pattern + # "remote-/" and cannot be used to build headless pod DNS entries + # (the unit name is a Juju-internal proxy token, not a real K8s pod name). + # Detect CMR by checking for the "remote-" prefix, and fall back to + # service-level DNS using the namespace provided in the relation data. + is_cmr = any(peer.name.startswith("remote-") for peer in relation.units) + if is_cmr: + svc_namespace = relation.data[relation.app].get( + "service-namespace", self.model.name + ) + service_url = f"{svc_name}.{svc_namespace}.{domain}" + backend = f"http://{service_url}:{svc_port}" + else: + clients = [] + for peer in relation.units: + unit_name = peer.name.replace("/", "-") + service_url = ( + f"{unit_name}.{svc_name}-endpoints.{self.model.name}.{domain}" + ) + clients.append(f"http://{service_url}:{svc_port}") + # XXX: Will need to deal with multiple units at some point + backend = clients[0] elif relation: return None else: diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index c8514b7d..fd0aca75 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -501,6 +501,61 @@ def test_make_env_config_with_proxy_relation(self): new_site = new_env_config["CONTENT_CACHE_SITE"] assert new_site == relations_data["service-hostname"] + def test_make_env_config_with_cmr_proxy_relation(self): + """ + arrange: set nginx-proxy relation with a CMR (Cross-Model Relation) remote unit + act: verify env config + assert: backend uses service-level DNS with service-namespace, not pod-level DNS + """ + config = self.config + harness = self.harness + harness.disable_hooks() + harness.update_config(config) + # CMR remote application names follow the "remote-" pattern + cmr_app_name = "remote-9e5538f0707b46568cc9b04d9f39f708" + relation_id = harness.add_relation("nginx-proxy", cmr_app_name) + harness.add_relation_unit(relation_id, f"{cmr_app_name}/0") + relations_data = { + "service-name": "indico", + "service-hostname": "indico.example.com", + "service-port": "8080", + "service-namespace": "target-model", + } + harness.update_relation_data(relation_id, cmr_app_name, relations_data) + env_config = harness.charm._make_env_config() + # The backend must use service-level K8s DNS, not pod-level headless DNS + assert env_config["CONTENT_CACHE_BACKEND"] == ( + "http://indico.target-model.svc.cluster.local:8080" + ) + assert env_config["NGINX_BACKEND"] == "http://indico.target-model.svc.cluster.local:8080" + assert env_config["CONTENT_CACHE_SITE"] == "indico.example.com" + + def test_make_env_config_with_cmr_proxy_relation_no_namespace(self): + """ + arrange: set nginx-proxy relation with a CMR remote unit but no service-namespace + act: verify env config + assert: backend falls back to service-level DNS using the local model name + """ + config = self.config + harness = self.harness + harness.disable_hooks() + harness.update_config(config) + cmr_app_name = "remote-9e5538f0707b46568cc9b04d9f39f708" + relation_id = harness.add_relation("nginx-proxy", cmr_app_name) + harness.add_relation_unit(relation_id, f"{cmr_app_name}/0") + relations_data = { + "service-name": "indico", + "service-hostname": "indico.example.com", + "service-port": "8080", + } + harness.update_relation_data(relation_id, cmr_app_name, relations_data) + env_config = harness.charm._make_env_config() + # Falls back to local model name when service-namespace is absent + local_model = harness.charm.model.name + assert env_config["CONTENT_CACHE_BACKEND"] == ( + f"http://indico.{local_model}.svc.cluster.local:8080" + ) + def test_make_pebble_config(self): """ arrange: define pebble config From 5b54f42940d89570efd672b05e90780196d94837 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:23:29 +0000 Subject: [PATCH 3/3] Fix ruff formatting: unwrap unnecessary parentheses in charm.py Co-authored-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> --- src/charm.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/charm.py b/src/charm.py index e5c1a3d3..4bd224c0 100755 --- a/src/charm.py +++ b/src/charm.py @@ -372,9 +372,7 @@ def _make_env_config(self, domain="svc.cluster.local") -> dict | None: clients = [] for peer in relation.units: unit_name = peer.name.replace("/", "-") - service_url = ( - f"{unit_name}.{svc_name}-endpoints.{self.model.name}.{domain}" - ) + service_url = f"{unit_name}.{svc_name}-endpoints.{self.model.name}.{domain}" clients.append(f"http://{service_url}:{svc_port}") # XXX: Will need to deal with multiple units at some point backend = clients[0]