Skip to content

Commit

Permalink
chore: update charm libraries (#259)
Browse files Browse the repository at this point in the history
Co-authored-by: Github Actions <[email protected]>
  • Loading branch information
observability-noctua-bot and Github Actions authored Jan 3, 2024
1 parent a086104 commit 4ed63ce
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):
import logging
from typing import List

from jsonschema import exceptions, validate # type: ignore[import]
from jsonschema import exceptions, validate # type: ignore[import-untyped]
from ops.charm import CharmBase, CharmEvents, RelationBrokenEvent, RelationChangedEvent
from ops.framework import EventBase, EventSource, Handle, Object

Expand All @@ -109,7 +109,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 4
LIBPATCH = 5

PYDEPS = ["jsonschema"]

Expand Down
64 changes: 41 additions & 23 deletions lib/charms/grafana_cloud_integrator/v0/cloud_config_requirer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

LIBID = "e6f580481c1b4388aa4d2cdf412a47fa"
LIBAPI = 0
LIBPATCH = 3
LIBPATCH = 4

DEFAULT_RELATION_NAME = "grafana-cloud-config"

Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(self, charm, relation_name = DEFAULT_RELATION_NAME):
super().__init__(charm, relation_name)
self._charm = charm
self._relation_name = relation_name

for event in self._change_events:
self.framework.observe(event, self._on_relation_changed)

Expand All @@ -56,14 +56,6 @@ def _on_relation_changed(self, event):
if not self._charm.unit.is_leader():
return

if not all(
self._is_not_empty(x)
for x in [
event.relation.data[event.app].get("username", ""),
event.relation.data[event.app].get("password", ""),
]):
return

self.on.cloud_config_available.emit() # pyright: ignore

def _on_relation_broken(self, event):
Expand Down Expand Up @@ -96,29 +88,55 @@ def _events(self):

@property
def credentials(self):
return Credentials(
self._data.get("username", ""),
self._data.get("password", "")
)
"""Return the credentials, if any; otherwise, return None."""
if not all(
self._is_not_empty(x)
for x in [
self._data.get("username", ""),
self._data.get("password", ""),
]):
return Credentials(
self._data.get("username", ""),
self._data.get("password", "")
)
return None

@property
def loki_ready(self):
return (
self._is_not_empty(self.credentials.username)
and self._is_not_empty(self.credentials.password)
and self._is_not_empty(self.loki_url))
return self._is_not_empty(self.loki_url)

@property
def loki_endpoint(self) -> dict:
"""Return the loki endpoint dict."""
if not self.loki_ready:
return {}

endpoint = {}
endpoint["url"] = self.loki_url
if self.credentials:
endpoint["basic_auth"] = {"username": self.credentials.username, "password": self.credentials.password}
return endpoint

@property
def prometheus_ready(self):
return (
self._is_not_empty(self.credentials.username)
and self._is_not_empty(self.credentials.password)
and self._is_not_empty(self.prometheus_url))
return self._is_not_empty(self.prometheus_url)

@property
def prometheus_endpoint(self) -> dict:
"""Return the prometheus endpoint dict."""
if not self.prometheus_ready:
return {}

endpoint = {}
endpoint["url"] = self.prometheus_url
if self.credentials:
endpoint["basic_auth"] = {"username": self.credentials.username, "password": self.credentials.password}
return endpoint

@property
def loki_url(self):
return self._data.get("loki_url", "")

