Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
alithethird committed Feb 5, 2025
2 parents 514eba0 + 7985e3b commit 3e667e9
Show file tree
Hide file tree
Showing 35 changed files with 10,974 additions and 644 deletions.
530 changes: 410 additions & 120 deletions examples/django/charm/lib/charms/data_platform_libs/v0/data_interfaces.py

Large diffs are not rendered by default.

791 changes: 791 additions & 0 deletions examples/django/charm/lib/charms/data_platform_libs/v0/s3.py

Large diffs are not rendered by default.

2,510 changes: 2,510 additions & 0 deletions examples/django/charm/lib/charms/loki_k8s/v0/loki_push_api.py

Large diffs are not rendered by default.

81 changes: 76 additions & 5 deletions examples/django/charm/lib/charms/loki_k8s/v1/loki_push_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,25 @@ def _alert_rules_error(self, event):
Units of consumer charm send their alert rules over app relation data using the `alert_rules`
key.
## Charm logging
The `charms.loki_k8s.v0.charm_logging` library can be used in conjunction with this one to configure python's
logging module to forward all logs to Loki via the loki-push-api interface.
```python
from lib.charms.loki_k8s.v0.charm_logging import log_charm
from lib.charms.loki_k8s.v1.loki_push_api import charm_logging_config, LokiPushApiConsumer
@log_charm(logging_endpoint="my_endpoints", server_cert="cert_path")
class MyCharm(...):
_cert_path = "/path/to/cert/on/charm/container.crt"
def __init__(self, ...):
self.logging = LokiPushApiConsumer(...)
self.my_endpoints, self.cert_path = charm_logging_config(
self.logging, self._cert_path)
```
Do this, and all charm logs will be forwarded to Loki as soon as a relation is formed.
"""

import json
Expand Down Expand Up @@ -527,7 +546,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 = 12
LIBPATCH = 13

PYDEPS = ["cosl"]

Expand Down Expand Up @@ -577,7 +596,11 @@ def _alert_rules_error(self, event):
GRPC_LISTEN_PORT_START = 9095 # odd start port


class RelationNotFoundError(ValueError):
class LokiPushApiError(Exception):
"""Base class for errors raised by this module."""


class RelationNotFoundError(LokiPushApiError):
"""Raised if there is no relation with the given name."""

def __init__(self, relation_name: str):
Expand All @@ -587,7 +610,7 @@ def __init__(self, relation_name: str):
super().__init__(self.message)


class RelationInterfaceMismatchError(Exception):
class RelationInterfaceMismatchError(LokiPushApiError):
"""Raised if the relation with the given name has a different interface."""

def __init__(
Expand All @@ -607,7 +630,7 @@ def __init__(
super().__init__(self.message)


class RelationRoleMismatchError(Exception):
class RelationRoleMismatchError(LokiPushApiError):
"""Raised if the relation with the given name has a different direction."""

def __init__(
Expand Down Expand Up @@ -2555,7 +2578,7 @@ def _on_pebble_ready(self, event: PebbleReadyEvent):

self._update_endpoints(event.workload, loki_endpoints)

def _update_logging(self, _):
def _update_logging(self, event: RelationEvent):
"""Update the log forwarding to match the active Loki endpoints."""
if not (loki_endpoints := self._retrieve_endpoints_from_relation()):
logger.warning("No Loki endpoints available")
Expand All @@ -2566,6 +2589,8 @@ def _update_logging(self, _):
self._update_endpoints(container, loki_endpoints)
# else: `_update_endpoints` will be called on pebble-ready anyway.

self._handle_alert_rules(event.relation)

def _retrieve_endpoints_from_relation(self) -> dict:
loki_endpoints = {}

Expand Down Expand Up @@ -2750,3 +2775,49 @@ def _exec(self, cmd) -> str:
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE)
output = result.stdout.decode("utf-8").strip()
return output


def charm_logging_config(
endpoint_requirer: LokiPushApiConsumer, cert_path: Optional[Union[Path, str]]
) -> Tuple[Optional[List[str]], Optional[str]]:
"""Utility function to determine the charm_logging config you will likely want.
If no endpoint is provided:
disable charm logging.
If https endpoint is provided but cert_path is not found on disk:
disable charm logging.
If https endpoint is provided and cert_path is None:
ERROR
Else:
proceed with charm logging (with or without tls, as appropriate)
Args:
endpoint_requirer: an instance of LokiPushApiConsumer.
cert_path: a path where a cert is stored.
Returns:
A tuple with (optionally) the values of the endpoints and the certificate path.
Raises:
LokiPushApiError: if some endpoint are http and others https.
"""
endpoints = [ep["url"] for ep in endpoint_requirer.loki_endpoints]
if not endpoints:
return None, None

https = tuple(endpoint.startswith("https://") for endpoint in endpoints)

if all(https): # all endpoints are https
if cert_path is None:
raise LokiPushApiError("Cannot send logs to https endpoints without a certificate.")
if not Path(cert_path).exists():
# if endpoints is https BUT we don't have a server_cert yet:
# disable charm logging until we do to prevent tls errors
return None, None
return endpoints, str(cert_path)

if all(not x for x in https): # all endpoints are http
return endpoints, None

# if there's a disagreement, that's very weird:
raise LokiPushApiError("Some endpoints are http, some others are https. That's not good.")
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 47
LIBPATCH = 48

PYDEPS = ["cosl"]

Expand Down Expand Up @@ -2364,12 +2364,9 @@ def _get_tool_path(self) -> Optional[Path]:
arch = "amd64" if arch == "x86_64" else arch
res = "cos-tool-{}".format(arch)
try:
path = Path(res).resolve()
path.chmod(0o777)
path = Path(res).resolve(strict=True)
return path
except NotImplementedError:
logger.debug("System lacks support for chmod")
except FileNotFoundError:
except (FileNotFoundError, OSError):
logger.debug('Could not locate cos-tool at: "{}"'.format(res))
return None

Expand Down
24 changes: 21 additions & 3 deletions examples/django/charm/lib/charms/redis_k8s/v0/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

# 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__)

Expand Down Expand Up @@ -78,6 +78,18 @@ def _on_relation_broken(self, event):
# Trigger an event that our charm can react to.
self.charm.on.redis_relation_updated.emit()

@property
def app_data(self) -> Optional[Dict[str, str]]:
"""Retrieve the app data.
Returns:
Dict: dict containing the app data.
"""
relation = self.model.get_relation(self.relation_name)
if not relation:
return None
return relation.data[relation.app]

@property
def relation_data(self) -> Optional[Dict[str, str]]:
"""Retrieve the relation data.
Expand All @@ -98,10 +110,16 @@ def url(self) -> Optional[str]:
Returns:
str: the Redis URL.
"""
relation_data = self.relation_data
if not relation_data:
if not (relation_data := self.relation_data):
return None

redis_host = relation_data.get("hostname")

if app_data := self.app_data:
try:
redis_host = self.app_data.get("leader-host", redis_host)
except KeyError:
pass
redis_port = relation_data.get("port")
return f"redis://{redis_host}:{redis_port}"

Expand Down
Loading

0 comments on commit 3e667e9

Please sign in to comment.