Skip to content

Commit

Permalink
chore: update charm libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
Github Actions committed Oct 20, 2023
1 parent 49abf15 commit e5c3ee8
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 87 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
3 changes: 2 additions & 1 deletion lib/charms/grafana_k8s/v0/grafana_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,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 = 34
LIBPATCH = 35

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -1195,6 +1195,7 @@ def _on_grafana_dashboard_relation_created(self, event: RelationCreatedEvent) ->
`grafana_dashboaard` relationship is joined
"""
if self._charm.unit.is_leader():
self._update_all_dashboards_from_dir()
self._upset_dashboards_on_relation(event.relation)

def _on_grafana_dashboard_relation_changed(self, event: RelationChangedEvent) -> None:
Expand Down
14 changes: 12 additions & 2 deletions lib/charms/loki_k8s/v0/loki_push_api.py
Original file line number Diff line number Diff line change
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 = 22

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -1773,6 +1773,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 +1794,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 @@ -2153,8 +2156,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
63 changes: 47 additions & 16 deletions lib/charms/observability_libs/v0/cert_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
This library requires a peer relation to be declared in the requirer's metadata. Peer relation data
is used for "persistent storage" of the private key and certs.
"""
import ipaddress
import json
import socket
from itertools import filterfalse
from typing import List, Optional, Union

try:
Expand Down Expand Up @@ -62,7 +64,16 @@

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


def is_ip_address(value: str) -> bool:
"""Return True if the input value is a valid IPv4 address; False otherwise."""
try:
ipaddress.IPv4Address(value)
return True
except ipaddress.AddressValueError:
return False


class CertChanged(EventBase):
Expand All @@ -88,7 +99,7 @@ def __init__(
peer_relation_name: str,
certificates_relation_name: str = "certificates",
cert_subject: Optional[str] = None,
extra_sans_dns: Optional[List[str]] = None,
extra_sans_dns: Optional[List[str]] = None, # TODO: in v1, rename arg to `sans`
):
"""CertHandler is used to wrap TLS Certificates management operations for charms.
Expand All @@ -111,7 +122,9 @@ def __init__(
self.cert_subject = charm.unit.name.replace("/", "-") if not cert_subject else cert_subject

# Use fqdn only if no SANs were given, and drop empty/duplicate SANs
self.sans_dns = list(set(filter(None, (extra_sans_dns or [socket.getfqdn()]))))
sans = list(set(filter(None, (extra_sans_dns or [socket.getfqdn()]))))
self.sans_ip = list(filter(is_ip_address, sans))
self.sans_dns = list(filterfalse(is_ip_address, sans))

self.peer_relation_name = peer_relation_name
self.certificates_relation_name = certificates_relation_name
Expand Down Expand Up @@ -168,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

# 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 the CSR if the certificates relation is ready."""
self._generate_privkey()

# 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 @@ -224,11 +244,17 @@ 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,
sans_dns=self.sans_dns,
sans_ip=self.sans_ip,
)

if renew and self._csr:
Expand All @@ -237,7 +263,12 @@ def _generate_csr(
new_certificate_signing_request=csr,
)
else:
logger.info("Creating CSR for %s with DNS %s", self.cert_subject, self.sans_dns)
logger.info(
"Creating CSR for %s with DNS %s and IPs %s",
self.cert_subject,
self.sans_dns,
self.sans_ip,
)
self.certificates.request_certificate_creation(certificate_signing_request=csr)

# Note: CSR is being replaced with a new one, so until we get the new cert, we'd have
Expand Down
20 changes: 6 additions & 14 deletions lib/charms/prometheus_k8s/v0/prometheus_scrape.py
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 = 41
LIBPATCH = 42

PYDEPS = ["cosl"]

Expand Down Expand Up @@ -604,12 +604,12 @@ def render_alertmanager_static_configs(alertmanagers: List[str]):
return {
"alertmanagers": [
{
# For https we still do not render a `tls_config` section because
# certs are expected to be made available by the charm via the
# `update-ca-certificates` mechanism.
"scheme": scheme,
"path_prefix": path_prefix,
"static_configs": [{"targets": netlocs}],
# FIXME figure out how to get alertmanager's ca_file into here
# Without this, prom errors: "x509: certificate signed by unknown authority"
"tls_config": {"insecure_skip_verify": True},
}
for (scheme, path_prefix), netlocs in paths.items()
]
Expand Down Expand Up @@ -1176,16 +1176,8 @@ def _static_scrape_config(self, relation) -> list:
scrape_configs, hosts, topology
)

# If scheme is https but no ca section present, then auto add "insecure_skip_verify",
# otherwise scraping errors out with "x509: certificate signed by unknown authority".
# https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config
for scrape_config in scrape_configs:
tls_config = scrape_config.get("tls_config", {})
ca_present = "ca" in tls_config or "ca_file" in tls_config
if scrape_config.get("scheme") == "https" and not ca_present:
tls_config["insecure_skip_verify"] = True
scrape_config["tls_config"] = tls_config

# For https scrape targets we still do not render a `tls_config` section because certs
# are expected to be made available by the charm via the `update-ca-certificates` mechanism.
return scrape_configs

def _relation_hosts(self, relation: Relation) -> Dict[str, Tuple[str, str]]:
Expand Down
4 changes: 2 additions & 2 deletions lib/charms/prometheus_k8s/v1/prometheus_remote_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

# 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 = ["cosl"]

Expand Down Expand Up @@ -838,7 +838,7 @@ def _inject_alert_expr_labels(self, rules: Dict[str, Any]) -> Dict[str, Any]:
# Inject topology and put it back in the list
rule["expr"] = self._tool.inject_label_matchers(
re.sub(r"%%juju_topology%%,?", "", rule["expr"]),
topology.label_matcher_dict,
topology.alert_expression_dict,
)
except KeyError:
# Some required JujuTopology key is missing. Just move on.
Expand Down
Loading

0 comments on commit e5c3ee8

Please sign in to comment.