ruby-grafana-reporter 0.1.6

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +248 -0
  4. data/lib/VERSION.rb +3 -0
  5. data/lib/grafana/abstract_panel_query.rb +20 -0
  6. data/lib/grafana/abstract_query.rb +127 -0
  7. data/lib/grafana/abstract_sql_query.rb +42 -0
  8. data/lib/grafana/dashboard.rb +66 -0
  9. data/lib/grafana/errors.rb +61 -0
  10. data/lib/grafana/grafana.rb +131 -0
  11. data/lib/grafana/panel.rb +39 -0
  12. data/lib/grafana/panel_image_query.rb +49 -0
  13. data/lib/grafana/variable.rb +259 -0
  14. data/lib/grafana_reporter/abstract_report.rb +109 -0
  15. data/lib/grafana_reporter/application/application.rb +229 -0
  16. data/lib/grafana_reporter/application/errors.rb +30 -0
  17. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +99 -0
  18. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +96 -0
  19. data/lib/grafana_reporter/asciidoctor/errors.rb +37 -0
  20. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +86 -0
  21. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +86 -0
  22. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +67 -0
  23. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +65 -0
  24. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +58 -0
  25. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +75 -0
  26. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +70 -0
  27. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +18 -0
  28. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +41 -0
  29. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +202 -0
  30. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +67 -0
  31. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +65 -0
  32. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +57 -0
  33. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +32 -0
  34. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +23 -0
  35. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +43 -0
  36. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +36 -0
  37. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +309 -0
  38. data/lib/grafana_reporter/asciidoctor/report.rb +159 -0
  39. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +34 -0
  40. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +32 -0
  41. data/lib/grafana_reporter/configuration.rb +326 -0
  42. data/lib/grafana_reporter/errors.rb +38 -0
  43. data/lib/grafana_reporter/logger/two_way_logger.rb +52 -0
  44. data/lib/ruby-grafana-reporter.rb +27 -0
  45. metadata +88 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1ed0c1b79ce0e155cc974eaa86c09221e106376660515903765cb9e78e9c00f1
