Skip to content

Commit

Permalink
Refactor loading dashboards from dir
Browse files Browse the repository at this point in the history
  • Loading branch information
sed-i committed Jan 10, 2025
1 parent db74a85 commit 9953221
Showing 1 changed file with 86 additions and 114 deletions.
200 changes: 86 additions & 114 deletions lib/charms/grafana_k8s/v0/grafana_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,54 +392,60 @@ def __init__(self, *args):
}


# def load_from_dir(dashboards_path: Path):
# # Path.glob uses fnmatch on the backend, which is pretty limited, so use a
# # custom function for the filter
# def _is_dashboard(p: Path) -> bool:
# return p.is_file() and p.name.endswith((".json", ".json.tmpl", ".tmpl"))
#
# for path in filter(_is_dashboard, Path(dashboards_path).glob("*")):
# id = "file:{}".format(path.stem)
#
# # If we're running this class from within an aggregator (such as grafana agent), then the uid was
# # already rendered there, so we do not want to overwrite it with a uid generated from aggregator's info.
# # We overwrite the uid only if it's not a valid "Path40" uid.
# try:
# dashboard_dict = json.loads(path.read_bytes())
# except json.JSONDecodeError as e:
# logger.error("Failed to load dashboard '%s': %s", path, e)
# continue
# if type(dashboard_dict) is not dict:
# logger.error(
# "Invalid dashboard '%s': expected dict, got %s", path, type(dashboard_dict)
# )
#
# if not DashboardPath40UID.is_valid(original_uid := dashboard_dict.get("uid", "")):
# rel_path = str(
# path.relative_to(self._charm.charm_dir) if path.is_absolute() else path
# )
# dashboard_dict["uid"] = DashboardPath40UID.generate(
# self._charm.meta.name, rel_path
# )
# logger.debug(
# "Processed dashboard '%s': replaced original uid '%s' with '%s'",
# path,
# original_uid,
# dashboard_dict["uid"],
# )
# else:
# logger.debug(
# "Processed dashboard '%s': kept original uid '%s'", path, original_uid
# )
#
# stored_dashboard_templates[id] = CharmedDashboard._content_to_dashboard_object(
# charm_name=self._charm.meta.name,
# content=LZMABase64.compress(json.dumps(dashboard_dict)),
# inject_dropdowns=inject_dropdowns,
# juju_topology=self._juju_topology,
# )
#
# stored_dashboard_templates[id]["dashboard_alt_uid"] = self._generate_alt_uid(id)
def load_from_dir(
*,
dashboards_path: Path,
charm_dir: Path,
charm_name: str,
inject_dropdowns: bool,
juju_topology: dict,
) -> dict:
"""Load dashboards files from directory into a mapping from "dashboard id" to a so-called "dashboard object"."""

# Path.glob uses fnmatch on the backend, which is pretty limited, so use a
# custom function for the filter
def _is_dashboard(p: Path) -> bool:
return p.is_file() and p.name.endswith((".json", ".json.tmpl", ".tmpl"))

dashboard_templates = {}

for path in filter(_is_dashboard, Path(dashboards_path).glob("*")):
id = "file:{}".format(path.stem)

# If we're running this class from within an aggregator (such as grafana agent), then the uid was
# already rendered there, so we do not want to overwrite it with a uid generated from aggregator's info.
# We overwrite the uid only if it's not a valid "Path40" uid.
try:
dashboard_dict = json.loads(path.read_bytes())
except json.JSONDecodeError as e:
logger.error("Failed to load dashboard '%s': %s", path, e)
continue
if type(dashboard_dict) is not dict:
logger.error(
"Invalid dashboard '%s': expected dict, got %s", path, type(dashboard_dict)
)

if not DashboardPath40UID.is_valid(original_uid := dashboard_dict.get("uid", "")):
rel_path = str(path.relative_to(charm_dir) if path.is_absolute() else path)
dashboard_dict["uid"] = DashboardPath40UID.generate(charm_name, rel_path)
logger.debug(
"Processed dashboard '%s': replaced original uid '%s' with '%s'",
path,
original_uid,
dashboard_dict["uid"],
)
else:
logger.debug("Processed dashboard '%s': kept original uid '%s'", path, original_uid)

dashboard_templates[id] = CharmedDashboard._content_to_dashboard_object(
charm_name=charm_name,
content=LZMABase64.compress(json.dumps(dashboard_dict)),
dashboard_alt_uid=CharmedDashboard._generate_alt_uid(charm_name, id),
inject_dropdowns=inject_dropdowns,
juju_topology=juju_topology,
)

return dashboard_templates


class RelationNotFoundError(Exception):
Expand Down Expand Up @@ -965,23 +971,42 @@ def _modify_panel(cls, panel: dict, topology: dict, transformer: "CosTool") -> d

