ruby-grafana-reporter 0.1.7 → 0.2.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +166 -339
  3. data/bin/ruby-grafana-reporter +5 -4
  4. data/lib/VERSION.rb +5 -3
  5. data/lib/grafana/abstract_panel_query.rb +22 -20
  6. data/lib/grafana/abstract_query.rb +132 -127
  7. data/lib/grafana/abstract_sql_query.rb +51 -42
  8. data/lib/grafana/dashboard.rb +77 -66
  9. data/lib/grafana/errors.rb +66 -61
  10. data/lib/grafana/grafana.rb +130 -131
  11. data/lib/grafana/panel.rb +41 -39
  12. data/lib/grafana/panel_image_query.rb +52 -49
  13. data/lib/grafana/variable.rb +217 -259
  14. data/lib/grafana_reporter/abstract_report.rb +112 -109
  15. data/lib/grafana_reporter/application/application.rb +404 -229
  16. data/lib/grafana_reporter/application/errors.rb +33 -30
  17. data/lib/grafana_reporter/application/webservice.rb +231 -0
  18. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +104 -99
  19. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +99 -96
  20. data/lib/grafana_reporter/asciidoctor/errors.rb +40 -37
  21. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +92 -86
  22. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +91 -86
  23. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +69 -67
  24. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +68 -65
  25. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +61 -58
  26. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +78 -75
  27. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +73 -70
  28. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +20 -18
  29. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +43 -41
  30. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +70 -67
  31. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +66 -65
  32. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +61 -57
  33. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +34 -32
  34. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +25 -23
  35. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +44 -43
  36. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +38 -36
  37. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +310 -309
  38. data/lib/grafana_reporter/asciidoctor/report.rb +177 -159
  39. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +37 -34
  40. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +39 -32
  41. data/lib/grafana_reporter/configuration.rb +257 -326
  42. data/lib/grafana_reporter/errors.rb +48 -38
  43. data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
  44. data/lib/ruby-grafana-reporter.rb +29 -27
  45. metadata +10 -23
