diff --git a/lib/charms/loki_k8s/v1/loki_push_api.py b/lib/charms/loki_k8s/v1/loki_push_api.py index 4bc4e22a..e2a61959 100644 --- a/lib/charms/loki_k8s/v1/loki_push_api.py +++ b/lib/charms/loki_k8s/v1/loki_push_api.py @@ -490,7 +490,7 @@ def _alert_rules_error(self, event): from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union from urllib import request -from urllib.error import HTTPError +from urllib.error import URLError import yaml from cosl import JujuTopology @@ -519,7 +519,7 @@ def _alert_rules_error(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 6 +LIBPATCH = 7 logger = logging.getLogger(__name__) @@ -2326,7 +2326,7 @@ def _ensure_promtail_binary(self, promtail_binaries: dict, container: Container) try: self._obtain_promtail(promtail_binaries[self._arch], container) - except HTTPError as e: + except URLError as e: msg = f"Promtail binary couldn't be downloaded - {str(e)}" logger.warning(msg) self.on.promtail_digest_error.emit(msg) diff --git a/lib/charms/observability_libs/v0/cert_handler.py b/lib/charms/observability_libs/v0/cert_handler.py index db14e00f..9dcfc8f1 100644 --- a/lib/charms/observability_libs/v0/cert_handler.py +++ b/lib/charms/observability_libs/v0/cert_handler.py @@ -37,22 +37,25 @@ import json import socket from itertools import filterfalse -from typing import List, Optional, Union +from typing import List, Optional, Union, cast try: - from charms.tls_certificates_interface.v2.tls_certificates import ( # type: ignore + from charms.tls_certificates_interface.v3.tls_certificates import ( # type: ignore AllCertificatesInvalidatedEvent, CertificateAvailableEvent, CertificateExpiringEvent, CertificateInvalidatedEvent, - TLSCertificatesRequiresV2, + TLSCertificatesRequiresV3, generate_csr, generate_private_key, ) -except ImportError: +except ImportError as e: raise ImportError( - "charms.tls_certificates_interface.v2.tls_certificates is missing; please get it through charmcraft fetch-lib" - ) + "failed to import charms.tls_certificates_interface.v3.tls_certificates; " + "Either the library itself is missing (please get it through charmcraft fetch-lib) " + "or one of its dependencies is unmet." + ) from e + import logging from ops.charm import CharmBase, RelationBrokenEvent @@ -64,7 +67,7 @@ LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a" LIBAPI = 0 -LIBPATCH = 9 +LIBPATCH = 11 def is_ip_address(value: str) -> bool: @@ -129,7 +132,7 @@ def __init__( self.peer_relation_name = peer_relation_name self.certificates_relation_name = certificates_relation_name - self.certificates = TLSCertificatesRequiresV2(self.charm, self.certificates_relation_name) + self.certificates = TLSCertificatesRequiresV3(self.charm, self.certificates_relation_name) self.framework.observe( self.charm.on.config_changed, @@ -279,7 +282,7 @@ def _generate_csr( if clear_cert: self._ca_cert = "" self._server_cert = "" - self._chain = [] + self._chain = "" def _on_certificate_available(self, event: CertificateAvailableEvent) -> None: """Get the certificate from the event and store it in a peer relation. @@ -301,7 +304,7 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None: if event_csr == self._csr: self._ca_cert = event.ca self._server_cert = event.certificate - self._chain = event.chain + self._chain = event.chain_as_pem() self.on.cert_changed.emit() # pyright: ignore @property @@ -372,21 +375,29 @@ def _server_cert(self, value: str): rel.data[self.charm.unit].update({"certificate": value}) @property - def _chain(self) -> List[str]: + def _chain(self) -> str: if self._peer_relation: - if chain := self._peer_relation.data[self.charm.unit].get("chain", []): - return json.loads(chain) - return [] + if chain := self._peer_relation.data[self.charm.unit].get("chain", ""): + chain = json.loads(chain) + + # In a previous version of this lib, chain used to be a list. + # Convert the List[str] to str, per + # https://github.com/canonical/tls-certificates-interface/pull/141 + if isinstance(chain, list): + chain = "\n\n".join(reversed(chain)) + + return cast(str, chain) + return "" @_chain.setter - def _chain(self, value: List[str]): + def _chain(self, value: str): # Caller must guard. We want the setter to fail loudly. Failure must have a side effect. rel = self._peer_relation assert rel is not None # For type checker rel.data[self.charm.unit].update({"chain": json.dumps(value)}) @property - def chain(self) -> List[str]: + def chain(self) -> str: """Return the ca chain.""" return self._chain diff --git a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py index 665af886..72c3fe72 100644 --- a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py +++ b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py @@ -362,7 +362,7 @@ def _on_scrape_targets_changed(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 44 +LIBPATCH = 45 PYDEPS = ["cosl"] @@ -1537,12 +1537,11 @@ def set_scrape_job_spec(self, _=None): relation.data[self._charm.app]["scrape_metadata"] = json.dumps(self._scrape_metadata) relation.data[self._charm.app]["scrape_jobs"] = json.dumps(self._scrape_jobs) - if alert_rules_as_dict: - # Update relation data with the string representation of the rule file. - # Juju topology is already included in the "scrape_metadata" field above. - # The consumer side of the relation uses this information to name the rules file - # that is written to the filesystem. - relation.data[self._charm.app]["alert_rules"] = json.dumps(alert_rules_as_dict) + # Update relation data with the string representation of the rule file. + # Juju topology is already included in the "scrape_metadata" field above. + # The consumer side of the relation uses this information to name the rules file + # that is written to the filesystem. + relation.data[self._charm.app]["alert_rules"] = json.dumps(alert_rules_as_dict) def _set_unit_ip(self, _=None): """Set unit host address. diff --git a/lib/charms/tempo_k8s/v1/charm_tracing.py b/lib/charms/tempo_k8s/v1/charm_tracing.py index c146e6d3..39ebcd46 100644 --- a/lib/charms/tempo_k8s/v1/charm_tracing.py +++ b/lib/charms/tempo_k8s/v1/charm_tracing.py @@ -146,9 +146,9 @@ def my_tracing_endpoint(self) -> Optional[str]: # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 2 +LIBPATCH = 5 -PYDEPS = ["opentelemetry-exporter-otlp-proto-http>=1.21.0"] +PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"] logger = logging.getLogger("tracing") @@ -205,7 +205,7 @@ def _get_tracer() -> Optional[Tracer]: return context_tracer.get() else: return None - except LookupError as err: + except LookupError: return None @@ -240,8 +240,8 @@ def _get_tracing_endpoint(tracing_endpoint_getter, self, charm): if tracing_endpoint is None: logger.debug( - "Charm tracing is disabled. Tracing endpoint is not defined - " - "tracing is not available or relation is not set." + f"{charm}.{tracing_endpoint_getter} returned None; quietly disabling " + f"charm_tracing for the run." ) return elif not isinstance(tracing_endpoint, str): @@ -310,7 +310,18 @@ def wrap_init(self: CharmBase, framework: Framework, *args, **kwargs): } ) provider = TracerProvider(resource=resource) - tracing_endpoint = _get_tracing_endpoint(tracing_endpoint_getter, self, charm) + try: + tracing_endpoint = _get_tracing_endpoint(tracing_endpoint_getter, self, charm) + except Exception: + # if anything goes wrong with retrieving the endpoint, we go on with tracing disabled. + # better than breaking the charm. + logger.exception( + f"exception retrieving the tracing " + f"endpoint from {charm}.{tracing_endpoint_getter}; " + f"proceeding with charm_tracing DISABLED. " + ) + return + if not tracing_endpoint: return @@ -528,6 +539,10 @@ def wrapped_function(*args, **kwargs): # type: ignore name = getattr(callable, "__qualname__", getattr(callable, "__name__", str(callable))) with _span(f"{'(static) ' if static else ''}{qualifier} call: {name}"): # type: ignore if static: + # fixme: do we or don't we need [1:]? + # The _trace_callable decorator doesn't always play nice with @staticmethods. + # Sometimes it will receive 'self', sometimes it won't. + # return callable(*args, **kwargs) # type: ignore return callable(*args[1:], **kwargs) # type: ignore return callable(*args, **kwargs) # type: ignore diff --git a/lib/charms/tempo_k8s/v2/tracing.py b/lib/charms/tempo_k8s/v2/tracing.py index 1660c971..d466531e 100644 --- a/lib/charms/tempo_k8s/v2/tracing.py +++ b/lib/charms/tempo_k8s/v2/tracing.py @@ -75,7 +75,6 @@ def __init__(self, *args): TYPE_CHECKING, Any, Dict, - Iterable, List, Literal, MutableMapping, @@ -105,7 +104,7 @@ def __init__(self, *args): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 1 +LIBPATCH = 2 PYDEPS = ["pydantic"] @@ -818,8 +817,13 @@ def get_endpoint( endpoint = self._get_endpoint(relation or self._relation, protocol=protocol) if not endpoint: requested_protocols = set() - for relation in self.relations: - databag = TracingRequirerAppData.load(relation.data[self._charm.app]) + relations = [relation] if relation else self.relations + for relation in relations: + try: + databag = TracingRequirerAppData.load(relation.data[self._charm.app]) + except DataValidationError: + continue + requested_protocols.update(databag.receivers) if protocol not in requested_protocols: