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 Feb 29, 2024
1 parent 13a7202 commit ee701cf
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 65 deletions.
34 changes: 16 additions & 18 deletions lib/charms/alertmanager_k8s/v1/alertmanager_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self, *args):
from ops.charm import CharmBase, RelationEvent, RelationJoinedEvent, RelationRole
from ops.framework import EventBase, EventSource, Object, ObjectEvents
from ops.model import Relation
from pydantic import computed_field

# The unique Charmhub library identifier, never change it
LIBID = "37f1ca6f8fe84e3092ebbf6dc2885310"
Expand All @@ -42,9 +43,9 @@ 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"]
PYDEPS = ["pydantic>=2"]

# Set to match metadata.yaml
INTERFACE_NAME = "alertmanager_dispatch"
Expand All @@ -61,23 +62,20 @@ class _ProviderSchemaV0(pydantic.BaseModel):
class _ProviderSchemaV1(pydantic.BaseModel):
url: str

# The following are v0 fields that are continued to be populated for backwards compatibility.
# TODO: when we switch to pydantic 2+, use computed_field instead of the following fields, and
# also drop the __init__.
# https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.computed_field
public_address: Optional[str] # v0 relic
scheme: Optional[str] # v0 relic

def __init__(self, **kwargs):
super().__init__(**kwargs)

parsed = urlparse(kwargs["url"])
@computed_field
@property
def public_address(self) -> Optional[str]:
# v0 relic
parsed = urlparse(self.url)
port = ":" + str(parsed.port) if parsed.port else ""
public_address = f"{parsed.hostname}{port}{parsed.path}"
return f"{parsed.hostname}{port}{parsed.path}"

# Derive v0 fields from v1 field
self.public_address = public_address
self.scheme = parsed.scheme
@computed_field
@property
def scheme(self) -> Optional[str]:
# v0 relic
parsed = urlparse(self.url)
return parsed.scheme


class ClusterChanged(EventBase):
Expand Down Expand Up @@ -332,7 +330,7 @@ def _generate_relation_data(self, relation: Relation) -> Dict[str, str]:
# "alertmanagers.[].static_configs.targets" section in the prometheus config should list
# all units.
data = _ProviderSchemaV1(url=self._external_url)
return data.dict()
return data.model_dump()