@classmethod
def _content_to_dashboard_object(
cls,
*,
charm_name,
content: str,
inject_dropdowns: bool = True,
juju_topology: Optional[dict] = None,
cls,
*,
charm_name,
content: str,
dashboard_alt_uid: Optional[str] = None,
inject_dropdowns: bool = True,
juju_topology: Optional[dict] = None,
) -> Dict:
if not juju_topology:
juju_topology = {}

return {
ret = {
"charm": charm_name,
"content": content,
"juju_topology": juju_topology if inject_dropdowns else {},
"inject_dropdowns": inject_dropdowns,
}

if dashboard_alt_uid is not None:
ret["dashboard_alt_uid"] = dashboard_alt_uid

return ret

@classmethod
def _generate_alt_uid(cls, charm_name: str, key: str) -> str:
"""Generate alternative uid for dashboards.
Args:
charm_name: The name of the charm (not app; from metadata).
key: A string used (along with charm.meta.name) to build the hash uid.
Returns: A hash string.
"""
raw_dashboard_alt_uid = "{}-{}".format(charm_name, key)
return hashlib.shake_256(raw_dashboard_alt_uid.encode("utf-8")).hexdigest(8)


def _type_convert_stored(obj):
"""Convert Stored* to their appropriate types, recursively."""
Expand Down Expand Up @@ -1170,12 +1195,11 @@ def add_dashboard(self, content: str, inject_dropdowns: bool = True) -> None:
stored_dashboard_templates[id] = CharmedDashboard._content_to_dashboard_object(
charm_name=self._charm.meta.name,
content=encoded_dashboard,
dashboard_alt_uid=CharmedDashboard._generate_alt_uid(self._charm.meta.name, id),
inject_dropdowns=inject_dropdowns,
juju_topology=self._juju_topology,
)

stored_dashboard_templates[id]["dashboard_alt_uid"] = self._generate_alt_uid(id)

if self._charm.unit.is_leader():
for dashboard_relation in self._charm.model.relations[self._relation_name]:
self._upset_dashboards_on_relation(dashboard_relation)
Expand Down Expand Up @@ -1217,72 +1241,20 @@ def _update_all_dashboards_from_dir(
if dashboard_id.startswith("file:"):
del stored_dashboard_templates[dashboard_id]

# Path.glob uses fnmatch on the backend, which is pretty limited, so use a
# custom function for the filter
def _is_dashboard(p: Path) -> bool:
return p.is_file() and p.name.endswith((".json", ".json.tmpl", ".tmpl"))

for path in filter(_is_dashboard, Path(self._dashboards_path).glob("*")):
# path = Path(path)
id = "file:{}".format(path.stem)

# If we're running this class from within an aggregator (such as grafana agent), then the uid was
# already rendered there, so we do not want to overwrite it with a uid generated from aggregator's info.
# We overwrite the uid only if it's not a valid "Path40" uid.
try:
dashboard_dict = json.loads(path.read_bytes())
except json.JSONDecodeError as e:
logger.error("Failed to load dashboard '%s': %s", path, e)
continue
if type(dashboard_dict) is not dict:
logger.error(
"Invalid dashboard '%s': expected dict, got %s", path, type(dashboard_dict)
)

if not DashboardPath40UID.is_valid(original_uid := dashboard_dict.get("uid", "")):
rel_path = str(
path.relative_to(self._charm.charm_dir) if path.is_absolute() else path
)
dashboard_dict["uid"] = DashboardPath40UID.generate(
self._charm.meta.name, rel_path
)
logger.debug(
"Processed dashboard '%s': replaced original uid '%s' with '%s'",
path,
original_uid,
dashboard_dict["uid"],
)
else:
logger.debug(
"Processed dashboard '%s': kept original uid '%s'", path, original_uid
)

stored_dashboard_templates[id] = CharmedDashboard._content_to_dashboard_object(
stored_dashboard_templates.update(
load_from_dir(
dashboards_path=Path(self._dashboards_path),
charm_dir=self._charm.charm_dir,
charm_name=self._charm.meta.name,
content=LZMABase64.compress(json.dumps(dashboard_dict)),
inject_dropdowns=inject_dropdowns,
juju_topology=self._juju_topology,
)

stored_dashboard_templates[id]["dashboard_alt_uid"] = self._generate_alt_uid(id)

self._stored.dashboard_templates = stored_dashboard_templates
)

if self._charm.unit.is_leader():
for dashboard_relation in self._charm.model.relations[self._relation_name]:
self._upset_dashboards_on_relation(dashboard_relation)

def _generate_alt_uid(self, key: str) -> str:
"""Generate alternative uid for dashboards.
Args:
key: A string used (along with charm.meta.name) to build the hash uid.
Returns: A hash string.
"""
raw_dashboard_alt_uid = "{}-{}".format(self._charm.meta.name, key)
return hashlib.shake_256(raw_dashboard_alt_uid.encode("utf-8")).hexdigest(8)

def _reinitialize_dashboard_data(self, inject_dropdowns: bool = True) -> None:
"""Triggers a reload of dashboard outside of an eventing workflow.
Expand Down

0 comments on commit 9953221

Please sign in to comment.