From c4d396be206e2c4b0a286875384304e83613a5ac Mon Sep 17 00:00:00 2001 From: Luca Bello Date: Thu, 14 Nov 2024 11:17:18 +0100 Subject: [PATCH 1/2] feat: support external ca from cloud-integrator --- .../v0/cloud_config_requirer.py | 49 ++++++++++++++----- src/grafana_agent.py | 9 +++- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lib/charms/grafana_cloud_integrator/v0/cloud_config_requirer.py b/lib/charms/grafana_cloud_integrator/v0/cloud_config_requirer.py index 59f72e7b..07cf4a9d 100644 --- a/lib/charms/grafana_cloud_integrator/v0/cloud_config_requirer.py +++ b/lib/charms/grafana_cloud_integrator/v0/cloud_config_requirer.py @@ -1,12 +1,12 @@ """Grafana Cloud Integrator Configuration Requirer.""" + import logging from ops.framework import EventBase, EventSource, Object, ObjectEvents - LIBID = "e6f580481c1b4388aa4d2cdf412a47fa" LIBAPI = 0 -LIBPATCH = 7 +LIBPATCH = 8 DEFAULT_RELATION_NAME = "grafana-cloud-config" @@ -14,6 +14,8 @@ class Credentials: + """Credentials for the remote endpoints.""" + def __init__(self, username, password): self.username = username self.password = password @@ -25,23 +27,27 @@ class CloudConfigAvailableEvent(EventBase): def __init__(self, handle): super().__init__(handle) + class CloudConfigRevokedEvent(EventBase): """Event emitted when cloud config is available.""" def __init__(self, handle): super().__init__(handle) + class GrafanaCloudConfigEvents(ObjectEvents): """Event descriptor for events raised by `GrafanaCloudConfigRequirer`.""" cloud_config_available = EventSource(CloudConfigAvailableEvent) cloud_config_revoked = EventSource(CloudConfigRevokedEvent) + class GrafanaCloudConfigRequirer(Object): + """Requirer side of the Grafana Cloud Config relation.""" on = GrafanaCloudConfigEvents() # pyright: ignore - def __init__(self, charm, relation_name = DEFAULT_RELATION_NAME): + def __init__(self, charm, relation_name=DEFAULT_RELATION_NAME): super().__init__(charm, relation_name) self._charm = charm self._relation_name = relation_name @@ -63,7 +69,7 @@ def _is_not_empty(self, s): @property def _change_events(self): - return [ + return [ self._events.relation_joined, self._events.relation_changed, self._events.relation_created, @@ -71,10 +77,7 @@ def _change_events(self): @property def _broken_events(self): - return [ - self._events.relation_departed, - self._events.relation_broken - ] + return [self._events.relation_departed, self._events.relation_broken] @property def _events(self): @@ -83,12 +86,15 @@ def _events(self): @property def credentials(self): """Return the credentials, if any; otherwise, return None.""" - if (username := self._data.get("username", "").strip()) and (password := self._data.get("password", "").strip()): + if (username := self._data.get("username", "").strip()) and ( + password := self._data.get("password", "").strip() + ): return Credentials(username, password) return None @property def loki_ready(self): + """Check whether there is a non-empty Loki url in relation data.""" return self._is_not_empty(self.loki_url) @property @@ -100,17 +106,27 @@ def loki_endpoint(self) -> dict: endpoint = {} endpoint["url"] = self.loki_url if self.credentials: - endpoint["basic_auth"] = {"username": self.credentials.username, "password": self.credentials.password} + endpoint["basic_auth"] = { + "username": self.credentials.username, + "password": self.credentials.password, + } return endpoint @property def prometheus_ready(self): + """Check whether there is a non-empty Prometheus url in relation data.""" return self._is_not_empty(self.prometheus_url) @property def tempo_ready(self): + """Check whether there is a non-empty Tempo url in relation data.""" return self._is_not_empty(self.tempo_url) + @property + def tls_ca_ready(self): + """Check whether there is a TLS CA in relation data.""" + return self._is_not_empty(self.tls_ca) + @property def prometheus_endpoint(self) -> dict: """Return the prometheus endpoint dict.""" @@ -120,21 +136,32 @@ def prometheus_endpoint(self) -> dict: endpoint = {} endpoint["url"] = self.prometheus_url if self.credentials: - endpoint["basic_auth"] = {"username": self.credentials.username, "password": self.credentials.password} + endpoint["basic_auth"] = { + "username": self.credentials.username, + "password": self.credentials.password, + } return endpoint @property def loki_url(self) -> str: + """The Loki endpoint from relation data.""" return self._data.get("loki_url", "") @property def tempo_url(self) -> str: + """The Tempo endpoint from relation data.""" return self._data.get("tempo_url", "") @property def prometheus_url(self) -> str: + """The Prometheus endpoint from relation data.""" return self._data.get("prometheus_url", "") + @property + def tls_ca(self) -> str: + """TLS CA from relation data.""" + return self._data.get("tls-ca", "") + @property def _data(self): for relation in self._charm.model.relations[self._relation_name]: diff --git a/src/grafana_agent.py b/src/grafana_agent.py index 8a591c7c..c2e9b323 100644 --- a/src/grafana_agent.py +++ b/src/grafana_agent.py @@ -92,6 +92,7 @@ class GrafanaAgentCharm(CharmBase): _key_path = "/tmp/agent/grafana-agent.key" _ca_path = "/usr/local/share/ca-certificates/grafana-agent-operator.crt" _ca_folder_path = "/usr/local/share/ca-certificates" + _cloud_ca_path = "/usr/local/share/ca-certificates/cloud-integrator.crt" # mapping from tempo-supported receivers to the receiver ports to be opened on the grafana-agent host _tracing_receivers_ports: Dict[ReceiverProtocol, int] = { @@ -349,6 +350,12 @@ def _on_config_changed(self, _event=None): def _on_cloud_config_available(self, _) -> None: logger.info("cloud config available") + # Write CA from cloud config + if self._cloud.tls_ca_ready: + self.write_file(self._cloud_ca_path, self._cloud.tls_ca) + else: + self._delete_file_if_exists(self._cloud_ca_path) + self.run(["update-ca-certificates", "--fresh"]) self._update_config() self._update_tracing_provider() @@ -1034,7 +1041,7 @@ def _loki_config(self) -> Dict[str, Union[Any, List[Any]]]: a dict with Loki config """ configs = [] - if self._loki_consumer.loki_endpoints: + if self._loki_consumer.loki_endpoints or self._cloud.loki_ready: configs.append( { "name": "push_api_server", From 6ff41531120fd6e08cdb2914694dd12b47ab4249 Mon Sep 17 00:00:00 2001 From: Luca Bello Date: Thu, 14 Nov 2024 15:56:43 +0100 Subject: [PATCH 2/2] add comment from pr review --- src/grafana_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/grafana_agent.py b/src/grafana_agent.py index c2e9b323..28deb433 100644 --- a/src/grafana_agent.py +++ b/src/grafana_agent.py @@ -92,6 +92,7 @@ class GrafanaAgentCharm(CharmBase): _key_path = "/tmp/agent/grafana-agent.key" _ca_path = "/usr/local/share/ca-certificates/grafana-agent-operator.crt" _ca_folder_path = "/usr/local/share/ca-certificates" + # We have a `limit: 1` on the cloud integrator relation so we expect only one such cert. _cloud_ca_path = "/usr/local/share/ca-certificates/cloud-integrator.crt" # mapping from tempo-supported receivers to the receiver ports to be opened on the grafana-agent host