def _update_relation_data(self, event: Optional[RelationEvent] = None):
"""Helper function for updating relation data bags.
Expand Down
32 changes: 28 additions & 4 deletions lib/charms/grafana_k8s/v0/grafana_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
`refresh_event`: A `PebbleReady` event from `charm`, used to refresh
the IP address sent to Grafana on a charm lifecycle event or
pod restart
`extra_fields`: None
`secure_extra_fields`: None
The value of `source_url` should be a fully-resolvable URL for a valid Grafana
source, e.g., `http://example.com/api` or similar.
Expand Down Expand Up @@ -160,7 +162,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 = 19
LIBPATCH = 21

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -324,6 +326,7 @@ def __init__(
refresh_event: Optional[Union[BoundEvent, List[BoundEvent]]] = None,
relation_name: str = DEFAULT_RELATION_NAME,
extra_fields: Optional[dict] = None,
secure_extra_fields: Optional[dict] = None,
) -> None:
"""Construct a Grafana charm client.
Expand Down Expand Up @@ -364,6 +367,8 @@ def __init__(
machine/VM restart.
extra_fields: a :dict: which is used for additional information required
for some datasources in the `jsonData` field
secure_extra_fields: a :dict: which is used for additional information required
for some datasources in the `secureJsonData`
"""
_validate_relation_by_interface_and_direction(
charm, relation_name, RELATION_INTERFACE_NAME, RelationRole.provides
Expand All @@ -382,6 +387,7 @@ def __init__(
extra_fields["implementation"] = "prometheus"

self._extra_fields = extra_fields
self._secure_extra_fields = secure_extra_fields

if not refresh_event:
if len(self._charm.meta.containers) == 1:
Expand Down Expand Up @@ -450,6 +456,7 @@ def _scrape_data(self) -> Dict:
"application": str(self._charm.model.app.name),
"type": self._source_type,
"extra_fields": self._extra_fields,
"secure_extra_fields": self._secure_extra_fields,
}
return data

Expand Down Expand Up @@ -574,6 +581,9 @@ def _get_source_config(self, rel: Relation):
if source_data.get("extra_fields", None):
host_data["extra_fields"] = source_data.get("extra_fields")

if source_data.get("secure_extra_fields", None):
host_data["secure_extra_fields"] = source_data.get("secure_extra_fields")

if host_data["source_name"] in sources_to_delete:
sources_to_delete.remove(host_data["source_name"])

Expand Down Expand Up @@ -648,7 +658,7 @@ def _remove_source_from_datastore(self, event: RelationDepartedEvent) -> bool:

def _remove_source(self, source_name: str) -> None:
"""Remove a datasource by name."""
sources_to_delete = self.get_peer_data("sources_to_delete")
sources_to_delete = self.get_peer_data("sources_to_delete") or []
if source_name not in sources_to_delete:
sources_to_delete.append(source_name)
self.set_peer_data("sources_to_delete", sources_to_delete)
Expand Down Expand Up @@ -713,7 +723,7 @@ def sources(self) -> List[dict]:
@property
def sources_to_delete(self) -> List[str]:
"""Returns an array of source names which have been removed."""
return self.get_peer_data("sources_to_delete")
return self.get_peer_data("sources_to_delete") or []

def _set_default_data(self) -> None:
"""Set defaults if they are not in peer relation data."""
Expand All @@ -724,9 +734,23 @@ def _set_default_data(self) -> None:

def set_peer_data(self, key: str, data: Any) -> None:
"""Put information into the peer data bucket instead of `StoredState`."""
self._charm.peers.data[self._charm.app][key] = json.dumps(data) # type: ignore[attr-defined]
peers = self._charm.peers # type: ignore[attr-defined]
if not peers:
# https://bugs.launchpad.net/juju/+bug/1998282
logger.info("set_peer_data: no peer relation. Is the charm being installed/removed?")
return

peers.data[self._charm.app][key] = json.dumps(data) # type: ignore[attr-defined]

def get_peer_data(self, key: str) -> Any:
"""Retrieve information from the peer data bucket instead of `StoredState`."""
peers = self._charm.peers # type: ignore[attr-defined]
if not peers:
# https://bugs.launchpad.net/juju/+bug/1998282
logger.warning(
"get_peer_data: no peer relation. Is the charm being installed/removed?"
)
return {}

data = self._charm.peers.data[self._charm.app].get(key, "") # type: ignore[attr-defined]
return json.loads(data) if data else {}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def setUp(self, *unused):

# 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


_Decimal = Union[Decimal, float, str, int] # types that are potentially convertible to Decimal
Expand Down Expand Up @@ -364,7 +364,7 @@ def is_patched(self, resource_reqs: ResourceRequirements) -> bool:
Returns:
bool: A boolean indicating if the service patch has been applied.
"""
return equals_canonically(self.get_templated(), resource_reqs)
return equals_canonically(self.get_templated(), resource_reqs) # pyright: ignore

def get_templated(self) -> Optional[ResourceRequirements]:
"""Returns the resource limits specified in the StatefulSet template."""
Expand Down Expand Up @@ -397,8 +397,8 @@ def is_ready(self, pod_name, resource_reqs: ResourceRequirements):
self.get_templated(),
self.get_actual(pod_name),
)
return self.is_patched(resource_reqs) and equals_canonically(
resource_reqs, self.get_actual(pod_name)
return self.is_patched(resource_reqs) and equals_canonically( # pyright: ignore
resource_reqs, self.get_actual(pod_name) # pyright: ignore
)

def apply(self, resource_reqs: ResourceRequirements) -> None:
Expand Down
16 changes: 5 additions & 11 deletions lib/charms/tempo_k8s/v1/charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ 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 = 1
LIBPATCH = 2

PYDEPS = ["opentelemetry-exporter-otlp-proto-http>=1.21.0"]

Expand Down Expand Up @@ -200,15 +200,12 @@ def _get_tracer() -> Optional[Tracer]:
return tracer.get()
except LookupError:
try:
logger.debug("tracer was not found in context variable, looking up in default context")
ctx: Context = copy_context()
if context_tracer := _get_tracer_from_context(ctx):
return context_tracer.get()
else:
logger.debug("Couldn't find context var for tracer: span will be skipped")
return None
except LookupError as err:
logger.debug(f"Couldn't find tracer: span will be skipped, err: {err}")
return None


Expand All @@ -219,7 +216,6 @@ def _span(name: str) -> Generator[Optional[Span], Any, Any]:
with tracer.start_as_current_span(name) as span:
yield cast(Span, span)
else:
logger.debug("tracer not found")
yield None


Expand All @@ -243,9 +239,9 @@ def _get_tracing_endpoint(tracing_endpoint_getter, self, charm):
tracing_endpoint = tracing_endpoint_getter(self)

if tracing_endpoint is None:
logger.warning(
f"{charm}.{getattr(tracing_endpoint_getter, '__qualname__', str(tracing_endpoint_getter))} "
f"returned None; continuing with tracing DISABLED."
logger.debug(
"Charm tracing is disabled. Tracing endpoint is not defined - "
"tracing is not available or relation is not set."
)
return
elif not isinstance(tracing_endpoint, str):
Expand All @@ -266,15 +262,14 @@ def _get_server_cert(server_cert_getter, self, charm):

if server_cert is None:
logger.warning(
f"{charm}.{server_cert_getter} returned None; continuing with INSECURE connection."
f"{charm}.{server_cert_getter} returned None; sending traces over INSECURE connection."

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (certificate)
as clear text.
)
return
elif not Path(server_cert).is_absolute():
raise ValueError(
f"{charm}.{server_cert_getter} should return a valid tls cert absolute path (string | Path)); "
f"got {server_cert} instead."
)
logger.debug("Certificate successfully retrieved.") # todo: some more validation?
return server_cert


Expand All @@ -300,7 +295,6 @@ def wrap_init(self: CharmBase, framework: Framework, *args, **kwargs):

original_event_context = framework._event_context

logging.debug("Initializing opentelemetry tracer...")
_service_name = service_name or self.app.name

resource = Resource.create(
Expand Down
16 changes: 10 additions & 6 deletions lib/charms/tempo_k8s/v1/tracing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2022 Pietro Pasotti
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""## Overview.
Expand Down Expand Up @@ -93,7 +93,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 = 2
LIBPATCH = 3

PYDEPS = ["pydantic>=2"]

Expand Down Expand Up @@ -151,8 +151,12 @@ def load(cls, databag: MutableMapping):
try:
return cls.parse_raw(json.dumps(data)) # type: ignore
except pydantic.ValidationError as e:
msg = f"failed to validate databag: {databag}"
logger.error(msg, exc_info=True)
if not data:
# databag is empty; this is usually expected
raise DataValidationError("empty databag")

msg = f"failed to validate databag contents: {data!r} as {cls}"
logger.debug(msg, exc_info=True)
raise DataValidationError(msg) from e

def dump(self, databag: Optional[MutableMapping] = None, clear: bool = True):
Expand Down Expand Up @@ -194,8 +198,8 @@ class TracingProviderAppData(DatabagModel): # noqa: D101


class _AutoSnapshotEvent(RelationEvent):
__args__ = () # type: Tuple[str, ...]
__optional_kwargs__ = {} # type: Dict[str, Any]
__args__: Tuple[str, ...] = ()
__optional_kwargs__: Dict[str, Any] = {}

@classmethod
def __attrs__(cls):
Expand Down
Loading

0 comments on commit ee701cf

Please sign in to comment.