Package: gdbt
GDBT (Grafana Dashboard Templater)GDBTis an infrastructure-as-code tool for Grafana dashboards. This is similar to Terraform, but specializes on templating dashboards based on various evaluations.Table of ContentsGDBT (Grafana Dashboard Templater)Table of ContentsInstallationReferenceConceptsGlobal configurationResource definitionsCaveatsCLICommandsDevelopmentHow does it work?Source code structureStandardsPrerequisitesUsing PoetryDocumentationBuildingReleasesRelease processCI/CDInstallationYou can install GDBT using Pip(Python 3.9+ required!):pipinstallgdbtRefer toCLI referencefor further info.ReferenceConceptsResource. A single object in Grafana, either folder or a dashboard.Resource definition. GDBT configuration file. Describes resource kind and its model as well as relevant lookups, evaluations and loops.Scope. A subdirectory of the whole GDBT configuration that includes a subset of resource definitions. You can run GDBT against any available scope, keeping other resources unaffected. This can be done by either running GDBT from a subdirectory or using-s/--scopeoption.Lookup. Static key-value configuration, useful for mapping or easy template variable modification. Available aslookupsdictionary in the template.Evaluation. Dynamic variant of lookup. Can be used to retrieve values at runtime, for example — fetch a list of metric label values from Prometheus.Evaluation lock. Evaluations are cached in lock files to make sure that identical codealwaysgenerates identical resource set. The lock files are stored beside resource definitions with the same filename and.lockextension. To update the lock file, run your command with-uor--updateflag. Locks are updated automatically when relevant evaluation definition is changed.Loop. Allows you to iterate over an array, making a separate resource for each array item. The iterable can be provided from an evaluation or a lookup. Current item is available in the template asloop.item.Global configurationUsed for global preferences, like defining providers or state storage configuration. It is written in TOML and always resides in the root directory of your configuration asconfig.toml.Example configuration:[providers][providers.example-grafana]kind="grafana"endpoint="https://grafana.example.com"token="$GRAFANA_TOKEN"[providers.example-prometheus]kind="prometheus"endpoint="http://prometheus.example.com:8248"http_proxy="http://proxy.example.com:8080"https_proxy="http://proxy.example.com:8443"[providers.state-s3]kind="s3"bucket="example-gdbt-state"[state]provider="state-s3"[concurrency]threads=16providers: provider definitions:kind: provider kind, one ofgrafana,prometheus(for evaluations),s3,consul,file(for state storage)other provider-specific parametersstate: state storage preferencesprovider: name of provider used for state storage (at the moment only S3 is supported)concurrency: parallelism preferencesthreads: how many threads to run for HTTP requests to APIs (Note: you may experience heavy API rate limiting if you set this value too high, so try to find a sweet spot considering your resource limitations)You can use environment variables inside this configuration as"$VARIABLE_NAME". Note: these should be enclosed in quotes.Resource definitionsThere are 2 kinds of resources:dashboardandfolder, each of these corresponding to relevant Grafana entities.Path to the resource definition relative to configuration root (without.yamlextension) is designated as the resource identifier. When resource definition produces multiple resources (e.g. when usingloop), the loop item value is appended to resource identifier with a colon symbol (example/resource:foo,example/resource:bar).Exampledashboardresource with multiple notification channels per service:kind:dashboardprovider:example-grafanafolder:example/foo/folderevaluations:example-services:kind:prometheussource:example-prometheusmetric:"sum(cpu_user_usage)by(service)[30m]"label:serviceloop:evaluations.example-serviceslookups:service_notification_channel:service1:-victorops-team1-slack-team1service2:-victorops-team1-mail-allservice3:-slack-team2DEFAULT:-mail-allmodel:|{"panels": [{"alert": {"name": "CPU usage high in {{ loop.item }} service","notifications": [{%- for notification_channel in lookups.service_notification_channel[loop.item] | default(lookups.service_notification_channel.DEFAULT) %}{"uid": "{$ notification_channel -$}"}{% if not loop.last %},{% endif %}{% endfor %}]},"targets": [{"expr": "avg by (service)(cpu_user_usage{service='{{ loop.item }}'})",}]}],"tags": ["example", "cpu", "{{ loop.item }}"],"title": "System CPU usage ({{ loop.item }})"}Exampledashboardresource with one notification channel per service:kind:dashboardprovider:example-grafanafolder:example/foo/folderevaluations:example-services:kind:prometheussource:example-prometheusmetric:"sum(cpu_user_usage)by(service)[30m]"label:serviceloop:evaluations.example-serviceslookups:service_notification_channel:service1:victorops-team1service2:victorops-team1service3:slack-team2DEFAULT:mail-allmodel:|{"panels": [{"alert": {"name": "CPU usage high in {{ loop.item }} service","notifications": [{"uid": "{$ lookups.service_notification_channel[loop.item] | default(lookups.service_notification_channel.DEFAULT) $}"}]},"targets": [{"expr": "avg by (service)(cpu_user_usage{service='{{ loop.item }}'})",}]}],"tags": ["example", "cpu", "{{ loop.item }}"],"title": "System CPU usage ({{ loop.item }})"}*Note:modelin the above example was stripped and only relevant fields were left. Please refer to Grafana documentation for valid resource model JSON format.Examplefolderresource:kind:folderprovider:example-grafanamodel:|{"title": "Example Folder"}kind: either offolder,dashboardprovider: name of Grafana provider this resource will be applied tofolder(only fordashboardkind): resource identifier of folder the dashboard will be created inevaluations(optional): dynamic lookups:kind: evaluation kind, onlyprometheusis supported at the momentsource: provider name to run the evaluation againstmetric: metric to evaluatelabel: name of label to extract from received metric setlookups(optional): static lookups, a simple key-valueloop(optional): loop against specified variable, will generate a resource for each item in provided variablemodel: Jinja2 template of Grafana resource model JSONSeeJinja2 documentationfor more info about templates.CaveatsGrafana forbids creating resources with identical titles. Because of this, be extra careful when usingloop, ensure that you include{{ loop.item }}as a part oftitle— otherwise you will get unexpected cryptic errors from Grafana.CLIgdbt [command] [parameters] [options]Use--helpfor information on specific command. The synopsis for each command shows its parameters and their usage.Commandsvalidate: Validate syntax:-s/--scope: Scope (default: current working directory).plan: Generates an execution plan for GDBT:-u/--update: Update evaluation locks.apply: Build or change Grafana resources according to the configuration in the current scope:-s/--scope: Scope (default: current working directory);-u/--update: Update evaluation locks;-y/--auto-approve: Do not ask for confirmation.destroy: Remove all defined resources within the current scope:-s/--scope: Scope (default: current working directory);-u/--update: Update evaluation locks;-y/--auto-approve: Do not ask for confirmation.version: Print GDBT version.DevelopmentBefore starting, please readContributing guidelines.Note:This repo usesmainas a default branch. Make sure you don't usemasteraccidentally.How does it work?Here's a flow of GDBT code:Query GitHub for new GDBT releases. If a valid release with a latter version is found, display a message, otherwise skip to the next step.Read evaluation cache from lock files. If lock file hash doesn't match, or-uflag was provided, query Prometheus (VictoriaMetrics) and write the new evaluation result to the lock file.Ifloopoption is provided, assign aresourcefor each loop item. Otherwise, a single resource is assigned.For every resource, query Grafana and fetch JSON model of a dashboard (or a folder).Calculate the difference between the configured and actual state of resources, and make it into a plan.If the command isapply— apply the plan by querying Grafana and sending JSON models of the resourcesSource code structureAll source code can be found undergdbtdirectory:code— configuration and templates parsingdynamic— implementation oflookupandevaluationconceptserrors— error handlingprovider— provider-specific code which implements provider interfaceresource— implementation of Grafana Resource objectstate— state and plan calculation related codecli.py— main program, definition of CLI commandsStandardsGDBT code shall follow OOP patterns. You can learn morehere. Code style shall beBlack. Code shall be formatted using Black formatter,isortand linted withflake8.
All dependencies shall be managed usingPoetry.
GDBT releases shall strictly followSemantic Versioningscheme.PrerequisitesTo work on this project you need:Python 3.8+ (usingpyenvis heavily recommended)PoetryIDE withBlackformatter,flake8linter andisorttool installedUsing PoetryThis project uses Poetry as a dependency management tool. It has a lot of advantages before plain pip, for example:automatic virtual env managementbetter dependency managementbuild systemintegration with test frameworksHere are some example actions you should know:# Activate the virtual environment and get a shell insidepoetryshell# Install the app with all dependenciespoetryinstall# Add a new dependency (identical to pip install x when not using Poetry)poetryaddx# Remove a dependencypoetryremovex# Bump package version (level is one of patch, minor, major)poetryversionlevel# Run a command in the virtual environmentpoetryrunx# Update dependenciespoetryupdate# Build package wheelpoetrybuild-fwheelAll dependencies should be added using Poetry:poetry add x. Please try to reuse existing dependencies to avoid bloating the package; you can find the list of these inpyproject.tomlor by runningpoetry show.Please specify any additional dependencies in module docstring.DocumentationThis tool uses Python docstrings for documentation purposes. Those docstrings should followGoogle's docstrings style guide. You should use docstrings module documentation, reference, TODOs etc.BuildingThis package is distributed as awheel. This simplifies installation and dependency management on target systems.Normally you should use stable CI-built wheels, available on Releases page. In case you need a development wheel for testing, you can always build one yourself:poetrybuild-fwheelYou can find the built wheel indistdirectory.Note:wheel file name contains package metadata, so do not rename the file, otherwise you might not be able to install it!ReleasesGDBTfollowssemantic versioningguidelines. Version numbers don't includevprefixunlessit is a tag name (i.e., tags look likev1.2.3, everything else —1.2.3).All changes are kept track of in the changelog, which can be found inCHANGELOG.mdfile. This file followskeep-a-changelogformat. Please make yourself familiar with it before contributing.Generally, you should test your changes before creating a release. After that, create arelease candidatepre-release, using the instruction below. Version number should be1.2.3-rc.1— an upcoming release version number with RC suffix. After extensively testing the release candidate, you can proceed to creating a release.Release processCheckoutmainbranch.Runpoetry version minor(ormajororpatch, depending on severity of changes in the release). This will bump project version inpyproject.toml.Change[Unreleased]H2 header inCHANGELOG.mdto the new release version (e.g.,[1.2.3]).Add current date in ISO format (YYYY-DD-MM) after the header (e.g.,[1.2.3] - 2011-12-13).Add new[Unreleased]H2 header above all version headers.Add compare link at the bottom ofCHANGELOG.mdas follows:[1.2.3]: https://github.com/zerodayyy/gdbt/compare/v1.2.2...v1.2.3right below[unreleased]link (replace1.2.3with the new release version,1.2.2with the previous release version).Change version in[unreleased]link at the bottom ofCHANGELOG.mdto the new release version (e.g.,[unreleased]: https://github.com/zerodayyy/gdbt/compare/v1.2.3...HEAD).Commit the changes tomainbranch. Commit message should beRelease v1.2.3.Create a release tag:git tag v1.2.3. This should be alightweighttag, not an annotated one.Push the changes and tag to GitHub:git push && git push --tags.If you find the above instructions unclear, take a look at previous releases or contact project maintainers.CI/CDThis repo uses GitHub Actions as CI/CD tool. Pipeline config can be found in.github/workflows/ci-release.yamlfile.As of now, the only pipeline isRelease: it builds the wheel and creates a release in GitHub. It is triggered on tags that start withvprefix.