ruby-grafana-reporter 0.1.6 → 0.3.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -0
  3. data/README.md +95 -173
  4. data/bin/ruby-grafana-reporter +5 -0
  5. data/lib/VERSION.rb +5 -3
  6. data/lib/grafana/abstract_panel_query.rb +22 -20
  7. data/lib/grafana/abstract_query.rb +132 -127
  8. data/lib/grafana/abstract_sql_query.rb +51 -42
  9. data/lib/grafana/dashboard.rb +77 -66
  10. data/lib/grafana/errors.rb +66 -61
  11. data/lib/grafana/grafana.rb +133 -131
  12. data/lib/grafana/panel.rb +41 -39
  13. data/lib/grafana/panel_image_query.rb +52 -49
  14. data/lib/grafana/variable.rb +217 -259
  15. data/lib/grafana_reporter/abstract_report.rb +112 -109
  16. data/lib/grafana_reporter/application/application.rb +158 -229
  17. data/lib/grafana_reporter/application/errors.rb +33 -30
  18. data/lib/grafana_reporter/application/webservice.rb +230 -0
  19. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +101 -99
  20. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +96 -96
  21. data/lib/grafana_reporter/asciidoctor/errors.rb +40 -37
  22. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +92 -86
  23. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +91 -86
  24. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +69 -67
  25. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +68 -65
  26. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +61 -58
  27. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +78 -75
  28. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +73 -70
  29. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +20 -18
  30. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +43 -41
  31. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +30 -202
  32. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +70 -67
  33. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +66 -65
  34. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +88 -57
  35. data/lib/grafana_reporter/asciidoctor/help.rb +435 -0
  36. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +36 -32
  37. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +28 -23
  38. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +44 -43
  39. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +40 -36
  40. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +312 -309
  41. data/lib/grafana_reporter/asciidoctor/report.rb +179 -159
  42. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +42 -34
  43. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +44 -32
  44. data/lib/grafana_reporter/configuration.rb +304 -326
  45. data/lib/grafana_reporter/console_configuration_wizard.rb +269 -0
  46. data/lib/grafana_reporter/errors.rb +48 -38
  47. data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
  48. data/lib/ruby-grafana-reporter.rb +32 -27
  49. metadata +116 -16
