Source code for grafanarmadillo.bulk

"""
Perform bulk operations on Grafana.

BulkOperations defines the basic interface. It is meant to be parametrised in 2 phases:
- information source: Subclasses of BulkOperations implement methods to get orgs and resources
- actions: Subclasses of those classes implement the actions to take

For example:
	BulkGrafanaOperation uses a Grafana instance as its source
	BulkExporter uses BulkGrafanaOperations to list all objects and write them to disk
"""
import logging
from abc import ABC, abstractmethod
from functools import lru_cache
from pathlib import Path
from typing import Generator, List, Tuple

from grafana_client import GrafanaApi

from grafanarmadillo.alerter import Alerter
from grafanarmadillo.dashboarder import Dashboarder
from grafanarmadillo.find import Finder
from grafanarmadillo.templator import Templator
from grafanarmadillo.types import AlertContent, DashboardContent, OrgMeta
from grafanarmadillo.util import exactly_one, read_from_file, write_to_file


l = logging.getLogger(__name__)


[docs]@lru_cache def get_all_orgs(gfn_multiorg) -> List[OrgMeta]: """Get a list of all orgs.""" return gfn_multiorg.organizations.list_organization()
[docs]@lru_cache def get_org(gfn_multiorg, org_name: str): """Get an org by name, efficiently.""" return exactly_one( list(filter(lambda o: o["name"] == org_name, get_all_orgs(gfn_multiorg))), f"org with name {org_name}" )
[docs]class BulkOperation(ABC): """Run bulk operations on Grafana.""" def __init__(self, cfg: dict): self.cfg = cfg self.gfn_multiorg = GrafanaApi(**self.cfg)
[docs] def run(self): """Run this bulk operation.""" for org, gfn in self.all_orgs(): for out_path, dashboard_content in self.get_all_dashboards(org, gfn): self.each_dashboard(out_path, dashboard_content) for out_path, alert_content in self.get_all_alerts(org, gfn): self.each_alert(out_path, alert_content)
[docs] @abstractmethod def all_orgs(self) -> Generator[Tuple[OrgMeta, GrafanaApi], None, None]: """ Iterate over all organisations. This method should be implemented for each source of information. """
[docs] @abstractmethod def get_all_dashboards(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, DashboardContent], None, None]: """ Iterate over all dashboards. This method should be implemented for each source of information. """
[docs] @abstractmethod def get_all_alerts(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, AlertContent], None, None]: """ Iterate over all alerts. This method should be implemented for each source of information. """
[docs] @abstractmethod def each_dashboard(self, path: Path, dashboard: DashboardContent): """ Act on each dashboard in Grafana. This method should be implemented for each operation. """
[docs] @abstractmethod def each_alert(self, path: Path, alert: AlertContent): """ Act on each alert in Grafana. This method should be implemented for each operation. """
[docs]class BulkGrafanaOperation(BulkOperation, ABC): """Bulk operations which uses a Grafana instance as its source."""
[docs] def all_orgs(self) -> Generator[Tuple[OrgMeta, GrafanaApi], None, None]: """Iterate over all organisations in Grafana.""" orgs = self.gfn_multiorg.organizations.list_organization() for org in orgs: gfn = GrafanaApi(**{**self.cfg, "organization_id": org["id"]}) yield org, gfn
[docs] def get_all_dashboards(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, DashboardContent], None, None]: """Get all dashboards.""" finder, dashboarder = Finder(gfn), Dashboarder(gfn) dashboards = finder.list_dashboards() for dashboard in dashboards: dashboard_content, folder = dashboarder.export_dashboard(dashboard) if folder is None: folder_name = "General" else: folder_name = folder["name"] out_path = Path(org["name"], folder_name, dashboard_content["title"]) yield out_path, dashboard_content
[docs] def get_all_alerts(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, AlertContent], None, None]: """Get all alerts.""" finder, alerter = Finder(gfn), Alerter(gfn) alerts = finder.list_alerts() for alert in alerts: alert_content, folder = alerter.export_alert(alert) folder_name = folder["title"] out_path = Path(org["name"], folder_name, alert_content["title"]) yield out_path, alert_content
[docs]class BulkFileOperation(BulkOperation, ABC): """Bulk operation which uses a filetree as its source.""" def __init__(self, cfg: dict, root_directory: Path): self.root_directory = root_directory super().__init__(cfg)
[docs] def all_orgs(self) -> Generator[Tuple[OrgMeta, GrafanaApi], None, None]: """Iterate over all organisations in Grafana.""" orgs = {o.name for o in self.root_directory.glob("*/*")} for org_name in orgs: org = self.gfn_multiorg.organization.find_organization(org_name) gfn = GrafanaApi(**{**self.cfg, "organization_id": org["id"]}) yield org, gfn
[docs] def get_all_dashboards(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, DashboardContent], None, None]: """Get all dashboards.""" folders = (self.root_directory / "dashboards" / org["name"]).glob("*") for folder_path in folders: for dashboard_path in folder_path.glob("*.json"): content = read_from_file(dashboard_path) yield Path(org["name"], folder_path.name, dashboard_path.name), content
[docs] def get_all_alerts(self, org: OrgMeta, gfn: GrafanaApi) -> Generator[Tuple[Path, AlertContent], None, None]: """Get all alerts.""" folders = (self.root_directory / "alerts" / org["name"]).glob("*") for folder_path in folders: for alert_path in folder_path.glob("*.json"): content = read_from_file(alert_path) yield Path(org["name"], folder_path.name, alert_path.name), content
[docs]class BulkExporter(BulkGrafanaOperation): """Export all resources from Grafana to files.""" def __init__(self, cfg: dict, root_directory: Path, templator: Templator): self.root_directory = root_directory self.templator = templator super().__init__(cfg)
[docs] def each_dashboard(self, path: Path, dashboard: DashboardContent): """Write each dashboard to files.""" dashboard_templated = self.templator.make_template_from_dashboard(dashboard) l.info(f"export dashboard path={path}") write_to_file((self.root_directory / "dashboards" / path).with_suffix(".json"), dashboard_templated)
[docs] def each_alert(self, path: Path, alert: AlertContent): """Write each alert to files.""" alert_templated = self.templator.make_template_from_dashboard(alert) l.info(f"export alert path={path}") write_to_file((self.root_directory / "alerts" / path).with_suffix(".json"), alert_templated)
[docs]class BulkImporter(BulkFileOperation): """Import all resources from files into Grafana.""" def __init__(self, cfg: dict, root_directory: Path, templator: Templator): self.templator = templator super().__init__(cfg, root_directory)
[docs] def each_dashboard(self, path: Path, dashboard: DashboardContent): """Import each dashboard into Grafana.""" org_name, folder_name, dashboard_name = path.parts org = get_org(self.gfn_multiorg, org_name) gfn = GrafanaApi(**{**self.cfg, "organization_id": org["id"]}) finder, dashboarder = Finder(gfn), Dashboarder(gfn) folder = finder.create_or_get_folder(folder_name) dashboard_templated = self.templator.make_dashboard_from_template( dashboard, dashboard ) l.info(f"import dashboard path={path}") dashboarder.import_dashboard(dashboard_templated, folder)
[docs] def each_alert(self, path: Path, alert: AlertContent): """Import each alert into Grafana.""" org_name, folder_name, dashboard_name = path.parts org = get_org(self.gfn_multiorg, org_name) gfn = GrafanaApi(**{**self.cfg, "organization_id": org["id"]}) finder, alerter = Finder(gfn), Alerter(gfn) folder = finder.create_or_get_folder(folder_name) alert_templated = self.templator.make_dashboard_from_template(alert, alert) l.info(f"import alert path={path}") alerter.import_alert(alert_templated, folder)