@property
def prometheus_url(self):
return self._data.get("prometheus_url", "")
Expand Down
56 changes: 38 additions & 18 deletions lib/charms/loki_k8s/v0/loki_push_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
implement the provider side of the `loki_push_api` relation interface. For instance, a Loki charm.
The provider side of the relation represents the server side, to which logs are being pushed.
- `LokiPushApiConsumer`: This object is meant to be used by any Charmed Operator that needs to
send log to Loki by implementing the consumer side of the `loki_push_api` relation interface.
For instance, a Promtail or Grafana agent charm which needs to send logs to Loki.
- `LokiPushApiConsumer`: Used to obtain the loki api endpoint. This is useful for configuring
applications such as pebble, or charmed operators of workloads such as grafana-agent or promtail,
that can communicate with loki directly.
- `LogProxyConsumer`: This object can be used by any Charmed Operator which needs to
send telemetry, such as logs, to Loki through a Log Proxy by implementing the consumer side of the
Expand Down Expand Up @@ -480,7 +480,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 = 21
LIBPATCH = 24

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -604,7 +604,9 @@ def _validate_relation_by_interface_and_direction(
actual_relation_interface = relation.interface_name
if actual_relation_interface != expected_relation_interface:
raise RelationInterfaceMismatchError(
relation_name, expected_relation_interface, actual_relation_interface
relation_name,
expected_relation_interface,
actual_relation_interface, # pyright: ignore
)

if expected_relation_role == RelationRole.provides:
Expand Down Expand Up @@ -866,20 +868,20 @@ def _from_dir(self, dir_path: Path, recursive: bool) -> List[dict]:

return alert_groups

def add_path(self, path: str, *, recursive: bool = False):
def add_path(self, path_str: str, *, recursive: bool = False):
"""Add rules from a dir path.
All rules from files are aggregated into a data structure representing a single rule file.
All group names are augmented with juju topology.
Args:
path: either a rules file or a dir of rules files.
path_str: either a rules file or a dir of rules files.
recursive: whether to read files recursively or not (no impact if `path` is a file).
Raises:
InvalidAlertRulePathError: if the provided path is invalid.
"""
path = Path(path) # type: Path
path = Path(path_str) # type: Path
if path.is_dir():
self.alert_groups.extend(self._from_dir(path, recursive))
elif path.is_file():
Expand Down Expand Up @@ -992,6 +994,8 @@ def __init__(self, handle, relation, relation_id, app=None, unit=None):

def snapshot(self) -> Dict:
"""Save event information."""
if not self.relation:
return {}
snapshot = {"relation_name": self.relation.name, "relation_id": self.relation.id}
if self.app:
snapshot["app_name"] = self.app.name
Expand Down Expand Up @@ -1052,7 +1056,7 @@ class LokiPushApiEvents(ObjectEvents):
class LokiPushApiProvider(Object):
"""A LokiPushApiProvider class."""

on = LokiPushApiEvents()
on = LokiPushApiEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1146,11 +1150,11 @@ def _on_logging_relation_changed(self, event: HookEvent):
event: a `CharmEvent` in response to which the consumer
charm must update its relation data.
"""
should_update = self._process_logging_relation_changed(event.relation)
should_update = self._process_logging_relation_changed(event.relation) # pyright: ignore
if should_update:
self.on.loki_push_api_alert_rules_changed.emit(
relation=event.relation,
relation_id=event.relation.id,
relation=event.relation, # pyright: ignore
relation_id=event.relation.id, # pyright: ignore
app=self._charm.app,
unit=self._charm.unit,
)
Expand Down Expand Up @@ -1517,7 +1521,7 @@ def loki_endpoints(self) -> List[dict]:
class LokiPushApiConsumer(ConsumerBase):
"""Loki Consumer class."""

on = LokiPushApiEvents()
on = LokiPushApiEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1760,7 +1764,7 @@ class LogProxyConsumer(ConsumerBase):
role.
"""

on = LogProxyEvents()
on = LogProxyEvents() # pyright: ignore

def __init__(
self,
Expand All @@ -1773,6 +1777,8 @@ def __init__(
recursive: bool = False,
container_name: str = "",
promtail_resource_name: Optional[str] = None,
*, # TODO: In v1, move the star up so everything after 'charm' is a kwarg
insecure_skip_verify: bool = False,
):
super().__init__(charm, relation_name, alert_rules_path, recursive)
self._charm = charm
Expand All @@ -1792,6 +1798,7 @@ def __init__(
self._is_syslog = enable_syslog
self.topology = JujuTopology.from_charm(charm)
self._promtail_resource_name = promtail_resource_name or "promtail-bin"
self.insecure_skip_verify = insecure_skip_verify

# architecture used for promtail binary
arch = platform.processor()
Expand Down Expand Up @@ -1882,7 +1889,7 @@ def _on_relation_departed(self, _: RelationEvent) -> None:
self._container.stop(WORKLOAD_SERVICE_NAME)
self.on.log_proxy_endpoint_departed.emit()

def _get_container(self, container_name: str = "") -> Container:
def _get_container(self, container_name: str = "") -> Container: # pyright: ignore
"""Gets a single container by name or using the only container running in the Pod.
If there is more than one container in the Pod a `PromtailDigestError` is emitted.
Expand Down Expand Up @@ -1956,7 +1963,9 @@ def _add_pebble_layer(self, workload_binary_path: str) -> None:
}
},
}
self._container.add_layer(self._container_name, pebble_layer, combine=True)
self._container.add_layer(
self._container_name, pebble_layer, combine=True # pyright: ignore
)

def _create_directories(self) -> None:
"""Creates the directories for Promtail binary and config file."""
Expand Down Expand Up @@ -1993,7 +2002,11 @@ def _push_binary_to_workload(self, binary_path: str, workload_binary_path: str)
"""
with open(binary_path, "rb") as f:
self._container.push(
workload_binary_path, f, permissions=0o755, encoding=None, make_dirs=True
workload_binary_path,
f,
permissions=0o755,
encoding=None, # pyright: ignore
make_dirs=True,
)
logger.debug("The promtail binary file has been pushed to the workload container.")

Expand Down Expand Up @@ -2153,8 +2166,15 @@ def _current_config(self) -> dict:

@property
def _promtail_config(self) -> dict:
"""Generates the config file for Promtail."""
"""Generates the config file for Promtail.
Reference: https://grafana.com/docs/loki/latest/send-data/promtail/configuration
"""
config = {"clients": self._clients_list()}
if self.insecure_skip_verify:
for client in config["clients"]:
client["tls_config"] = {"insecure_skip_verify": True}

config.update(self._server_config())
config.update(self._positions())
config.update(self._scrape_configs())
Expand Down
38 changes: 25 additions & 13 deletions lib/charms/observability_libs/v0/cert_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@

LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a"
LIBAPI = 0
LIBPATCH = 8
LIBPATCH = 9


def is_ip_address(value: str) -> bool:
Expand Down Expand Up @@ -181,33 +181,40 @@ def _peer_relation(self) -> Optional[Relation]:
return self.charm.model.get_relation(self.peer_relation_name, None)

def _on_peer_relation_created(self, _):
"""Generate the private key and store it in a peer relation."""
# We're in "relation-created", so the relation should be there
"""Generate the CSR if the certificates relation is ready."""
self._generate_privkey()

# Just in case we already have a private key, do not overwrite it.
# Not sure how this could happen.
# TODO figure out how to go about key rotation.
if not self._private_key:
private_key = generate_private_key()
self._private_key = private_key.decode()

# Generate CSR here, in case peer events fired after tls-certificate relation events
# check cert relation is ready
if not (self.charm.model.get_relation(self.certificates_relation_name)):
# peer relation event happened to fire before tls-certificates events.
# Abort, and let the "certificates joined" observer create the CSR.
logger.info("certhandler waiting on certificates relation")
return

logger.debug("certhandler has peer and certs relation: proceeding to generate csr")
self._generate_csr()

def _on_certificates_relation_joined(self, _) -> None:
"""Generate the CSR and request the certificate creation."""
"""Generate the CSR if the peer relation is ready."""
self._generate_privkey()

# check peer relation is there
if not self._peer_relation:
# tls-certificates relation event happened to fire before peer events.
# Abort, and let the "peer joined" relation create the CSR.
logger.info("certhandler waiting on peer relation")
return

logger.debug("certhandler has peer and certs relation: proceeding to generate csr")
self._generate_csr()

def _generate_privkey(self):
# Generate priv key unless done already
# TODO figure out how to go about key rotation.
if not self._private_key:
private_key = generate_private_key()
self._private_key = private_key.decode()

def _on_config_changed(self, _):
# FIXME on config changed, the web_external_url may or may not change. But because every
# call to `generate_csr` appends a uuid, CSRs cannot be easily compared to one another.
Expand Down Expand Up @@ -237,7 +244,12 @@ def _generate_csr(
# In case we already have a csr, do not overwrite it by default.
if overwrite or renew or not self._csr:
private_key = self._private_key
assert private_key is not None # for type checker
if private_key is None:
# FIXME: raise this in a less nested scope by
# generating privkey and csr in the same method.
raise RuntimeError(
"private key unset. call _generate_privkey() before you call this method."
)
csr = generate_csr(
private_key=private_key.encode(),
subject=self.cert_subject,
Expand Down
Loading

0 comments on commit 4ed63ce

Please sign in to comment.