4
+ data.tar.gz: 5e6fb779aa14cfbcc75f4f6916b24013fdcb036bf1a9fb46f844bbf0eaa99771
5
+ SHA512:
6
+ metadata.gz: caac9139284da4855b6ca4686b4283129ca455ad0ad3b68b0e06c0232e48facacd4e813efe0960cb277f1d8d8ce64e30209af04fd6ab7eadac778a973c0192a3
7
+ data.tar.gz: b738db7b1bc73c95369682c977e7db4757920539ec07ba386f7b91af7ea3321403f759a75ad4041c3bd94756eea2c1e70bc24759e9a9ee72fcec871958c3d85f
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Christian Kohlmeyer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESs
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,248 @@
1
+ [![MIT License](https://img.shields.io/github/license/divinity666/ruby-grafana-reporter.svg?style=flat-square)](https://github.com/divinity666/ruby-grafana-reporter/blob/main/LICENSE)
2
+
3
+ # Ruby Grafana Reporter
4
+ (Asciidoctor) Reporter Service for Grafana
5
+
6
+ Did you ever want to create (professional) reports based on Grafana dashboards?
7
+ I did so in order to being able to automatically get monthly reports of my
8
+ home's energy usage. That's how it started.
9
+
10
+ The reporter provides a full extension setup for the famous
11
+ [Asciidoctor](https://github.com/asciidoctor/asciidoctor) and can perfectly
12
+ integrate in a docker environment.
13
+
14
+ As a result of the reporter, you receive PDF documents or any other format that
15
+ is supported by [Asciidoctor](https://github.com/asciidoctor/asciidoctor).
16
+
17
+ ## Documentation
18
+
19
+ Find the complete
20
+ [API documentation](https://rubydoc.info/github/divinity666/ruby-grafana-reporter)
21
+ at this link.
22
+
23
+ ## Installing
24
+
25
+ There exist several ways of installing the reporter. All of them have in
26
+ common, that they require a working ruby environment. Check with the following
27
+ commands, that the tools are setup and run properly:
28
+
29
+ ruby -v
30
+ gem -v
31
+
32
+ Download the ruby grafana reporter to a folder of your choice.
33
+
34
+ You may want to use the single file application as well. BTW, you may build
35
+ your own single file application by calling
36
+
37
+ ruby bin/get_single_file_application.rb
38
+
39
+ ### Barebone ruby installation
40
+
41
+ To install on a plain ruby installation, follow these steps:
42
+
43
+ Install asciidoctor
44
+
45
+ gem install asciidoctor asciidoctor-pdf zip
46
+
47
+ or simply use
48
+
49
+ bundle install
50
+
51
+ To check if all dependencies are setup properly, run the following command
52
+ in that folder:
53
+
54
+ ruby bin/ruby-grafana-reporter.rb -h
55
+
56
+ ### GEM installation
57
+
58
+ To install as a gem, simply run:
59
+
60
+ gem install ruby-grafana-reporter
61
+
62
+ To see if it works properly, you may run:
63
+
64
+ irb
65
+ require 'ruby-grafana-reporter'
66
+ GrafanaReporter::Application::Application.new.configure_and_run
67
+
68
+ The gem installation might mainly be interesting, if you would like to use the
69
+ reporter as a library and include it in other application setups.
70
+
71
+ ### Docker integration
72
+
73
+ Essentially you need to make the application available within your asciidoctor
74
+ docker container and run the following command
75
+
76
+ ruby bin/ruby-grafana-reporter.rb -h
77
+
78
+ If you are unsure, on how to make it available in the container, you may refer
79
+ to the information in chapter 'Run as a service' for the docker integration
80
+ below.
81
+
82
+ ## Initial Configuration
83
+
84
+ Create a first configuration file, named e.g. `myconfig` with the following
85
+ content:
86
+
87
+ grafana-reporter:
88
+ templates-folder: templates
89
+ reports-folder: reports
90
+
91
+ grafana:
92
+ default:
93
+ host: <<url to your grafana host, e.g. https://localhost:3000>>
94
+ api_key: <<api key to be used by the reporter>>
95
+ datasources: # mandatory, if the api_key has only viewer rights, optional otherwise
96
+ "<<data source name in grafana>>": <<data source id in grafana>>
97
+
98
+ default-document-attributes:
99
+ imagesdir: .
100
+
101
+ Check out if the configuration is valid and your grafana instance can be accessed
102
+ properly.
103
+
104
+ ### Barebone ruby installation
105
+
106
+ ruby bin/ruby-grafana-reporter.rb myconfig --test default
107
+
108
+ ### GEM installation
109
+
110
+ require 'ruby-grafana-reporter'
111
+ GrafanaReporter::Application::Application.new.configure_and_run(["myconfig", "--test", "default"])
112
+
113
+ ### Docker integration
114
+
115
+ Same as in barebone ruby installation. Make sure you are running the command
116
+ from inside the container, e.g. by using `docker exec`.
117
+
118
+ ## Hello World example
119
+
120
+ Create a first asciidoctor template file in your `templates-folder`, e.g.
121
+ `myfirsttemplate.adoc` with the following content:
122
+
123
+ = First Ruby Grafana Reporter Example
124
+
125
+ include::grafana_help[]
126
+
127
+ include::grafana_environment[]
128
+
129
+ Now you're ready to go! Let's check it out!
130
+
131
+ ### Barebone ruby installation
132
+
133
+ ruby bin/ruby-grafana-reporter.rb myconfig --template myfirsttemplate.adoc --output myfirstrender.pdf
134
+
135
+ You should now find a PDF document named `myfirstrender.pdf` which includes a detailed
136
+ help page on how to use the ruby grafana reporter functions in asciidoctor, as well
137
+ as a list of all environment variables that can be accessed.
138
+
139
+ ### GEM installation
140
+
141
+ require 'ruby-grafana-reporter'
142
+ GrafanaReporter::Application::Application.new.configure_and_run(["myconfig", "--template", "myfirsttemplate.adoc", "--output", "myfirstrender.pdf"])
143
+
144
+ ### Docker integration
145
+
146
+ Same as in barebone ruby installation. Make sure you are running the command
147
+ from inside the container, e.g. by using `docker exec`.
148
+
149
+ ## Run as a service
150
+
151
+ Running the reporter as a webservice provides the following URLs
152
+
153
+ /render - for rendering a template
154
+ /overview - for all running or retained renderings
155
+ /view_report - for viewing the status or receving the result of a specific rendering
156
+ /cancel_report - for cancelling the rendering of a specific report
157
+
158
+ ### Barebone ruby installation
159
+
160
+ ruby bin/ruby-grafana-reporter.rb myconfig
161
+
162
+ Test your configuration by requesting the following URL in a browser of your
163
+ choice:
164
+
165
+ http://<<your-server-url>>:8815/render?var-template=myfirsttemplate.adoc
166
+
167
+ ### GEM installation
168
+
169
+ require 'ruby-grafana-reporter'
170
+ GrafanaReporter::Application::Application.new.configure_and_run(["myconfig"])
171
+
172
+ Test your configuration by requesting the following URL in a browser of your
173
+ choice:
174
+
175
+ http://<<your-server-url>>:8815/render?var-template=myfirsttemplate.adoc
176
+
177
+ ### Docker integration
178
+
179
+ Assuming you have a `docker-compose` setup running, you may want to add the
180
+ following to your services secion in your `docker-compose.yml`:
181
+
182
+ asciidoctor:
183
+ image: asciidoctor/docker-asciidoctor
184
+ container_name: asciidoctor
185
+ hostname: asciidoctor
186
+ volumes:
187
+ - /<<an-empty-local-path>>:/documents
188
+ command:
189
+ sh /documents/startup.sh
190
+ restart: unless-stopped
191
+
192
+ Additionally you need to create a `startup.sh` file in the folder
193
+ `<<an-empty-local-path>>` with the following content:
194
+
195
+ cd /documents
196
+ ruby bin/ruby-grafana-reporter.rb myconfig
197
+
198
+ After restarting the container, the service should be running.
199
+
200
+ Test your configuration by requesting the following URL in a browser of your
201
+ choice:
202
+
203
+ http://<<your-server-url>>:8815/render?var-template=myfirsttemplate.adoc
204
+
205
+ ## Features
206
+
207
+ * Integrate grafana panel images, grafana panel query results as table or single values,
208
+ custom SQL query results as tables, alers, annotations and many more
209
+ * Solid as a rock, also in case of template errors (at least it aims to be)
210
+ * Runs standalone or as a webservice
211
+ * Seamlessly integrates with asciidoctor docker container
212
+ * Developed for being able to support other tools than asciidoctor as well
213
+
214
+ ## Roadmap
215
+
216
+ This is just a collection of things, I am heading for in future, without a schedule.
217
+
218
+ * Add documentation for configuration file
219
+ * Share (anonymized) rspec tests in this repo
220
+ * Add a simple plugin system to support specific asciidoctor modifications
221
+ * Solve code TODOs
222
+ * Become [rubocop](https://rubocop.org/) ready
223
+
224
+ ## Contributing
225
+
226
+ If you'd like to contribute, please fork the repository and use a feature
227
+ branch. Pull requests are warmly welcome.
228
+
229
+ Though not yet valid for my code, I'd like to see the project become
230
+ [rubocop](https://rubocop.org/) ready :-)
231
+
232
+ ## Licensing
233
+
234
+ The code in this project is licensed under MIT license.
235
+
236
+ ## Acknowledgements
237
+ * [asciidoctor](https://github.com/asciidoctor/asciidoctor)
238
+ * [asciidoctor-pdf](https://github.com/asciidoctor/asciidoctor-pdf)
239
+
240
+ Inspired by [Izak Marai's grafana reporter](https://github.com/IzakMarais/reporter)
241
+
242
+ ## Donations
243
+
244
+ If this project saves you as much time as I hope it does, and if you'd like to
245
+ support my work, feel free donate, even a cup of coffee is appreciated :)
246
+
247
+ [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
248
+
@@ -0,0 +1,3 @@
1
+ # Version information
2
+ GRAFANA_REPORTER_VERSION = [0, 1, 6].freeze
3
+ GRAFANA_REPORTER_RELEASE_DATE = '2020-10-19'
@@ -0,0 +1,20 @@
1
+ require_relative 'abstract_query'
2
+
3
+ module Grafana
4
+ # @abstract
5
+ #
6
+ # Used as a superclass for all queries, which rely on a {Panel} object.
7
+ #
8
+ # @see AbstractQuery
9
+ class AbstractPanelQuery < AbstractQuery
10
+ attr_reader :panel
11
+
12
+ # Initializes the variables of the query using {AbstractQuery#extract_dashboard_variables}.
13
+ # @param panel [Panel] panel for which the query shall be executed
14
+ def initialize(panel)
15
+ super()
16
+ @panel = panel
17
+ extract_dashboard_variables(@panel.dashboard)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,127 @@
1
+ module Grafana
2
+ # @abstract Override {#url}, #{#request}, {#pre_process} and {#post_process} in subclass.
3
+ #
4
+ # Superclass containing everything for all queries towards grafana.
5
+ class AbstractQuery
6
+ attr_accessor :from, :to, :timeout, :result
7
+ attr_reader :variables
8
+
9
+ def initialize
10
+ @variables = {}
11
+ end
12
+
13
+ # Runs the whole process to receive values properly from this query:
14
+ # - calls {#pre_process}
15
+ # - executes this query against the given {Grafana} instance
16
+ # - calls {#post_process}
17
+ # - returns the result
18
+ #
19
+ # @param grafana [Grafana] {Grafana} object, against which the query is executed
20
+ # @return [Object] result of the query
21
+ def execute(grafana)
22
+ return @result unless @result.nil?
23
+
24
+ pre_process(grafana)
25
+ @result = grafana.execute_http_request(url, request, timeout)
26
+ post_process
27
+ @result
28
+ end
29
+
30
+ # Used to retrieve default configurations from the given {Dashboard} and store them as settings in the query.
31
+ #
32
+ # Following data is extracted:
33
+ # - +from+, by {Dashboard#from_time}
34
+ # - +to+, by {Dashboard#to_time}
35
+ # - and all variables as {Variable}, prefixed with +var-+, as grafana also does it
36
+ def extract_dashboard_variables(dashboard)
37
+ @from = dashboard.from_time
38
+ @to = dashboard.to_time
39
+ dashboard.variables.each { |item| merge_variables({ "var-#{item.name}": item }) }
40
+ self
41
+ end
42
+
43
+ # Merges the given Hash with the stored variables.
44
+ #
45
+ # Can be used to easily set many values at once in the local variables hash.
46
+ #
47
+ # Please note, that the values of the Hash need to be of type {Variable}.
48
+ #
49
+ # @param hash [Hash<String,Variable>] Hash containing variable name as key and {Variable} as value
50
+ # @return [AbstractQuery] this object
51
+ def merge_variables(hash)
52
+ hash.each do |k, v|
53
+ if @variables[k.to_s].nil?
54
+ @variables[k.to_s] = v
55
+ else
56
+ @variables[k.to_s].raw_value = v.raw_value
57
+ end
58
+ end
59
+ self
60
+ end
61
+
62
+ # @return [Hash<String, Variable>] all grafana variables stored in this query, i.e. the variable name is prefixed with +var-+
63
+ def grafana_variables
64
+ @variables.select { |k, _v| k =~ /^var-.+/ }
65
+ end
66
+
67
+ # Replaces the grafana variables in the given string with their replacement value.
68
+ #
69
+ # @param string [String] string in which the variables shall be replaced
70
+ # @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the given string
71
+ # @return [String] string in which all variables are properly replaced
72
+ def replace_variables(string, variables = {})
73
+ res = string
74
+ repeat = true
75
+ repeat_count = 0
76
+
77
+ # TODO find a proper way to replace variables recursively instead of over and over again
78
+ # TODO add tests for recursive replacement of variable
79
+ while repeat and repeat_count < 3
80
+ repeat = false
81
+ repeat_count += 1
82
+ variables.each do |var_name, obj|
83
+ # only set ticks if value is string
84
+ variable = var_name.gsub(/^var-/, '')
85
+ res = res.gsub(/(?:\$\{#{variable}(?::(?<format>[\w]+))?\}|(?<!\.)\$#{variable}(?!\.))/) do
86
+ obj.value_formatted($~[:format])
87
+ end
88
+ end
89
+ repeat = true if res.include?('$')
90
+ end
91
+
92
+ res
93
+ end
94
+
95
+ # @abstract
96
+ #
97
+ # @return [String] String containing the relative URL to execute the query
98
+ def uri
99
+ raise NotImplementedError
100
+ end
101
+
102
+ # @abstract
103
+ #
104
+ # @return [Hash] Hash containing the request parameters, which shall be overwritten or extended in {Grafana#execute_http_request}
105
+ def request
106
+ raise NotImplementedError
107
+ end
108
+
109
+ # @abstract
110
+ #
111
+ # Use this function to perform all necessary actions, before the query is actually executed.
112
+ # Here you can e.g. set values of variables or similar.
113
+ #
114
+ # @param grafana [Grafana] {Grafana} object, against which the query shall be executed
115
+ def pre_process(grafana)
116
+ raise NotImplementedError
117
+ end
118
+
119
+ # @abstract
120
+ #
121
+ # Use this function to format the raw result of the @result variable to conform to the expected return value.
122
+ # You might also want to {#replace_variables} in the @result or similar.
123
+ def post_process
124
+ raise NotImplementedError
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,42 @@
1
+ module Grafana
2
+ # @abstract
3
+ #
4
+ # Used as a superclass for all queries, which execute SQL queries against {Grafana}.
5
+ #
6
+ # @see AbstractQuery
7
+ class AbstractSqlQuery < AbstractQuery
8
+ attr_reader :sql, :datasource_id
9
+
10
+ # @param raw_sql [String] raw sql statement, as it can be sent to a SQL database
11
+ # @param datasource_id [Integer] ID of the datasource against which the query is run
12
+ def initialize(raw_sql, datasource_id)
13
+ super()
14
+ @sql = raw_sql
15
+ @datasource_id = datasource_id
16
+ end
17
+
18
+ # @return [String] relative URL, where the request has to be sent to.
19
+ def url
20
+ '/api/tsdb/query'
21
+ end
22
+
23
+ # @return [Hash] request, which executes the SQL statement against the specified datasource
24
+ def request
25
+ { body: { from: @from, to: @to, queries: [rawSql: @sql, datasourceId: @datasource_id.to_i, format: 'table'] }.to_json, request: Net::HTTP::Post }
26
+ end
27
+
28
+ # Replaces all variables in the SQL statement.
29
+ def pre_process(grafana)
30
+ raise MissingSqlQueryError if @sql.nil?
31
+ unless grafana.datasource_id_exists?(@datasource_id.to_i)
32
+ raise DatasourceDoesNotExistError.new('id', @datasource_id)
33
+ end
34
+
35
+ @sql = replace_variables(@sql, grafana_variables)
36
+ # remove comments in query
37
+ @sql.gsub!(/--[^\r\n]*(?:[\r\n]+|$)/, ' ')
38
+ @sql.gsub!(/\r\n/, ' ')
39
+ @sql.gsub!(/\n/, ' ')
40
+ end
41
+ end
42
+ end