Skip to content

Commit

Permalink
[MISC] Update charm libs (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
deusebio authored Aug 7, 2023
1 parent 0649ab0 commit ab04b66
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 142 deletions.
118 changes: 54 additions & 64 deletions 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 = 28
LIBPATCH = 32

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -525,7 +525,7 @@ def _validate_relation_by_interface_and_direction(
relation = charm.meta.relations[relation_name]

actual_relation_interface = relation.interface_name
if actual_relation_interface != expected_relation_interface:
if actual_relation_interface and actual_relation_interface != expected_relation_interface:
raise RelationInterfaceMismatchError(
relation_name, expected_relation_interface, actual_relation_interface
)
Expand Down Expand Up @@ -582,7 +582,7 @@ def _convert_dashboard_fields(content: str, inject_dropdowns: bool = True) -> st

# If no existing template variables exist, just insert our own
if "templating" not in dict_content:
dict_content["templating"] = {"list": [d for d in template_dropdowns]} # type: ignore
dict_content["templating"] = {"list": list(template_dropdowns)} # type: ignore
else:
# Otherwise, set a flag so we can go back later
existing_templates = True
Expand Down Expand Up @@ -830,18 +830,18 @@ def _modify_panel(panel: dict, topology: dict, transformer: "CosTool") -> dict:

if "datasource" not in panel.keys():
continue
else:
if type(panel["datasource"]) == str:
if panel["datasource"] not in known_datasources:
continue
querytype = known_datasources[panel["datasource"]]
elif type(panel["datasource"]) == dict:
if panel["datasource"]["uid"] not in known_datasources:
continue
querytype = known_datasources[panel["datasource"]["uid"]]
else:
logger.error("Unknown datasource format: skipping")

if type(panel["datasource"]) == str:
if panel["datasource"] not in known_datasources:
continue
querytype = known_datasources[panel["datasource"]]
elif type(panel["datasource"]) == dict:
if panel["datasource"]["uid"] not in known_datasources:
continue
querytype = known_datasources[panel["datasource"]["uid"]]
else:
logger.error("Unknown datasource format: skipping")
continue

# Capture all values inside `[]` into a list which we'll iterate over later to
# put them back in-order. Then apply the regex again and replace everything with
Expand Down Expand Up @@ -901,13 +901,12 @@ def _type_convert_stored(obj):
"""Convert Stored* to their appropriate types, recursively."""
if isinstance(obj, StoredList):
return list(map(_type_convert_stored, obj))
elif isinstance(obj, StoredDict):
if isinstance(obj, StoredDict):
rdict = {} # type: Dict[Any, Any]
for k in obj.keys():
rdict[k] = _type_convert_stored(obj[k])
return rdict
else:
return obj
return obj


class GrafanaDashboardsChanged(EventBase):
Expand Down Expand Up @@ -956,7 +955,7 @@ def restore(self, snapshot):
"""Restore grafana source information."""
self.error_message = snapshot["error_message"]
self.valid = snapshot["valid"]
self.errors = json.loads(snapshot["errors"])
self.errors = json.loads(str(snapshot["errors"]))


class GrafanaProviderEvents(ObjectEvents):
Expand All @@ -969,7 +968,7 @@ class GrafanaDashboardProvider(Object):
"""An API to provide Grafana dashboards to a Grafana charm."""

_stored = StoredState()
on = GrafanaProviderEvents()
on = GrafanaProviderEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1073,7 +1072,7 @@ def add_dashboard(self, content: str, inject_dropdowns: bool = True) -> None:
"""
# Update of storage must be done irrespective of leadership, so
# that the stored state is there when this unit becomes leader.
stored_dashboard_templates = self._stored.dashboard_templates # type: Any
stored_dashboard_templates: Any = self._stored.dashboard_templates # pyright: ignore

encoded_dashboard = _encode_dashboard_content(content)

Expand All @@ -1094,7 +1093,7 @@ def remove_non_builtin_dashboards(self) -> None:
"""Remove all dashboards to the relation added via :method:`add_dashboard`."""
# Update of storage must be done irrespective of leadership, so
# that the stored state is there when this unit becomes leader.
stored_dashboard_templates = self._stored.dashboard_templates # type: Any
stored_dashboard_templates: Any = self._stored.dashboard_templates # pyright: ignore

for dashboard_id in list(stored_dashboard_templates.keys()):
if dashboard_id.startswith("prog:"):
Expand All @@ -1121,7 +1120,7 @@ def _update_all_dashboards_from_dir(
# Ensure we do not leave outdated dashboards by removing from stored all
# the encoded dashboards that start with "file/".
if self._dashboards_path:
stored_dashboard_templates = self._stored.dashboard_templates # type: Any
stored_dashboard_templates: Any = self._stored.dashboard_templates # pyright: ignore

for dashboard_id in list(stored_dashboard_templates.keys()):
if dashboard_id.startswith("file:"):
Expand Down Expand Up @@ -1175,7 +1174,7 @@ def _reinitialize_dashboard_data(self, inject_dropdowns: bool = True) -> None:
e.grafana_dashboards_absolute_path,
e.message,
)
stored_dashboard_templates = self._stored.dashboard_templates # type: Any
stored_dashboard_templates: Any = self._stored.dashboard_templates # pyright: ignore

for dashboard_id in list(stored_dashboard_templates.keys()):
if dashboard_id.startswith("file:"):
Expand Down Expand Up @@ -1213,16 +1212,18 @@ def _on_grafana_dashboard_relation_changed(self, event: RelationChangedEvent) ->
valid = bool(data.get("valid", True))
errors = data.get("errors", [])
if valid and not errors:
self.on.dashboard_status_changed.emit(valid=valid)
self.on.dashboard_status_changed.emit(valid=valid) # pyright: ignore
else:
self.on.dashboard_status_changed.emit(valid=valid, errors=errors)
self.on.dashboard_status_changed.emit( # pyright: ignore
valid=valid, errors=errors
)

def _upset_dashboards_on_relation(self, relation: Relation) -> None:
"""Update the dashboards in the relation data bucket."""
# It's completely ridiculous to add a UUID, but if we don't have some
# pseudo-random value, this never makes it across 'juju set-state'
stored_data = {
"templates": _type_convert_stored(self._stored.dashboard_templates),
"templates": _type_convert_stored(self._stored.dashboard_templates), # pyright: ignore
"uuid": str(uuid.uuid4()),
}

Expand Down Expand Up @@ -1251,13 +1252,13 @@ def _juju_topology(self) -> Dict:
@property
def dashboard_templates(self) -> List:
"""Return a list of the known dashboard templates."""
return [v for v in self._stored.dashboard_templates.values()] # type: ignore
return list(self._stored.dashboard_templates.values()) # type: ignore


class GrafanaDashboardConsumer(Object):
"""A consumer object for working with Grafana Dashboards."""

on = GrafanaDashboardEvents()
on = GrafanaDashboardEvents() # pyright: ignore
_stored = StoredState()

def __init__(
Expand Down Expand Up @@ -1305,7 +1306,7 @@ def __init__(
self._relation_name = relation_name
self._tranformer = CosTool(self._charm)

self._stored.set_default(dashboards=dict()) # type: ignore
self._stored.set_default(dashboards={}) # type: ignore

self.framework.observe(
self._charm.on[self._relation_name].relation_changed,
Expand Down Expand Up @@ -1349,13 +1350,13 @@ def _on_grafana_dashboard_relation_changed(self, event: RelationChangedEvent) ->
changes = self._render_dashboards_and_signal_changed(event.relation)

if changes:
self.on.dashboards_changed.emit()
self.on.dashboards_changed.emit() # pyright: ignore

def _on_grafana_peer_changed(self, _: RelationChangedEvent) -> None:
"""Emit dashboard events on peer events so secondary charm data updates."""
if self._charm.unit.is_leader():
return
self.on.dashboards_changed.emit()
self.on.dashboards_changed.emit() # pyright: ignore

def update_dashboards(self, relation: Optional[Relation] = None) -> None:
"""Re-establish dashboards on one or more relations.
Expand Down Expand Up @@ -1402,7 +1403,7 @@ def _render_dashboards_and_signal_changed(self, relation: Relation) -> bool: #
"""
other_app = relation.app

raw_data = relation.data[other_app].get("dashboards", {}) # type: ignore
raw_data = relation.data[other_app].get("dashboards", "") # pyright: ignore

if not raw_data:
logger.warning(
Expand All @@ -1417,11 +1418,6 @@ def _render_dashboards_and_signal_changed(self, relation: Relation) -> bool: #
# The only piece of data needed on this side of the relations is "templates"
templates = data.pop("templates")

# Import only if a charmed operator uses the consumer, we don't impose these
# dependencies on the client
from jinja2 import Template
from jinja2.exceptions import TemplateSyntaxError

# The dashboards are WAY too big since this ultimately calls out to Juju to
# set the relation data, and it overflows the maximum argument length for
# subprocess, so we have to use b64, annoyingly.
Expand All @@ -1434,14 +1430,12 @@ def _render_dashboards_and_signal_changed(self, relation: Relation) -> bool: #
relation_has_invalid_dashboards = False

for _, (fname, template) in enumerate(templates.items()):
decoded_content = None
content = None
error = None
topology = template.get("juju_topology", {})
try:
decoded_content = _decode_dashboard_content(template["content"])
content = _decode_dashboard_content(template["content"])
inject_dropdowns = template.get("inject_dropdowns", True)
content = Template(decoded_content).render()
content = self._manage_dashboard_uid(content, template)
content = _convert_dashboard_fields(content, inject_dropdowns)

Expand All @@ -1456,9 +1450,6 @@ def _render_dashboards_and_signal_changed(self, relation: Relation) -> bool: #
error = str(e.msg)
logger.warning("Invalid JSON in Grafana dashboard: {}".format(fname))
continue
except TemplateSyntaxError as e:
error = str(e)
relation_has_invalid_dashboards = True

# Prepend the relation name and ID to the dashboard ID to avoid clashes with
# multiple relations with apps from the same charm, or having dashboards with
Expand Down Expand Up @@ -1505,36 +1496,35 @@ def _render_dashboards_and_signal_changed(self, relation: Relation) -> bool: #

# Dropping dashboards for a relation needs to be signalled
return True
else:
stored_data = rendered_dashboards
currently_stored_data = self._get_stored_dashboards(relation.id)

coerced_data = (
_type_convert_stored(currently_stored_data) if currently_stored_data else {}
)
stored_data = rendered_dashboards
currently_stored_data = self._get_stored_dashboards(relation.id)

coerced_data = _type_convert_stored(currently_stored_data) if currently_stored_data else {}

if not coerced_data == stored_data:
stored_dashboards = self.get_peer_data("dashboards")
stored_dashboards[relation.id] = stored_data
self.set_peer_data("dashboards", stored_dashboards)
return True
if not coerced_data == stored_data:
stored_dashboards = self.get_peer_data("dashboards")
stored_dashboards[relation.id] = stored_data
self.set_peer_data("dashboards", stored_dashboards)
return True
return None # type: ignore

def _manage_dashboard_uid(self, dashboard: str, template: dict) -> str:
"""Add an uid to the dashboard if it is not present."""
dashboard = json.loads(dashboard)
dashboard_dict = json.loads(dashboard)

if not dashboard.get("uid", None) and "dashboard_alt_uid" in template:
dashboard["uid"] = template["dashboard_alt_uid"]
if not dashboard_dict.get("uid", None) and "dashboard_alt_uid" in template:
dashboard_dict["uid"] = template["dashboard_alt_uid"]

return json.dumps(dashboard)
return json.dumps(dashboard_dict)

def _remove_all_dashboards_for_relation(self, relation: Relation) -> None:
"""If an errored dashboard is in stored data, remove it and trigger a deletion."""
if self._get_stored_dashboards(relation.id):
stored_dashboards = self.get_peer_data("dashboards")
stored_dashboards.pop(str(relation.id))
self.set_peer_data("dashboards", stored_dashboards)
self.on.dashboards_changed.emit()
self.on.dashboards_changed.emit() # pyright: ignore

def _to_external_object(self, relation_id, dashboard):
return {
Expand Down Expand Up @@ -1616,7 +1606,7 @@ class GrafanaDashboardAggregator(Object):
"""

_stored = StoredState()
on = GrafanaProviderEvents()
on = GrafanaProviderEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1681,7 +1671,7 @@ def _update_remote_grafana(self, _: Optional[RelationEvent] = None) -> None:
"""Push dashboards to the downstream Grafana relation."""
# It's still ridiculous to add a UUID here, but needed
stored_data = {
"templates": _type_convert_stored(self._stored.dashboard_templates),
"templates": _type_convert_stored(self._stored.dashboard_templates), # pyright: ignore
"uuid": str(uuid.uuid4()),
}

Expand All @@ -1702,7 +1692,7 @@ def remove_dashboards(self, event: RelationBrokenEvent) -> None:
del self._stored.dashboard_templates[id] # type: ignore

stored_data = {
"templates": _type_convert_stored(self._stored.dashboard_templates),
"templates": _type_convert_stored(self._stored.dashboard_templates), # pyright: ignore
"uuid": str(uuid.uuid4()),
}

Expand Down Expand Up @@ -1830,10 +1820,10 @@ def _handle_reactive_dashboards(self, event: RelationEvent) -> Optional[Dict]:
# Replace old piechart panels
dash = re.sub(r'"type": "grafana-piechart-panel"', '"type": "piechart"', dash)

from jinja2 import Template
from jinja2 import DebugUndefined, Template

content = _encode_dashboard_content(
Template(dash).render(host=r"$host", datasource=r"${prometheusds}") # type: ignore
Template(dash, undefined=DebugUndefined).render(datasource=r"${prometheusds}") # type: ignore
)
id = "prog:{}".format(content[-24:-16])

Expand Down
Loading

0 comments on commit ab04b66

Please sign in to comment.