@@ -1,42 +1,51 @@
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
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # @abstract
5
+ #
6
+ # Used as a superclass for all queries, which execute SQL queries against {Grafana}.
7
+ #
8
+ # @see AbstractQuery
9
+ class AbstractSqlQuery < AbstractQuery
10
+ attr_reader :sql, :datasource_id
11
+
12
+ # @param raw_sql [String] raw sql statement, as it can be sent to a SQL database
13
+ # @param datasource_id [Integer] ID of the datasource against which the query is run
14
+ def initialize(raw_sql, datasource_id)
15
+ super()
16
+ @sql = raw_sql
17
+ @datasource_id = datasource_id
18
+ end
19
+
20
+ # @return [String] relative URL, where the request has to be sent to.
21
+ def url
22
+ '/api/tsdb/query'
23
+ end
24
+
25
+ # @return [Hash] request, which executes the SQL statement against the specified datasource
26
+ def request
27
+ {
28
+ body: {
29
+ from: @from,
30
+ to: @to,
31
+ queries: [rawSql: @sql, datasourceId: @datasource_id.to_i, format: 'table']
32
+ }.to_json,
33
+ request: Net::HTTP::Post
34
+ }
35
+ end
36
+
37
+ # Replaces all variables in the SQL statement.
38
+ def pre_process(grafana)
39
+ raise MissingSqlQueryError if @sql.nil?
40
+ unless grafana.datasource_id_exists?(@datasource_id.to_i)
41
+ raise DatasourceDoesNotExistError.new('id', @datasource_id)
42
+ end
43
+
44
+ @sql = replace_variables(@sql, grafana_variables)
45
+ # remove comments in query
46
+ @sql.gsub!(/--[^\r\n]*(?:[\r\n]+|$)/, ' ')
47
+ @sql.gsub!(/\r\n/, ' ')
48
+ @sql.gsub!(/\n/, ' ')
49
+ end
50
+ end
51
+ end
@@ -1,66 +1,77 @@
1
- module Grafana
2
- # Representation of one specific dashboard in a {Grafana} instance.
3
- class Dashboard
4
- # @return [Grafana] parent {Grafana} object
5
- attr_reader :grafana
6
- attr_reader :panels, :variables
7
-
8
- # @param model [Hash] converted JSON Hash of the grafana dashboard
9
- # @param grafana [Grafana] parent {Grafana} object
10
- def initialize(model, grafana)
11
- @grafana = grafana
12
- @model = model
13
-
14
- # read panels
15
- @panels = []
16
- if @model.key?('panels')
17
- @model['panels'].each do |panel|
18
- if panel.key?('panels')
19
- panel['panels'].each do |subpanel|
20
- @panels << Panel.new(subpanel, self)
21
- end
22
- else
23
- @panels << Panel.new(panel, self)
24
- end
25
- end
26
- end
27
-
28
- # store variables in array as objects of type Variable
29
- @variables = []
30
- return unless @model.key?('templating')
31
-
32
- list = @model['templating']['list']
33
- return unless list.is_a? Array
34
-
35
- list.each do |item|
36
- @variables << Variable.new(item)
37
- end
38
- end
39
-
40
- # @return [String] +from+ time configured in the dashboard.
41
- def from_time
42
- return @model['time']['from'] if @model['time']
43
-
44
- nil
45
- end
46
-
47
- # @return [String] +to+ time configured in the dashboard.
48
- def to_time
49
- @model['time']['to'] if @model['time']
50
- nil
51
- end
52
-
53
- # @return [String] dashboard UID
54
- def id
55
- @model['uid']
56
- end
57
-
58
- # @return [Panel] panel for the specified ID
59
- def panel(id)
60
- panels = @panels.select { |item| item.field('id') == id.to_i }
61
- raise PanelDoesNotExistError.new(id, self) if panels.empty?
62
-
63
- panels.first
64
- end
65
- end
66
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # Representation of one specific dashboard in a {Grafana} instance.
5
+ class Dashboard
6
+ # @return [Grafana] parent {Grafana} object
7
+ attr_reader :grafana
8
+ attr_reader :panels, :variables
9
+
10
+ # @param model [Hash] converted JSON Hash of the grafana dashboard
11
+ # @param grafana [Grafana] parent {Grafana} object
12
+ def initialize(model, grafana)
13
+ @grafana = grafana
14
+ @model = model
15
+
16
+ init_panels
17
+ init_variables
18
+ end
19
+
20
+ # @return [String] +from+ time configured in the dashboard.
21
+ def from_time
22
+ return @model['time']['from'] if @model['time']
23
+
24
+ nil
25
+ end
26
+
27
+ # @return [String] +to+ time configured in the dashboard.
28
+ def to_time
29
+ @model['time']['to'] if @model['time']
30
+ nil
31
+ end
32
+
33
+ # @return [String] dashboard UID
34
+ def id
35
+ @model['uid']
36
+ end
37
+
38
+ # @return [Panel] panel for the specified ID
39
+ def panel(id)
40
+ panels = @panels.select { |item| item.field('id') == id.to_i }
41
+ raise PanelDoesNotExistError.new(id, self) if panels.empty?
42
+
43
+ panels.first
44
+ end
45
+
46
+ private
47
+
48
+ # store variables in array as objects of type Variable
49
+ def init_variables
50
+ @variables = []
51
+ return unless @model.key?('templating')
52
+
53
+ list = @model['templating']['list']
54
+ return unless list.is_a? Array
55
+
56
+ list.each do |item|
57
+ @variables << Variable.new(item)
58
+ end
59
+ end
60
+
61
+ # read panels
62
+ def init_panels
63
+ @panels = []
64
+ return unless @model.key?('panels')
65
+
66
+ @model['panels'].each do |panel|
67
+ if panel.key?('panels')
68
+ panel['panels'].each do |subpanel|
69
+ @panels << Panel.new(subpanel, self)
70
+ end
71
+ else
72
+ @panels << Panel.new(panel, self)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -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,133 @@
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 +:ssl_cert+.
17
+ def initialize(base_uri, key = nil, opts = {})
18
+ @base_uri = base_uri
19
+ @key = key
20
+ @dashboards = {}
21
+ @ssl_cert = opts[:ssl_cert]
22
+ @logger = opts[:logger] || ::Logger.new(nil)
23
+
24
+ initialize_datasources unless @base_uri.empty?
25
+ end
26
+
27
+ # Used to test a connection to the grafana instance.
28
+ #
29
+ # Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
30
+ # or even fails on connecting to grafana.
31
+ #
32
+ # @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
33
+ def test_connection
34
+ if execute_http_request('/api/datasources').is_a?(Net::HTTPOK)
35
+ # we have admin rights
36
+ @logger.warn('Reporter is running with Admin privileges on grafana. This is a potential security risk.')
37
+ return 'Admin'
38
+ end
39
+ # check if we have lower rights
40
+ return 'Failed' unless execute_http_request('/api/dashboards/home').is_a?(Net::HTTPOK)
41
+
42
+ @logger.info('Reporter is running with NON-Admin privileges on grafana.')
43
+ 'NON-Admin'
44
+ end
45
+
46
+ # Returns the ID of a datasource, which has been queried by the datasource name.
47
+ #
48
+ # @return [Integer] ID for the specified datasource name
49
+ def datasource_id(datasource_name)
50
+ datasource_name ||= 'default'
51
+ return @datasources[datasource_name] if @datasources[datasource_name]
52
+
53
+ raise DatasourceDoesNotExistError.new('name', datasource_name)
54
+ end
55
+
56
+ # Returns if the given datasource ID exists for the grafana instance.
57
+ #
58
+ # @return [Boolean] true if exists, false otherwise
59
+ def datasource_id_exists?(datasource_id)
60
+ @datasources.value?(datasource_id)
61
+ end
62
+
63
+ # @param dashboard_uid [String] UID of the searched {Dashboard}
64
+ # @return [Dashboard] dashboard object, if it has been found
65
+ def dashboard(dashboard_uid)
66
+ return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
67
+
68
+ response = execute_http_request("/api/dashboards/uid/#{dashboard_uid}")
69
+ model = JSON.parse(response.body)['dashboard']
70
+
71
+ raise DashboardDoesNotExistError, dashboard_uid if model.nil?
72
+
73
+ # cache dashboard for reuse
74
+ @dashboards[dashboard_uid] = Dashboard.new(model, self)
75
+
76
+ @dashboards[dashboard_uid]
77
+ end
78
+
79
+ # Runs a specific HTTP request against the current grafana instance.
80
+ #
81
+ # Default (can be overridden, by specifying the options Hash):
82
+ # accept: 'application/json'
83
+ # request: Net::HTTP::Get
84
+ # content_type: 'application/json'
85
+ #
86
+ # @param relative_uri [String] relative URL with a leading slash, which shall be queried
87
+ # @param options [Hash] options, which shall be merged to the request.
88
+ # @param timeout [Integer] number of seconds to wait, before the http request is cancelled, defaults to 60 seconds
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)
116
+ end
117
+
118
+ private
119
+
120
+ def initialize_datasources
121
+ @datasources = {}
122
+
123
+ settings = execute_http_request('/api/frontend/settings')
124
+ return unless settings.is_a?(Net::HTTPOK)
125
+
126
+ json = JSON.parse(settings.body)
127
+ json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
128
+ @datasources[ds_name] = ds_value['id'].to_i
129
+ end
130
+ @datasources['default'] = @datasources[json['defaultDatasource']]
131
+ end
132
+ end
133
+ end