ruby-grafana-reporter 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +81 -71
- data/bin/ruby-grafana-reporter +5 -5
- data/lib/VERSION.rb +3 -2
- data/lib/grafana/abstract_datasource.rb +116 -0
- data/lib/grafana/dashboard.rb +1 -3
- data/lib/grafana/errors.rb +15 -0
- data/lib/grafana/grafana.rb +53 -56
- data/lib/grafana/grafana_alerts_datasource.rb +57 -0
- data/lib/grafana/grafana_annotations_datasource.rb +56 -0
- data/lib/grafana/grafana_property_datasource.rb +25 -0
- data/lib/grafana/graphite_datasource.rb +44 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/panel.rb +9 -3
- data/lib/grafana/prometheus_datasource.rb +39 -0
- data/lib/grafana/sql_datasource.rb +65 -0
- data/lib/grafana/variable.rb +1 -0
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +401 -0
- data/lib/grafana_reporter/abstract_report.rb +54 -3
- data/lib/grafana_reporter/alerts_table_query.rb +44 -0
- data/lib/grafana_reporter/annotations_table_query.rb +43 -0
- data/lib/grafana_reporter/application/application.rb +12 -8
- data/lib/grafana_reporter/application/webservice.rb +18 -6
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +89 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +76 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +77 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +72 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +98 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +23 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +24 -31
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +92 -0
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +88 -0
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
- data/lib/grafana_reporter/configuration.rb +12 -6
- data/lib/grafana_reporter/console_configuration_wizard.rb +115 -65
- data/lib/grafana_reporter/demo_report_wizard.rb +87 -0
- data/lib/grafana_reporter/errors.rb +33 -0
- data/lib/grafana_reporter/help.rb +447 -0
- data/lib/grafana_reporter/logger/two_way_logger.rb +1 -1
- data/lib/grafana_reporter/panel_image_query.rb +29 -0
- data/lib/grafana_reporter/panel_property_query.rb +22 -0
- data/lib/grafana_reporter/query_value_query.rb +79 -0
- data/lib/grafana_reporter/report_webhook.rb +35 -0
- data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +0 -3
- metadata +37 -35
- data/lib/grafana/abstract_panel_query.rb +0 -22
- data/lib/grafana/abstract_query.rb +0 -132
- data/lib/grafana/abstract_sql_query.rb +0 -51
- data/lib/grafana/panel_image_query.rb +0 -52
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -101
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
- data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
- data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -30
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -88
- data/lib/grafana_reporter/asciidoctor/help.rb +0 -435
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -36
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -28
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -40
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -312
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -42
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db7e3fbf0f8c1a3d7d9ce8d3e3e215b6f6bb5ec3c170252d575e0516b3312b20
|
4
|
+
data.tar.gz: b05cfa64a8d5bef650917df47ad9b84a140f302e87d2c6664c1fab3272e7212e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3925cc108c3cedc51d6a37926b4ba937296cfd55137d8af40b9254bb92383cf253713321e95e6cfcfeb50de9adf7acd11777cf108227527d2c87658d74173a51
|
7
|
+
data.tar.gz: 79c345933fd16d7c2acfa24715ef09119c1973c605bb5a7b2caa959fcf08ae748e087338717d6e4343281e68a9a3ed9440d5c74e57846691f7033ef2f41e4079
|
data/README.md
CHANGED
@@ -9,15 +9,11 @@ Reporting Service for Grafana
|
|
9
9
|
## Table of Contents
|
10
10
|
|
11
11
|
* [About the project](#about-the-project)
|
12
|
-
* [Getting started](#getting-started)
|
13
|
-
* [Grafana integration](#grafana-integration)
|
14
|
-
* [Webservice overview](#webservice-overview)
|
15
|
-
* [Documentation](#documentation)
|
16
12
|
* [Features](#features)
|
13
|
+
* [Quick Start](#quick-start)
|
14
|
+
* [Grafana integration](#grafana-integration)
|
15
|
+
* [Webservice overview](#webservice-overview)
|
17
16
|
* [Roadmap](#roadmap)
|
18
|
-
* [Contributing](#contributing)
|
19
|
-
* [Licensing](#licensing)
|
20
|
-
* [Acknowledgements](#acknowledgements)
|
21
17
|
* [Donations](#donations)
|
22
18
|
|
23
19
|
## About the project
|
@@ -26,53 +22,88 @@ Did you ever want to create (professional) reports based on Grafana dashboards?
|
|
26
22
|
I did so in order to being able to automatically get monthly reports of my
|
27
23
|
home's energy usage. That's how it started.
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
## Features
|
26
|
+
|
27
|
+
* Build PDF reports based on [grafana](https://github.com/grafana/grafana) dashboards
|
28
|
+
(other formats supported)
|
29
|
+
* Include dynamic content from grafana (see [function documentation](FUNCTION_CALLS.md)
|
30
|
+
as a detailed reference):
|
31
|
+
* panels as images
|
32
|
+
* tables based on grafana panel queries or custom database queries (no images!)
|
33
|
+
* single values to be integrated in text, based on grafana panel queries or custom
|
34
|
+
database queries
|
35
|
+
* Multi purpose use of the reporter
|
36
|
+
* webservice to be called directly from grafana - it also runs without further
|
37
|
+
dependencies in the standard asciidoctor docker container!
|
38
|
+
* standalone command line tool, e.g. to be automated with cron or bash scrips
|
39
|
+
* Comes with a complete configuration wizard, including functionality to build a
|
40
|
+
demo report on top of the configured grafana host
|
41
|
+
* Supports all SQL based datasources, as well as graphite and prometheus
|
42
|
+
* Solid as a rock, also in case of template errors and whatever else may happen
|
43
|
+
* Full [API documentation](https://rubydoc.info/gems/ruby-grafana-reporter) available
|
33
44
|
|
34
|
-
|
45
|
+
## Quick Start
|
35
46
|
|
36
|
-
|
37
|
-
|
47
|
+
You don't have a grafana setup runnning already? No worries, just configure
|
48
|
+
`https://play.grafana.org` in the configuration wizard and see the magic
|
49
|
+
happen for that!
|
38
50
|
|
39
|
-
|
40
|
-
|
51
|
+
If your grafana setup requires a login, you'll have to setup an api key for
|
52
|
+
the reporter. Please follow the steps
|
53
|
+
[described here](https://github.com/divinity666/ruby-grafana-reporter/issues/2#issuecomment-811836757)
|
54
|
+
first.
|
41
55
|
|
42
|
-
|
56
|
+
**Windows:**
|
43
57
|
|
44
|
-
|
45
|
-
|
46
|
-
integration, please have a look at the more extended
|
47
|
-
[installation documentation](INSTALL.md).
|
58
|
+
* [Download latest Windows executable](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
|
59
|
+
* `ruby-grafana-reporter -w`
|
48
60
|
|
49
|
-
|
61
|
+
Known issues:
|
62
|
+
* images are currently not included in PDF conversions due to missing support in Prawn gem for windows;
|
63
|
+
other target formats do work properly with images
|
50
64
|
|
51
|
-
|
65
|
+
**Raspberry Pi:**
|
52
66
|
|
53
|
-
|
54
|
-
|
67
|
+
* `sudo apt-get install ruby`
|
68
|
+
* `gem install ruby-grafana-reporter`
|
69
|
+
* `ruby-grafana-reporter -w`
|
55
70
|
|
56
|
-
|
71
|
+
**Ruby environment:**
|
57
72
|
|
58
|
-
|
73
|
+
* `gem install ruby-grafana-reporter`
|
74
|
+
* `ruby-grafana-reporter -w`
|
59
75
|
|
60
|
-
|
61
|
-
of the procedure, to get a detailed documentation of all the reporter capabilities.
|
62
|
-
The whole [function documentation](FUNCTION_CALLS.md) is also available at the
|
63
|
-
previous link.
|
76
|
+
**Docker environment** (advanced users):
|
64
77
|
|
65
|
-
|
78
|
+
* [Download latest single-rb file](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
|
79
|
+
to an empty folder
|
80
|
+
* create a configuration file by calling `ruby ruby-grafana-reporter -w` (if in doubt,
|
81
|
+
run the command within your docker container)
|
82
|
+
* create file `/<<path-to-single-rb-file-folder>>/startup.sh` with the following
|
83
|
+
content:
|
66
84
|
|
67
|
-
|
85
|
+
```
|
86
|
+
cd /documents
|
87
|
+
ruby bin/ruby-grafana-reporter
|
88
|
+
```
|
89
|
+
* add asciidoctor your compose yaml:
|
68
90
|
|
69
|
-
|
91
|
+
```
|
92
|
+
asciidoctor:
|
93
|
+
image: asciidoctor/docker-asciidoctor
|
94
|
+
container_name: asciidoctor
|
95
|
+
hostname: asciidoctor
|
96
|
+
volumes:
|
97
|
+
- /<<path-to-single-rb-file-folder>>:/documents
|
98
|
+
command:
|
99
|
+
sh /documents/startup.sh
|
100
|
+
restart: unless-stopped
|
101
|
+
```
|
102
|
+
* start/restart the asciidoctor docker container
|
70
103
|
|
71
|
-
|
104
|
+
## Grafana integration
|
72
105
|
|
73
|
-
The key feature of the report is, that it can easily be integrated with grafana
|
74
|
-
(I've not even been talking about the features it is providing for that, but
|
75
|
-
you'll find them having a look in the example results above).
|
106
|
+
The key feature of the report is, that it can easily be integrated with grafana.
|
76
107
|
|
77
108
|
For accessing the reporter from grafana, you need to simply add a link to your
|
78
109
|
grafana dashboard:
|
@@ -82,27 +113,27 @@ grafana dashboard:
|
|
82
113
|
* Select `Add`
|
83
114
|
* Fill out as following:
|
84
115
|
* Type: `link`
|
85
|
-
* Url: `http://<<your-server-url>>:<<your-webservice-port>>/render?var-template=
|
86
|
-
* Title: `
|
116
|
+
* Url: `http://<<your-server-url>>:<<your-webservice-port>>/render?var-template=demo_report`
|
117
|
+
* Title: `Demo Report`
|
87
118
|
* Select `Time range`
|
88
119
|
* Select `Variable values`
|
89
120
|
* Select `Add`
|
90
121
|
|
91
|
-
Now go back to your dashboard and click the newly generated
|
122
|
+
Now go back to your dashboard and click the newly generated `Demo Report`
|
92
123
|
link on it. Now the renderer should start it's task and show you the expected
|
93
124
|
results.
|
94
125
|
|
95
|
-
|
96
|
-
|
97
|
-
|
126
|
+
Please note, that the reporter won't automatically refresh your screen to update
|
127
|
+
the progress. Simply hit `F5` to refresh your browser. After the report has been
|
128
|
+
successfully built, it will show the PDF after the next refresh automatically.
|
98
129
|
|
99
|
-
|
130
|
+
You want to select a template in grafana, which shall then be rendered?
|
100
131
|
Piece of cake: Just add a dashboard variable to your grafana dashboard named
|
101
132
|
`template` and let the user select or enter a template name. To make use of it,
|
102
|
-
you should change the link of the
|
103
|
-
`http://<<your-server-url>>:<<your-webservice-port>>/render
|
104
|
-
|
105
|
-
|
133
|
+
you should change the link of the `Demo Report` link to
|
134
|
+
`http://<<your-server-url>>:<<your-webservice-port>>/render?`. On
|
135
|
+
hitting the new link in the dashboard, grafana will add the selected template as
|
136
|
+
a variable and forward it to the reporter.
|
106
137
|
|
107
138
|
## Webservice overview
|
108
139
|
|
@@ -117,33 +148,13 @@ The main endpoint to call for report generation is configured in the previous ch
|
|
117
148
|
|
118
149
|
However, if you would like to see, currently running report generations and previously generated reports, you may want to call the endpoint `/overview`.
|
119
150
|
|
120
|
-
## Documentation
|
121
|
-
|
122
|
-
The [function documentation](FUNCTION_CALLS.md) contains a complete overview of
|
123
|
-
all possible grafana calls, to generate dynamic report templates.
|
124
|
-
|
125
|
-
The [API documentation](https://rubydoc.info/gems/ruby-grafana-reporter) can be
|
126
|
-
found here.
|
127
|
-
|
128
|
-
## Features
|
129
|
-
|
130
|
-
* Build report template including all imaginable grafana content:
|
131
|
-
* panels as images
|
132
|
-
* panel table query or custom query results as real document tables (not images!)
|
133
|
-
* single panel value or custom query single value result integrated in texts
|
134
|
-
* Solid as a rock, also in case of template errors and whatever else may happen
|
135
|
-
* Fully controllable as command line application or as a webservice
|
136
|
-
* Seamlessly integrates with asciidoctor docker container
|
137
|
-
* Developed to support other tools than asciidoctor as well
|
138
|
-
|
139
151
|
## Roadmap
|
140
152
|
|
141
153
|
This is just a collection of things, I am heading for in future, without a schedule.
|
142
154
|
|
143
|
-
*
|
155
|
+
* Support all grafana datasources
|
144
156
|
* Solve code TODOs
|
145
157
|
* Become [rubocop](https://rubocop.org/) ready
|
146
|
-
* Clean and properly setup test cases
|
147
158
|
|
148
159
|
## Contributing
|
149
160
|
|
@@ -167,4 +178,3 @@ If this project saves you as much time as I hope it does, and if you'd like to
|
|
167
178
|
support my work, feel free donate, even a cup of coffee is appreciated :)
|
168
179
|
|
169
180
|
[](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
|
170
|
-
|
data/bin/ruby-grafana-reporter
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative '../lib/
|
5
|
-
GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocra)
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/ruby_grafana_reporter'
|
5
|
+
GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocra)
|
data/lib/VERSION.rb
CHANGED
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# This abstract class defines the base functionalities for the common datasource implementations.
|
5
|
+
# Additionally it provides a factory method to build a real datasource from a given specification.
|
6
|
+
class AbstractDatasource
|
7
|
+
attr_reader :model
|
8
|
+
|
9
|
+
# Factory method to build a datasource from a given datasource Hash description.
|
10
|
+
# @param ds_model [Hash] grafana specification of a single datasource
|
11
|
+
# @return [AbstractDatasource] instance of a fitting datasource implementation
|
12
|
+
def self.build_instance(ds_model)
|
13
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model.is_a?(Hash)
|
14
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']
|
15
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']['id']
|
16
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta']['category']
|
17
|
+
|
18
|
+
return SqlDatasource.new(ds_model) if ds_model['meta']['category'] == 'sql'
|
19
|
+
|
20
|
+
case ds_model['meta']['id']
|
21
|
+
when 'graphite'
|
22
|
+
return GraphiteDatasource.new(ds_model)
|
23
|
+
|
24
|
+
when 'prometheus'
|
25
|
+
return PrometheusDatasource.new(ds_model)
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
raise DatasourceTypeNotSupportedError.new(ds_model['name'], ds_model['meta']['id'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(model)
|
33
|
+
@model = model
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] name of the datasource
|
37
|
+
def name
|
38
|
+
@model['name']
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Integer] ID of the datasource
|
42
|
+
def id
|
43
|
+
@model['id'].to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
# @abstract
|
47
|
+
#
|
48
|
+
# Executes a request for the current database with the given options.
|
49
|
+
#
|
50
|
+
# Used format of the response will always be the following:
|
51
|
+
#
|
52
|
+
# {
|
53
|
+
# :header => [column_title_1, column_title_2],
|
54
|
+
# :content => [
|
55
|
+
# [row_1_column_1, row_1_column_2],
|
56
|
+
# [row_2_column_1, row_2_column_2]
|
57
|
+
# ]
|
58
|
+
# }
|
59
|
+
#
|
60
|
+
# @param query_description [Hash] query description, which will requested:
|
61
|
+
# @option [String] :from +from+ timestamp
|
62
|
+
# @option [String] :to +to+ timestamp
|
63
|
+
# @option [Integer] :timeout expected timeout for the request
|
64
|
+
# @option [WebRequest] :prepared_request prepared web request for relevant {Grafana} instance, if this is needed by datasource
|
65
|
+
# @option [String] :raw_query raw query, which shall be executed. May include variables, which will be replaced before execution
|
66
|
+
# @option [Hash<Variable>] :variables hash of variables, which can potentially be replaced in the given +:raw_query+
|
67
|
+
# @return [Hash] sql result formatted as stated above
|
68
|
+
def request(query_description)
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
# @abstract
|
73
|
+
#
|
74
|
+
# The different datasources supported by grafana use different ways to store the query in the
|
75
|
+
# panel's JSON model. This method extracts a query from that description, that can be used
|
76
|
+
# by the {AbstractDatasource} implementation of the datasource.
|
77
|
+
#
|
78
|
+
# @param panel_query_target [Hash] grafana panel target, which contains the query description
|
79
|
+
# @return [String] query string, which can be used as +raw_query+ in a {#request}
|
80
|
+
def raw_query_from_panel_model(panel_query_target)
|
81
|
+
raise NotImplementedError
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Replaces the grafana variables in the given string with their replacement value.
|
87
|
+
#
|
88
|
+
# @param string [String] string in which the variables shall be replaced
|
89
|
+
# @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the
|
90
|
+
# given string
|
91
|
+
# @return [String] string in which all variables are properly replaced
|
92
|
+
def replace_variables(string, variables = {})
|
93
|
+
res = string
|
94
|
+
repeat = true
|
95
|
+
repeat_count = 0
|
96
|
+
|
97
|
+
# TODO: find a proper way to replace variables recursively instead of over and over again
|
98
|
+
# TODO: add tests for recursive replacement of variable
|
99
|
+
while repeat && (repeat_count < 3)
|
100
|
+
repeat = false
|
101
|
+
repeat_count += 1
|
102
|
+
variables.each do |var_name, obj|
|
103
|
+
# only set ticks if value is string
|
104
|
+
variable = var_name.gsub(/^var-/, '')
|
105
|
+
res = res.gsub(/(?:\$\{#{variable}(?::(?<format>\w+))?\}|\$#{variable})/) do
|
106
|
+
# TODO: respect datasource requirements for formatting here
|
107
|
+
obj.value_formatted($LAST_MATCH_INFO ? $LAST_MATCH_INFO[:format] : nil)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
repeat = true if res.include?('$')
|
111
|
+
end
|
112
|
+
|
113
|
+
res
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/grafana/dashboard.rb
CHANGED
data/lib/grafana/errors.rb
CHANGED
@@ -63,4 +63,19 @@ module Grafana
|
|
63
63
|
super('No SQL statement has been specified.')
|
64
64
|
end
|
65
65
|
end
|
66
|
+
|
67
|
+
# Raised if a datasource shall be queried, which is not (yet) supported by the reporter
|
68
|
+
class DatasourceTypeNotSupportedError < GrafanaError
|
69
|
+
def initialize(name, type)
|
70
|
+
super("The configured datasource with name '#{name}' is of type '#{type}', which is currently "\
|
71
|
+
'not supported by ruby-grafana-reporter. It will only be usable in panel image queries.')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Raised if a datasource shall be queried, which is not (yet) supported by the reporter
|
76
|
+
class InvalidDatasourceQueryProvidedError < GrafanaError
|
77
|
+
def initialize(query)
|
78
|
+
super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
|
79
|
+
end
|
80
|
+
end
|
66
81
|
end
|
data/lib/grafana/grafana.rb
CHANGED
@@ -13,12 +13,11 @@ module Grafana
|
|
13
13
|
# trailing slash, e.g. +https://localhost:3000+.
|
14
14
|
# @param key [String] API key for the grafana instance, if required
|
15
15
|
# @param opts [Hash] additional options.
|
16
|
-
# Currently supporting +:logger
|
16
|
+
# Currently supporting +:logger+.
|
17
17
|
def initialize(base_uri, key = nil, opts = {})
|
18
18
|
@base_uri = base_uri
|
19
19
|
@key = key
|
20
20
|
@dashboards = {}
|
21
|
-
@ssl_cert = opts[:ssl_cert]
|
22
21
|
@logger = opts[:logger] || ::Logger.new(nil)
|
23
22
|
|
24
23
|
initialize_datasources unless @base_uri.empty?
|
@@ -31,33 +30,52 @@ module Grafana
|
|
31
30
|
#
|
32
31
|
# @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
|
33
32
|
def test_connection
|
34
|
-
if
|
33
|
+
if prepare_request({ relative_url: '/api/datasources' }).execute.is_a?(Net::HTTPOK)
|
35
34
|
# we have admin rights
|
36
35
|
@logger.warn('Reporter is running with Admin privileges on grafana. This is a potential security risk.')
|
37
36
|
return 'Admin'
|
38
37
|
end
|
39
38
|
# check if we have lower rights
|
40
|
-
return 'Failed' unless
|
39
|
+
return 'Failed' unless prepare_request({ relative_url: '/api/dashboards/home' }).execute.is_a?(Net::HTTPOK)
|
41
40
|
|
42
41
|
@logger.info('Reporter is running with NON-Admin privileges on grafana.')
|
43
42
|
'NON-Admin'
|
44
43
|
end
|
45
44
|
|
46
|
-
# Returns the
|
45
|
+
# Returns the datasource, which has been queried by the datasource name.
|
47
46
|
#
|
48
|
-
# @
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
# @param datasource_name [String] name of the searched datasource
|
48
|
+
# @return [Datasource] Datasource for the specified datasource name
|
49
|
+
def datasource_by_name(datasource_name)
|
50
|
+
datasource_name = 'default' if datasource_name.to_s.empty?
|
51
|
+
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
52
52
|
|
53
|
-
|
53
|
+
@datasources[datasource_name]
|
54
54
|
end
|
55
55
|
|
56
|
-
# Returns
|
56
|
+
# Returns the datasource, which has been queried by the datasource id.
|
57
57
|
#
|
58
|
-
# @
|
59
|
-
|
60
|
-
|
58
|
+
# @param datasource_id [Integer] id of the searched datasource
|
59
|
+
# @return [Datasource] Datasource for the specified datasource id
|
60
|
+
def datasource_by_id(datasource_id)
|
61
|
+
datasource = @datasources.select { |_name, ds| ds.id == datasource_id.to_i }.values.first
|
62
|
+
raise DatasourceDoesNotExistError.new('id', datasource_id) unless datasource
|
63
|
+
|
64
|
+
datasource
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array] Array of dashboard uids within the current grafana object
|
68
|
+
def dashboard_ids
|
69
|
+
response = prepare_request({ relative_url: '/api/search' }).execute
|
70
|
+
return [] unless response.is_a?(Net::HTTPOK)
|
71
|
+
|
72
|
+
dashboards = JSON.parse(response.body)
|
73
|
+
|
74
|
+
dashboards.each do |dashboard|
|
75
|
+
@dashboards[dashboard['uid']] = nil unless @dashboards[dashboard['uid']]
|
76
|
+
end
|
77
|
+
|
78
|
+
@dashboards.keys
|
61
79
|
end
|
62
80
|
|
63
81
|
# @param dashboard_uid [String] UID of the searched {Dashboard}
|
@@ -65,54 +83,27 @@ module Grafana
|
|
65
83
|
def dashboard(dashboard_uid)
|
66
84
|
return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
|
67
85
|
|
68
|
-
response =
|
69
|
-
|
70
|
-
|
71
|
-
raise DashboardDoesNotExistError, dashboard_uid if model.nil?
|
86
|
+
response = prepare_request({ relative_url: "/api/dashboards/uid/#{dashboard_uid}" }).execute
|
87
|
+
raise DashboardDoesNotExistError, dashboard_uid unless response.is_a?(Net::HTTPOK)
|
72
88
|
|
73
89
|
# cache dashboard for reuse
|
90
|
+
model = JSON.parse(response.body)['dashboard']
|
74
91
|
@dashboards[dashboard_uid] = Dashboard.new(model, self)
|
75
92
|
|
76
93
|
@dashboards[dashboard_uid]
|
77
94
|
end
|
78
95
|
|
79
|
-
#
|
96
|
+
# Prepares a {WebRequest} object for the current {Grafana} instance, which may be enriched
|
97
|
+
# with further properties and can then run {WebRequest#execute}.
|
80
98
|
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def execute_http_request(relative_uri, options = {}, timeout = nil)
|
90
|
-
uri = URI.parse(@base_uri + relative_uri)
|
91
|
-
default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
|
92
|
-
options = default_options.merge(options)
|
93
|
-
|
94
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
95
|
-
if @base_uri =~ /^https/
|
96
|
-
http.use_ssl = true
|
97
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
98
|
-
if @ssl_cert && !File.exist?(@ssl_cert)
|
99
|
-
@logger.warn('SSL certificate file does not exist.')
|
100
|
-
elsif @ssl_cert
|
101
|
-
http.cert_store = OpenSSL::X509::Store.new
|
102
|
-
http.cert_store.set_default_paths
|
103
|
-
http.cert_store.add_file(@ssl_cert)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
http.read_timeout = timeout.to_i if timeout
|
107
|
-
|
108
|
-
request = options[:request].new(uri.request_uri)
|
109
|
-
request['Accept'] = options[:accept]
|
110
|
-
request['Content-Type'] = options[:content_type]
|
111
|
-
request['Authorization'] = "Bearer #{@key}" unless @key.nil?
|
112
|
-
request.body = options[:body]
|
113
|
-
|
114
|
-
@logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
|
115
|
-
http.request(request)
|
99
|
+
# @option options [Hash] :relative_url relative URL with a leading slash, which shall be queried
|
100
|
+
# @option options [Hash] :accept
|
101
|
+
# @option options [Hash] :body
|
102
|
+
# @option options [Hash] :content_type
|
103
|
+
# @return [WebRequest] webrequest prepared for execution
|
104
|
+
def prepare_request(options = {})
|
105
|
+
auth = @key ? { authorization: "Bearer #{@key}" } : {}
|
106
|
+
WebRequest.new(@base_uri, auth.merge({ logger: @logger }).merge(options))
|
116
107
|
end
|
117
108
|
|
118
109
|
private
|
@@ -120,12 +111,18 @@ module Grafana
|
|
120
111
|
def initialize_datasources
|
121
112
|
@datasources = {}
|
122
113
|
|
123
|
-
settings =
|
114
|
+
settings = prepare_request({ relative_url: '/api/frontend/settings' }).execute
|
124
115
|
return unless settings.is_a?(Net::HTTPOK)
|
125
116
|
|
126
117
|
json = JSON.parse(settings.body)
|
127
118
|
json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
|
128
|
-
|
119
|
+
begin
|
120
|
+
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
|
121
|
+
rescue DatasourceTypeNotSupportedError => e
|
122
|
+
# an unsupported datasource type has been configured in the dashboard
|
123
|
+
# - no worries here
|
124
|
+
@logger.warn(e.message)
|
125
|
+
end
|
129
126
|
end
|
130
127
|
@datasources['default'] = @datasources[json['defaultDatasource']]
|
131
128
|
end
|