Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
divinity666 committed May 17, 2021
2 parents 4ea1659 + 89ebcc8 commit 13bbac4
Show file tree
Hide file tree
Showing 64 changed files with 589 additions and 1,502 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: ruby
rvm:
- 2.3.3
- 2.5.5
- 3.0.1
before_install:
- export TZ=Europe/Berlin
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,38 @@ home's energy usage. That's how it started.

## Features

* Build PDF reports based on [grafana](https://github.com/grafana/grafana) dashboards
(other formats supported)
* Include dynamic content from grafana (see [function documentation](FUNCTION_CALLS.md)
as a detailed reference):
* Build reports based on [grafana](https://github.com/grafana/grafana) dashboards, PDF
(default) and many other formats supported
* Easy-to-use configuration wizard, including fully automated functionality to create a
demo report
* Include dynamic content from grafana (find here a reference for all
[asciidcotor reporter calls](FUNCTION_CALLS.md)):
* panels as images
* tables based on grafana panel queries or custom database queries (no images!)
* single values to be integrated in text, based on grafana panel queries or custom
database queries
* Multi purpose use of the reporter
* webservice to be called directly from grafana - it also runs without further
dependencies in the standard asciidoctor docker container!
* standalone command line tool, e.g. to be automated with cron or bash scrips
* Comes with a complete configuration wizard, including functionality to build a
demo report on top of the configured grafana host
* Supports all SQL based datasources, as well as graphite and prometheus
* webservice to be called directly from grafana
* standalone command line tool, e.g. to be automated with `cron` or `bash` scrips
* seemlessly runs from asciidocotor docker container without further dependencies
* Webhook callbacks before, on cancel and on finishing callbacks (see configuration file)
* Solid as a rock, also in case of template errors and whatever else may happen
* Full [API documentation](https://rubydoc.info/gems/ruby-grafana-reporter) available

Functionalities are provided as shown here:

Database | Image rendering | Panel-based rendering | Query-based rendering
------------------------- | :-------: | :-----------: | :------------:
all SQL based datasources | supported | supported | supported
Graphite | supported | supported | supported
Prometheus | supported | supported | supported
other datasources | supported | not-supported | not-supported

## Quick Start

You don't have a grafana setup runnning already? No worries, just configure
`https://play.grafana.org` in the configuration wizard and see the magic
happen for that!
happen!

If your grafana setup requires a login, you'll have to setup an api key for
the reporter. Please follow the steps
Expand All @@ -58,10 +67,6 @@ first.
* [Download latest Windows executable](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
* `ruby-grafana-reporter -w`

Known issues:
* images are currently not included in PDF conversions due to missing support in Prawn gem for windows;
other target formats do work properly with images

**Raspberry Pi:**

* `sudo apt-get install ruby`
Expand Down Expand Up @@ -175,6 +180,6 @@ Inspired by [Izak Marai's grafana reporter](https://github.com/IzakMarais/report
## Donations

If this project saves you as much time as I hope it does, and if you'd like to
support my work, feel free donate, even a cup of coffee is appreciated :)
support my work, feel free donate. :)

[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
4 changes: 2 additions & 2 deletions lib/VERSION.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Version information
GRAFANA_REPORTER_VERSION = [0, 4, 0].freeze
GRAFANA_REPORTER_VERSION = [0, 4, 1].freeze
# Release date
GRAFANA_REPORTER_RELEASE_DATE = '2021-04-14'
GRAFANA_REPORTER_RELEASE_DATE = '2021-05-17'
44 changes: 32 additions & 12 deletions lib/grafana/abstract_datasource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,53 @@ module Grafana
class AbstractDatasource
attr_reader :model

@@subclasses = []

# Registers the subclass as datasource, which is asked by {#accepts?}, if it can handle a datasource
# model.
# @param subclass [Class] class inheriting from this abstract class
def self.inherited(subclass)
@@subclasses << subclass
end

# Overwrite this method, to specify if the current datasource implementation handles the given model.
# This method is called by {#build_instance} to determine, if the current datasource implementation
# can handle the given grafana model. By default this method returns false.
# @param model [Hash] grafana specification of the datasource to check
# @return [Boolean] True if fits, false otherwise
def self.handles?(_model)
false
end

# Factory method to build a datasource from a given datasource Hash description.
# @param ds_model [Hash] grafana specification of a single datasource
# @return [AbstractDatasource] instance of a fitting datasource implementation
def self.build_instance(ds_model)
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model.is_a?(Hash)
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']['id']
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']['category']

return SqlDatasource.new(ds_model) if ds_model['meta']['category'] == 'sql'

case ds_model['meta']['id']
when 'graphite'
return GraphiteDatasource.new(ds_model)

when 'prometheus'
return PrometheusDatasource.new(ds_model)
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta'].is_a?(Hash)

@@subclasses.each do |datasource_class|
return datasource_class.new(ds_model) if datasource_class.handles?(ds_model)
end

raise DatasourceTypeNotSupportedError.new(ds_model['name'], ds_model['meta']['id'])
UnsupportedDatasource.new(ds_model)
end

def initialize(model)
@model = model
end

# @return [String] category of the datasource, e.g. `tsdb` or `sql`
def category
@model['meta']['category']
end

# @return [String] type of the datasource, e.g. `mysql`
def type
@model['type'] || @model['meta']['id']
end

# @return [String] name of the datasource
def name
@model['name']
Expand Down
10 changes: 1 addition & 9 deletions lib/grafana/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,13 @@ def initialize(panel)
end
end

# Raised if no SQL query is specified in a {AbstractSqlQuery} object.
# Raised if no SQL query is specified.
class MissingSqlQueryError < GrafanaError
def initialize
super('No SQL statement has been specified.')
end
end

# Raised if a datasource shall be queried, which is not (yet) supported by the reporter
class DatasourceTypeNotSupportedError < GrafanaError
def initialize(name, type)
super("The configured datasource with name '#{name}' is of type '#{type}', which is currently "\
'not supported by ruby-grafana-reporter. It will only be usable in panel image queries.')
end
end

# Raised if a datasource shall be queried, which is not (yet) supported by the reporter
class InvalidDatasourceQueryProvidedError < GrafanaError
def initialize(query)
Expand Down
10 changes: 3 additions & 7 deletions lib/grafana/grafana.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
module Grafana
# Main class for handling the interaction with one specific Grafana instance.
class Grafana
attr_reader :logger

# @param base_uri [String] full URI pointing to the specific grafana instance without
# trailing slash, e.g. +https://localhost:3000+.
# @param key [String] API key for the grafana instance, if required
Expand Down Expand Up @@ -116,13 +118,7 @@ def initialize_datasources

json = JSON.parse(settings.body)
json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
begin
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
rescue DatasourceTypeNotSupportedError => e
# an unsupported datasource type has been configured in the dashboard
# - no worries here
@logger.warn(e.message)
end
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
end
@datasources['default'] = @datasources[json['defaultDatasource']]
end
Expand Down
6 changes: 6 additions & 0 deletions lib/grafana/graphite_datasource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module Grafana
# Implements the interface to graphite datasources.
class GraphiteDatasource < AbstractDatasource
# @see AbstractDatasource#handles?
def self.handles?(model)
tmp = new(model)
tmp.type == 'graphite'
end

# +:raw_query+ needs to contain a Graphite query as String
# @see AbstractDatasource#request
def request(query_description)
Expand Down
31 changes: 0 additions & 31 deletions lib/grafana/influxdb_datasource.1disabled_rb

This file was deleted.

6 changes: 6 additions & 0 deletions lib/grafana/prometheus_datasource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module Grafana
# Implements the interface to Prometheus datasources.
class PrometheusDatasource < AbstractDatasource
# @see AbstractDatasource#handles?
def self.handles?(model)
tmp = new(model)
tmp.type == 'prometheus'
end

# +:raw_query+ needs to contain a Prometheus query as String
# @see AbstractDatasource#request
def request(query_description)
Expand Down
6 changes: 6 additions & 0 deletions lib/grafana/sql_datasource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module Grafana
# Implements the interface to all SQL based datasources (tested with PostgreSQL and MariaDB/MySQL).
class SqlDatasource < AbstractDatasource
# @see AbstractDatasource#handles?
def self.handles?(model)
tmp = new(model)
tmp.category == 'sql'
end

# +:raw_query+ needs to contain a SQL query as String in the respective database dialect
# @see AbstractDatasource#request
def request(query_description)
Expand Down
7 changes: 7 additions & 0 deletions lib/grafana/unsupported_datasource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Grafana
# Dummy class, which is used, if a datasource is currently unsupported.
class UnsupportedDatasource < AbstractDatasource
end
end
2 changes: 1 addition & 1 deletion lib/grafana/webrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def execute(timeout = nil)
def configure_ssl
@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
if self.class.ssl_cert && !File.exist?(self.class.ssl_cert)
if self.class.ssl_cert && !File.file?(self.class.ssl_cert)
@logger.warn('SSL certificate file does not exist.')
elsif self.class.ssl_cert
@http.cert_store = OpenSSL::X509::Store.new
Expand Down
Loading

0 comments on commit 13bbac4

Please sign in to comment.