@@ -1,61 +1,66 @@
1
- module Grafana
2
- # A top level alarm for all other errors in current module.
3
- class GrafanaError < StandardError
4
- def initialize(message)
5
- super("GrafanaError: #{message} (#{self.class})")
6
- end
7
- end
8
-
9
- # Raised if a given dashboard does not exist in a specific {Grafana} instance.
10
- class DashboardDoesNotExistError < GrafanaError
11
- # @param dashboard_uid [String] dashboard uid, which could not be found
12
- def initialize(dashboard_uid)
13
- super("The specified dashboard '#{dashboard_uid}' does not exist.")
14
- end
15
- end
16
-
17
- # Raised if a given panel does not exist on a specific {Dashboard} in the current {Grafana} instance.
18
- class PanelDoesNotExistError < GrafanaError
19
- # @param panel_id [String] panel id, which could not be found on the dashboard
20
- # @param dashboard [Dashboard] dashboard object on which the panel could not be found
21
- def initialize(panel_id, dashboard)
22
- super("The specified panel id '#{panel_id}' does not exist on the dashboard '#{dashboard.id}'.")
23
- end
24
- end
25
-
26
- # Raised if a given query letter does not exist on a specific {Panel}.
27
- class QueryLetterDoesNotExistError < GrafanaError
28
- # @param query_letter [String] query letter name, which could not be found on the panel
29
- # @param panel [Panel] panel object on which the query could not be found
30
- def initialize(query_letter, panel)
31
- super("The specified query '#{query_letter}' does not exist in the panel '#{panel.id}' in dashboard '#{panel.dashboard}'.")
32
- end
33
- end
34
-
35
- # Raised if a given datasource does not exist in a specific {Grafana} instance.
36
- class DatasourceDoesNotExistError < GrafanaError
37
- # @param field [String] specifies, how the datasource has been searched, e.g. 'id' or 'name'
38
- # @param datasource_identifier [String] identifier of the datasource, which could not be found, e.g. the specifiy id or name
39
- def initialize(field, datasource_identifier)
40
- super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
41
- end
42
- end
43
-
44
- # Raised if a {Panel} could not be rendered as an image.
45
- #
46
- # Most likely this happens, because the image renderer is not configures properly in grafana,
47
- # or the panel rendering ran into a timeout.
48
- class ImageCouldNotBeRenderedError < GrafanaError
49
- # @param panel [Panel] panel object, which could not be rendered
50
- def initialize(panel)
51
- super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be rendered to an image.")
52
- end
53
- end
54
-
55
- # Raised if no SQL query is specified in a {AbstractSqlQuery} object.
56
- class MissingSqlQueryError < GrafanaError
57
- def initialize
58
- super('No SQL statement has been specified.')
59
- end
60
- end
61
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # A top level alarm for all other errors in current module.
5
+ class GrafanaError < StandardError
6
+ def initialize(message)
7
+ super("GrafanaError: #{message} (#{self.class})")
8
+ end
9
+ end
10
+
11
+ # Raised if a given dashboard does not exist in a specific {Grafana} instance.
12
+ class DashboardDoesNotExistError < GrafanaError
13
+ # @param dashboard_uid [String] dashboard uid, which could not be found
14
+ def initialize(dashboard_uid)
15
+ super("The specified dashboard '#{dashboard_uid}' does not exist.")
16
+ end
17
+ end
18
+
19
+ # Raised if a given panel does not exist on a specific {Dashboard} in the current {Grafana} instance.
20
+ class PanelDoesNotExistError < GrafanaError
21
+ # @param panel_id [String] panel id, which could not be found on the dashboard
22
+ # @param dashboard [Dashboard] dashboard object on which the panel could not be found
23
+ def initialize(panel_id, dashboard)
24
+ super("The specified panel id '#{panel_id}' does not exist on the dashboard '#{dashboard.id}'.")
25
+ end
26
+ end
27
+
28
+ # Raised if a given query letter does not exist on a specific {Panel}.
29
+ class QueryLetterDoesNotExistError < GrafanaError
30
+ # @param query_letter [String] query letter name, which could not be found on the panel
31
+ # @param panel [Panel] panel object on which the query could not be found
32
+ def initialize(query_letter, panel)
33
+ super("The specified query '#{query_letter}' does not exist in the panel '#{panel.id}' "\
34
+ "in dashboard '#{panel.dashboard}'.")
35
+ end
36
+ end
37
+
38
+ # Raised if a given datasource does not exist in a specific {Grafana} instance.
39
+ class DatasourceDoesNotExistError < GrafanaError
40
+ # @param field [String] specifies, how the datasource has been searched, e.g. 'id' or 'name'
41
+ # @param datasource_identifier [String] identifier of the datasource, which could not be found,
42
+ # e.g. the specifiy id or name
43
+ def initialize(field, datasource_identifier)
44
+ super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
45
+ end
46
+ end
47
+
48
+ # Raised if a {Panel} could not be rendered as an image.
49
+ #
50
+ # Most likely this happens, because the image renderer is not configures properly in grafana,
51
+ # or the panel rendering ran into a timeout.
52
+ class ImageCouldNotBeRenderedError < GrafanaError
53
+ # @param panel [Panel] panel object, which could not be rendered
54
+ def initialize(panel)
55
+ super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be "\
56
+ 'rendered to an image.')
57
+ end
58
+ end
59
+
60
+ # Raised if no SQL query is specified in a {AbstractSqlQuery} object.
61
+ class MissingSqlQueryError < GrafanaError
62
+ def initialize
63
+ super('No SQL statement has been specified.')
64
+ end
65
+ end
66
+ end
@@ -1,131 +1,130 @@
1
- # Contains all objects for creating structured objects for interfacing grafana.
2
- #
3
- # The intention is, that these represent the business logic contained within grafana in an appropriate object model for the reporter to work with.
4
- #
5
- # For details, see also {https://grafana.com/docs/grafana/latest/http_api Grafana API}.
6
- module Grafana
7
- # Main class for handling the interaction with one specific Grafana instance.
8
- class Grafana
9
- # @param base_uri [String] full URI pointing to the specific grafana instance without trailing slash, e.g. +https://localhost:3000+.
10
- # @param key [String] API key for the grafana instance, if required
11
- # @param opts [Hash] additional options.
12
- # Currently supporting +:logger+ and +:datasources+.
13
- # +:datasources+ need to be an Hash with datasource name as key and datasource id as value.
14
- # If not specified, the datasources will be queried from the grafana interface.
15
- # Specifying +:datasources+> here can be used, so that the interface can be used without grafana Admin privileges.
16
- def initialize(base_uri, key = nil, opts = {})
17
- @base_uri = base_uri
18
- @key = key
19
- @dashboards = {}
20
- @logger = opts[:logger] || ::Logger.new(nil)
21
- @datasources = opts[:datasources]
22
- end
23
-
24
- # Used to test a connection to the grafana instance.
25
- #
26
- # Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
27
- # or even fails on connecting to grafana.
28
- #
29
- # @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
30
- def test_connection
31
- res = execute_http_request('/api/datasources')
32
- if res.is_a?(Net::HTTPOK)
33
- # we have admin rights
34
- @logger.info('Reporter is running with Admin privileges on grafana.')
35
- return 'Admin'
36
- else
37
- # check if we have lower rights
38
- res = execute_http_request('/api/dashboards/home')
39
- if res.is_a?(Net::HTTPOK)
40
- @logger.warn('Reporter is running with NON-Admin privileges on grafana. Make sure that necessary datasources are specified in CONFIG_FILE, otherwise operation will fail')
41
- return 'NON-Admin'
42
- end
43
- end
44
-
45
- 'Failed'
46
- end
47
-
48
- # Returns the ID of a datasource, which has been queried by the datasource name.
49
- #
50
- # @return [Integer] ID for the specified datasource name
51
- def datasource_id(datasource_name)
52
- id = datasources[datasource_name]
53
- raise DatasourceDoesNotExistError.new('name', datasource_name) unless id
54
-
55
- id
56
- end
57
-
58
- # Returns if the given datasource ID exists for the grafana instance.
59
- #
60
- # @return [Boolean] true if exists, false otherwise
61
- def datasource_id_exists?(datasource_id)
62
- datasources.value?(datasource_id)
63
- end
64
-
65
- # @param dashboard_uid [String] UID of the searched {Dashboard}
66
- # @return [Dashboard] dashboard object, if it has been found
67
- def dashboard(dashboard_uid)
68
- return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
69
-
70
- response = execute_http_request('/api/dashboards/uid/' + dashboard_uid)
71
- model = JSON.parse(response.body)['dashboard']
72
-
73
- raise DashboardDoesNotExistError, dashboard_uid if model.nil?
74
-
75
- # cache dashboard for reuse
76
- @dashboards[dashboard_uid] = Dashboard.new(model, self)
77
-
78
- @dashboards[dashboard_uid]
79
- end
80
-
81
- # Runs a specific HTTP request against the current grafana instance.
82
- #
83
- # Default (can be overridden, by specifying the options Hash):
84
- # accept: 'application/json'
85
- # request: Net::HTTP::Get
86
- # content_type: 'application/json'
87
- #
88
- # @param relative_uri [String] relative URL with a leading slash, which shall be queried
89
- # @param options [Hash] options, which shall be merged to the request.
90
- # @param timeout [Integer] number of seconds to wait, before the http request is cancelled, defaults to 60 seconds
91
- def execute_http_request(relative_uri, options = {}, timeout = nil)
92
- uri = URI.parse(@base_uri + relative_uri)
93
- default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
94
- options = default_options.merge(options)
95
-
96
- http = Net::HTTP.new(uri.host, uri.port)
97
- if @base_uri =~ /^https/
98
- http.use_ssl = true
99
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
100
- end
101
- http.read_timeout = timeout.to_i if timeout
102
-
103
- request = options[:request].new(uri.request_uri)
104
- request['Accept'] = options[:accept]
105
- request['Content-Type'] = options[:content_type]
106
- request['Authorization'] = 'Bearer ' + @key unless @key.nil?
107
- request.body = options[:body]
108
-
109
- @logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
110
- resp = http.request(request)
111
- resp
112
- end
113
-
114
- private
115
-
116
- def datasources
117
- if @datasources.nil?
118
- # load datasources from grafana directly, if allowed
119
- response = execute_http_request('/api/datasources')
120
- if response['message'].nil?
121
- json = JSON.parse(response.body)
122
- # only store needed values
123
- @datasources = json.map { |item| [item['name'], item['id']] }.to_h
124
- else
125
- @datasources = {}
126
- end
127
- end
128
- @datasources
129
- end
130
- end
131
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Contains all objects for creating structured objects for interfacing grafana.
4
+ #
5
+ # The intention is, that these represent the business logic contained within grafana
6
+ # in an appropriate object model for the reporter to work with.
7
+ #
8
+ # For details, see also {https://grafana.com/docs/grafana/latest/http_api Grafana API}.
9
+ module Grafana
10
+ # Main class for handling the interaction with one specific Grafana instance.
11
+ class Grafana
12
+ # @param base_uri [String] full URI pointing to the specific grafana instance without
13
+ # trailing slash, e.g. +https://localhost:3000+.
14
+ # @param key [String] API key for the grafana instance, if required
15
+ # @param opts [Hash] additional options.
16
+ # Currently supporting +:logger+ and +:datasources+.
17
+ # +:datasources+ need to be an Hash with datasource name as key and datasource id as value.
18
+ # If not specified, the datasources will be queried from the grafana interface.
19
+ # Specifying +:datasources+> here can be used, so that the interface can be used without grafana Admin privileges.
20
+ def initialize(base_uri, key = nil, opts = {})
21
+ @base_uri = base_uri
22
+ @key = key
23
+ @dashboards = {}
24
+ @logger = opts[:logger] || ::Logger.new(nil)
25
+ @datasources = opts[:datasources]
26
+ end
27
+
28
+ # Used to test a connection to the grafana instance.
29
+ #
30
+ # Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
31
+ # or even fails on connecting to grafana.
32
+ #
33
+ # @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
34
+ def test_connection
35
+ if execute_http_request('/api/datasources').is_a?(Net::HTTPOK)
36
+ # we have admin rights
37
+ @logger.info('Reporter is running with Admin privileges on grafana.')
38
+ return 'Admin'
39
+ end
40
+ # check if we have lower rights
41
+ return 'Failed' unless execute_http_request('/api/dashboards/home').is_a?(Net::HTTPOK)
42
+
43
+ @logger.warn('Reporter is running with NON-Admin privileges on grafana. Make sure that necessary'\
44
+ 'datasources are specified in CONFIG_FILE, otherwise operation will fail')
45
+ 'NON-Admin'
46
+ end
47
+
48
+ # Returns the ID of a datasource, which has been queried by the datasource name.
49
+ #
50
+ # @return [Integer] ID for the specified datasource name
51
+ def datasource_id(datasource_name)
52
+ id = datasources[datasource_name]
53
+ raise DatasourceDoesNotExistError.new('name', datasource_name) unless id
54
+
55
+ id
56
+ end
57
+
58
+ # Returns if the given datasource ID exists for the grafana instance.
59
+ #
60
+ # @return [Boolean] true if exists, false otherwise
61
+ def datasource_id_exists?(datasource_id)
62
+ datasources.value?(datasource_id)
63
+ end
64
+
65
+ # @param dashboard_uid [String] UID of the searched {Dashboard}
66
+ # @return [Dashboard] dashboard object, if it has been found
67
+ def dashboard(dashboard_uid)
68
+ return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
69
+
70
+ response = execute_http_request("/api/dashboards/uid/#{dashboard_uid}")
71
+ model = JSON.parse(response.body)['dashboard']
72
+
73
+ raise DashboardDoesNotExistError, dashboard_uid if model.nil?
74
+
75
+ # cache dashboard for reuse
76
+ @dashboards[dashboard_uid] = Dashboard.new(model, self)
77
+
78
+ @dashboards[dashboard_uid]
79
+ end
80
+
81
+ # Runs a specific HTTP request against the current grafana instance.
82
+ #
83
+ # Default (can be overridden, by specifying the options Hash):
84
+ # accept: 'application/json'
85
+ # request: Net::HTTP::Get
86
+ # content_type: 'application/json'
87
+ #
88
+ # @param relative_uri [String] relative URL with a leading slash, which shall be queried
89
+ # @param options [Hash] options, which shall be merged to the request.
90
+ # @param timeout [Integer] number of seconds to wait, before the http request is cancelled, defaults to 60 seconds
91
+ def execute_http_request(relative_uri, options = {}, timeout = nil)
92
+ uri = URI.parse(@base_uri + relative_uri)
93
+ default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
94
+ options = default_options.merge(options)
95
+
96
+ http = Net::HTTP.new(uri.host, uri.port)
97
+ if @base_uri =~ /^https/
98
+ http.use_ssl = true
99
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
100
+ end
101
+ http.read_timeout = timeout.to_i if timeout
102
+
103
+ request = options[:request].new(uri.request_uri)
104
+ request['Accept'] = options[:accept]
105
+ request['Content-Type'] = options[:content_type]
106
+ request['Authorization'] = "Bearer #{@key}" unless @key.nil?
107
+ request.body = options[:body]
108
+
109
+ @logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
110
+ http.request(request)
111
+ end
112
+
113
+ private
114
+
115
+ def datasources
116
+ if @datasources.nil?
117
+ # load datasources from grafana directly, if allowed
118
+ response = execute_http_request('/api/datasources')
119
+ if response['message'].nil?
120
+ json = JSON.parse(response.body)
121
+ # only store needed values
122
+ @datasources = json.map { |item| [item['name'], item['id']] }.to_h
123
+ else
124
+ @datasources = {}
125
+ end
126
+ end
127
+ @datasources
128
+ end
129
+ end
130
+ end
@@ -1,39 +1,41 @@
1
- module Grafana
2
- # Representation of one specific panel in a {Dashboard} instance.
3
- class Panel
4
- # @return [Dashboard] parent {Dashboard} object
5
- attr_reader :dashboard
6
-
7
- # @param model [Hash] converted JSON Hash of the panel
8
- # @param dashboard [Dashboard] parent {Dashboard} object
9
- def initialize(model, dashboard)
10
- @model = model
11
- @dashboard = dashboard
12
- end
13
-
14
- # @return [String] content of the requested field or +''+ if not found
15
- def field(field)
16
- return @model[field] if @model.key?(field)
17
-
18
- ''
19
- end
20
-
21
- # @return [String] panel ID
22
- def id
23
- @model['id']
24
- end
25
-
26
- # @return [String] SQL query string for the requested query letter
27
- def query(query_letter)
28
- query_item = @model['targets'].select { |item| item['refId'].to_s == query_letter.to_s }.first
29
- raise QueryLetterDoesNotExistError.new(query_letter, self) if query_item.nil?
30
-
31
- query_item['rawSql']
32
- end
33
-
34
- # @return [String] relative rendering URL for the panel, to create an image out of it
35
- def render_url
36
- "/render/d-solo/#{@dashboard.id}?panelId=#{@model['id']}"
37
- end
38
- end
39
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # Representation of one specific panel in a {Dashboard} instance.
5
+ class Panel
6
+ # @return [Dashboard] parent {Dashboard} object
7
+ attr_reader :dashboard
8
+
9
+ # @param model [Hash] converted JSON Hash of the panel
10
+ # @param dashboard [Dashboard] parent {Dashboard} object
11
+ def initialize(model, dashboard)
12
+ @model = model
13
+ @dashboard = dashboard
14
+ end
15
+
16
+ # @return [String] content of the requested field or +''+ if not found
17
+ def field(field)
18
+ return @model[field] if @model.key?(field)
19
+
20
+ ''
21
+ end
22
+
23
+ # @return [String] panel ID
24
+ def id
25
+ @model['id']
26
+ end
27
+
28
+ # @return [String] SQL query string for the requested query letter
29
+ def query(query_letter)
30
+ query_item = @model['targets'].select { |item| item['refId'].to_s == query_letter.to_s }.first
31
+ raise QueryLetterDoesNotExistError.new(query_letter, self) if query_item.nil?
32
+
33
+ query_item['rawSql']
34
+ end
35
+
36
+ # @return [String] relative rendering URL for the panel, to create an image out of it
37
+ def render_url
38
+ "/render/d-solo/#{@dashboard.id}?panelId=#{@model['id']}"
39
+ end
40
+ end
41
+ end