ruby-grafana-reporter 0.3.0 → 0.4.4
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 +337 -170
- data/bin/ruby-grafana-reporter +5 -5
- data/lib/VERSION.rb +3 -2
- data/lib/grafana/abstract_datasource.rb +149 -0
- data/lib/grafana/dashboard.rb +1 -3
- data/lib/grafana/errors.rb +20 -5
- data/lib/grafana/grafana.rb +52 -57
- 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 +37 -0
- data/lib/grafana/graphite_datasource.rb +72 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/influxdb_datasource.rb +70 -0
- data/lib/grafana/panel.rb +10 -4
- data/lib/grafana/prometheus_datasource.rb +67 -0
- data/lib/grafana/sql_datasource.rb +70 -0
- data/lib/grafana/unsupported_datasource.rb +7 -0
- data/lib/grafana/variable.rb +27 -21
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +478 -0
- data/lib/grafana_reporter/abstract_report.rb +152 -18
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
- data/lib/grafana_reporter/alerts_table_query.rb +43 -0
- data/lib/grafana_reporter/annotations_table_query.rb +42 -0
- data/lib/grafana_reporter/application/application.rb +28 -25
- data/lib/grafana_reporter/application/webservice.rb +80 -39
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +92 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +91 -0
- data/lib/grafana_reporter/asciidoctor/help.rb +336 -313
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +78 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +80 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +74 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +99 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +50 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +41 -82
- 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 +94 -0
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
- data/lib/grafana_reporter/configuration.rb +26 -8
- data/lib/grafana_reporter/console_configuration_wizard.rb +109 -67
- data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +104 -0
- data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
- data/lib/grafana_reporter/erb/report.rb +36 -0
- data/lib/grafana_reporter/erb/report_jail.rb +21 -0
- data/lib/grafana_reporter/errors.rb +57 -0
- data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
- data/lib/grafana_reporter/panel_image_query.rb +25 -0
- data/lib/grafana_reporter/panel_property_query.rb +22 -0
- data/lib/grafana_reporter/query_value_query.rb +61 -0
- data/lib/grafana_reporter/report_webhook.rb +39 -0
- data/lib/ruby_grafana_extension.rb +8 -0
- data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +1 -3
- metadata +49 -38
- 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/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
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,149 @@
|
|
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
|
+
@@subclasses = []
|
10
|
+
|
11
|
+
# Registers the subclass as datasource.
|
12
|
+
# @param subclass [Class] class inheriting from this abstract class
|
13
|
+
def self.inherited(subclass)
|
14
|
+
@@subclasses << subclass
|
15
|
+
end
|
16
|
+
|
17
|
+
# Overwrite this method, to specify if the current datasource implementation handles the given model.
|
18
|
+
# This method is called by {build_instance} to determine, if the current datasource implementation
|
19
|
+
# can handle the given grafana model. By default this method returns false.
|
20
|
+
# @param model [Hash] grafana specification of the datasource to check
|
21
|
+
# @return [Boolean] True if fits, false otherwise
|
22
|
+
def self.handles?(model)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Factory method to build a datasource from a given datasource Hash description.
|
27
|
+
# @param ds_model [Hash] grafana specification of a single datasource
|
28
|
+
# @return [AbstractDatasource] instance of a fitting datasource implementation
|
29
|
+
def self.build_instance(ds_model)
|
30
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model.is_a?(Hash)
|
31
|
+
|
32
|
+
raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta'].is_a?(Hash)
|
33
|
+
|
34
|
+
@@subclasses.each do |datasource_class|
|
35
|
+
return datasource_class.new(ds_model) if datasource_class.handles?(ds_model)
|
36
|
+
end
|
37
|
+
|
38
|
+
UnsupportedDatasource.new(ds_model)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(model)
|
42
|
+
@model = model
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String] category of the datasource, e.g. +tsdb+ or +sql+
|
46
|
+
def category
|
47
|
+
@model['meta']['category']
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String] type of the datasource, e.g. +mysql+
|
51
|
+
def type
|
52
|
+
@model['type'] || @model['meta']['id']
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String] name of the datasource
|
56
|
+
def name
|
57
|
+
@model['name']
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Integer] ID of the datasource
|
61
|
+
def id
|
62
|
+
@model['id'].to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
# @abstract
|
66
|
+
#
|
67
|
+
# Executes a request for the current database with the given options.
|
68
|
+
#
|
69
|
+
# Used format of the response will always be the following:
|
70
|
+
#
|
71
|
+
# {
|
72
|
+
# :header => [column_title_1, column_title_2],
|
73
|
+
# :content => [
|
74
|
+
# [row_1_column_1, row_1_column_2],
|
75
|
+
# [row_2_column_1, row_2_column_2]
|
76
|
+
# ]
|
77
|
+
# }
|
78
|
+
#
|
79
|
+
# @param query_description [Hash] query description, which will requested:
|
80
|
+
# @option query_description [String] :from +from+ timestamp
|
81
|
+
# @option query_description [String] :to +to+ timestamp
|
82
|
+
# @option query_description [Integer] :timeout expected timeout for the request
|
83
|
+
# @option query_description [WebRequest] :prepared_request prepared web request for relevant {Grafana} instance, if this is needed by datasource
|
84
|
+
# @option query_description [String] :raw_query raw query, which shall be executed. May include variables, which will be replaced before execution
|
85
|
+
# @option query_description [Hash<Variable>] :variables hash of variables, which can potentially be replaced in the given +:raw_query+
|
86
|
+
# @return [Hash] sql result formatted as stated above
|
87
|
+
def request(query_description)
|
88
|
+
raise NotImplementedError
|
89
|
+
end
|
90
|
+
|
91
|
+
# @abstract
|
92
|
+
#
|
93
|
+
# The different datasources supported by grafana use different ways to store the query in the
|
94
|
+
# panel's JSON model. This method extracts a query from that description, that can be used
|
95
|
+
# by the {AbstractDatasource} implementation of the datasource.
|
96
|
+
#
|
97
|
+
# @param panel_query_target [Hash] grafana panel target, which contains the query description
|
98
|
+
# @return [String] query string, which can be used as +raw_query+ in a {#request}
|
99
|
+
def raw_query_from_panel_model(panel_query_target)
|
100
|
+
raise NotImplementedError
|
101
|
+
end
|
102
|
+
|
103
|
+
# @abstract
|
104
|
+
#
|
105
|
+
# Overwrite in subclass, to specify the default variable format during replacement of variables.
|
106
|
+
# @return [String] default {Variable#value_formatted} format
|
107
|
+
def default_variable_format
|
108
|
+
raise NotImplementedError
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Replaces the grafana variables in the given string with their replacement value.
|
114
|
+
#
|
115
|
+
# @param string [String] string in which the variables shall be replaced
|
116
|
+
# @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the
|
117
|
+
# given string
|
118
|
+
# @return [String] string in which all variables are properly replaced
|
119
|
+
def replace_variables(string, variables = {})
|
120
|
+
res = string
|
121
|
+
repeat = true
|
122
|
+
repeat_count = 0
|
123
|
+
|
124
|
+
# TODO: find a proper way to replace variables recursively instead of over and over again
|
125
|
+
# TODO: add tests for recursive replacement of variable
|
126
|
+
while repeat && (repeat_count < 3)
|
127
|
+
repeat = false
|
128
|
+
repeat_count += 1
|
129
|
+
|
130
|
+
variables.each do |name, variable|
|
131
|
+
# only set ticks if value is string
|
132
|
+
var_name = name.gsub(/^var-/, '')
|
133
|
+
next unless var_name =~ /^\w+$/
|
134
|
+
|
135
|
+
res = res.gsub(/(?:\$\{#{var_name}(?::(?<format>\w+))?\}|\$#{var_name}(?!\w))/) do
|
136
|
+
format = default_variable_format
|
137
|
+
if $LAST_MATCH_INFO
|
138
|
+
format = $LAST_MATCH_INFO[:format] if $LAST_MATCH_INFO[:format]
|
139
|
+
end
|
140
|
+
variable.value_formatted(format)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
repeat = true if res.include?('$')
|
144
|
+
end
|
145
|
+
|
146
|
+
res
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/grafana/dashboard.rb
CHANGED
data/lib/grafana/errors.rb
CHANGED
@@ -37,9 +37,9 @@ module Grafana
|
|
37
37
|
|
38
38
|
# Raised if a given datasource does not exist in a specific {Grafana} instance.
|
39
39
|
class DatasourceDoesNotExistError < GrafanaError
|
40
|
-
# @param field [String] specifies, how the datasource has been searched, e.g.
|
40
|
+
# @param field [String] specifies, how the datasource has been searched, e.g. +id+ or +name+
|
41
41
|
# @param datasource_identifier [String] identifier of the datasource, which could not be found,
|
42
|
-
# e.g. the
|
42
|
+
# e.g. the specified id or name
|
43
43
|
def initialize(field, datasource_identifier)
|
44
44
|
super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
|
45
45
|
end
|
@@ -49,18 +49,33 @@ module Grafana
|
|
49
49
|
#
|
50
50
|
# Most likely this happens, because the image renderer is not configures properly in grafana,
|
51
51
|
# or the panel rendering ran into a timeout.
|
52
|
+
# @param panel [Panel] panel object, which could not be rendered
|
52
53
|
class ImageCouldNotBeRenderedError < GrafanaError
|
53
|
-
# @param panel [Panel] panel object, which could not be rendered
|
54
54
|
def initialize(panel)
|
55
|
-
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be "\
|
55
|
+
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id}' could not be "\
|
56
56
|
'rendered to an image.')
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
# Raised if no SQL query is specified
|
60
|
+
# Raised if no SQL query is specified.
|
61
61
|
class MissingSqlQueryError < GrafanaError
|
62
62
|
def initialize
|
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 InvalidDatasourceQueryProvidedError < GrafanaError
|
69
|
+
def initialize(query)
|
70
|
+
super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Raised if a datasource implementation cannot handle a query, which is composed
|
75
|
+
# in the grafana visual editor.
|
76
|
+
class ComposedQueryNotSupportedError < GrafanaError
|
77
|
+
def initialize(class_obj)
|
78
|
+
super("Composed queries are not yet supported for datasource '#{class_obj}'.")
|
79
|
+
end
|
80
|
+
end
|
66
81
|
end
|
data/lib/grafana/grafana.rb
CHANGED
@@ -9,16 +9,17 @@
|
|
9
9
|
module Grafana
|
10
10
|
# Main class for handling the interaction with one specific Grafana instance.
|
11
11
|
class Grafana
|
12
|
+
attr_reader :logger
|
13
|
+
|
12
14
|
# @param base_uri [String] full URI pointing to the specific grafana instance without
|
13
15
|
# trailing slash, e.g. +https://localhost:3000+.
|
14
16
|
# @param key [String] API key for the grafana instance, if required
|
15
17
|
# @param opts [Hash] additional options.
|
16
|
-
# Currently supporting +:logger
|
18
|
+
# Currently supporting +:logger+.
|
17
19
|
def initialize(base_uri, key = nil, opts = {})
|
18
20
|
@base_uri = base_uri
|
19
21
|
@key = key
|
20
22
|
@dashboards = {}
|
21
|
-
@ssl_cert = opts[:ssl_cert]
|
22
23
|
@logger = opts[:logger] || ::Logger.new(nil)
|
23
24
|
|
24
25
|
initialize_datasources unless @base_uri.empty?
|
@@ -31,33 +32,54 @@ module Grafana
|
|
31
32
|
#
|
32
33
|
# @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
|
33
34
|
def test_connection
|
34
|
-
if
|
35
|
+
if prepare_request({ relative_url: '/api/datasources' }).execute.is_a?(Net::HTTPOK)
|
35
36
|
# we have admin rights
|
36
37
|
@logger.warn('Reporter is running with Admin privileges on grafana. This is a potential security risk.')
|
37
38
|
return 'Admin'
|
38
39
|
end
|
39
40
|
# check if we have lower rights
|
40
|
-
return 'Failed' unless
|
41
|
+
return 'Failed' unless prepare_request({ relative_url: '/api/dashboards/home' }).execute.is_a?(Net::HTTPOK)
|
41
42
|
|
42
43
|
@logger.info('Reporter is running with NON-Admin privileges on grafana.')
|
43
44
|
'NON-Admin'
|
44
45
|
end
|
45
46
|
|
46
|
-
# Returns the
|
47
|
+
# Returns the datasource, which has been queried by the datasource name.
|
47
48
|
#
|
48
|
-
# @
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
# @param datasource_name [String] name of the searched datasource
|
50
|
+
# @return [Datasource] Datasource for the specified datasource name
|
51
|
+
def datasource_by_name(datasource_name)
|
52
|
+
datasource_name = 'default' if datasource_name.to_s.empty?
|
53
|
+
# TODO: add support for grafana builtin datasource types
|
54
|
+
return UnsupportedDatasource.new(nil) if datasource_name.to_s =~ /-- (?:Mixed|Dashboard|Grafana) --/
|
55
|
+
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
56
|
+
|
57
|
+
@datasources[datasource_name]
|
54
58
|
end
|
55
59
|
|
56
|
-
# Returns
|
60
|
+
# Returns the datasource, which has been queried by the datasource id.
|
57
61
|
#
|
58
|
-
# @
|
59
|
-
|
60
|
-
|
62
|
+
# @param datasource_id [Integer] id of the searched datasource
|
63
|
+
# @return [Datasource] Datasource for the specified datasource id
|
64
|
+
def datasource_by_id(datasource_id)
|
65
|
+
datasource = @datasources.select { |_name, ds| ds.id == datasource_id.to_i }.values.first
|
66
|
+
raise DatasourceDoesNotExistError.new('id', datasource_id) unless datasource
|
67
|
+
|
68
|
+
datasource
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Array] Array of dashboard uids within the current grafana object
|
72
|
+
def dashboard_ids
|
73
|
+
response = prepare_request({ relative_url: '/api/search' }).execute
|
74
|
+
return [] unless response.is_a?(Net::HTTPOK)
|
75
|
+
|
76
|
+
dashboards = JSON.parse(response.body)
|
77
|
+
|
78
|
+
dashboards.each do |dashboard|
|
79
|
+
@dashboards[dashboard['uid']] = nil unless @dashboards[dashboard['uid']]
|
80
|
+
end
|
81
|
+
|
82
|
+
@dashboards.keys
|
61
83
|
end
|
62
84
|
|
63
85
|
# @param dashboard_uid [String] UID of the searched {Dashboard}
|
@@ -65,54 +87,27 @@ module Grafana
|
|
65
87
|
def dashboard(dashboard_uid)
|
66
88
|
return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
|
67
89
|
|
68
|
-
response =
|
69
|
-
|
70
|
-
|
71
|
-
raise DashboardDoesNotExistError, dashboard_uid if model.nil?
|
90
|
+
response = prepare_request({ relative_url: "/api/dashboards/uid/#{dashboard_uid}" }).execute
|
91
|
+
raise DashboardDoesNotExistError, dashboard_uid unless response.is_a?(Net::HTTPOK)
|
72
92
|
|
73
93
|
# cache dashboard for reuse
|
94
|
+
model = JSON.parse(response.body)['dashboard']
|
74
95
|
@dashboards[dashboard_uid] = Dashboard.new(model, self)
|
75
96
|
|
76
97
|
@dashboards[dashboard_uid]
|
77
98
|
end
|
78
99
|
|
79
|
-
#
|
100
|
+
# Prepares a {WebRequest} object for the current {Grafana} instance, which may be enriched
|
101
|
+
# with further properties and can then run {WebRequest#execute}.
|
80
102
|
#
|
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)
|
103
|
+
# @option options [Hash] :relative_url relative URL with a leading slash, which shall be queried
|
104
|
+
# @option options [Hash] :accept
|
105
|
+
# @option options [Hash] :body
|
106
|
+
# @option options [Hash] :content_type
|
107
|
+
# @return [WebRequest] webrequest prepared for execution
|
108
|
+
def prepare_request(options = {})
|
109
|
+
auth = @key ? { authorization: "Bearer #{@key}" } : {}
|
110
|
+
WebRequest.new(@base_uri, auth.merge({ logger: @logger }).merge(options))
|
116
111
|
end
|
117
112
|
|
118
113
|
private
|
@@ -120,12 +115,12 @@ module Grafana
|
|
120
115
|
def initialize_datasources
|
121
116
|
@datasources = {}
|
122
117
|
|
123
|
-
settings =
|
118
|
+
settings = prepare_request({ relative_url: '/api/frontend/settings' }).execute
|
124
119
|
return unless settings.is_a?(Net::HTTPOK)
|
125
120
|
|
126
121
|
json = JSON.parse(settings.body)
|
127
122
|
json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
|
128
|
-
@datasources[ds_name] = ds_value
|
123
|
+
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
|
129
124
|
end
|
130
125
|
@datasources['default'] = @datasources[json['defaultDatasource']]
|
131
126
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the datasource interface to grafana alerts.
|
5
|
+
class GrafanaAlertsDatasource < AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# dashboardId: Dashboard ID as String or nil
|
10
|
+
# panelId: Panel ID as String or nil
|
11
|
+
# columns:
|
12
|
+
# limit:
|
13
|
+
# query:
|
14
|
+
# state:
|
15
|
+
# folderId:
|
16
|
+
# dashboardQuery:
|
17
|
+
# dashboardTag:
|
18
|
+
# }
|
19
|
+
# @see AbstractDatasource#request
|
20
|
+
def request(query_description)
|
21
|
+
webrequest = query_description[:prepared_request]
|
22
|
+
webrequest.relative_url = "/api/alerts#{url_parameters(query_description)}"
|
23
|
+
|
24
|
+
result = webrequest.execute(query_description[:timeout])
|
25
|
+
|
26
|
+
json = JSON.parse(result.body)
|
27
|
+
|
28
|
+
content = []
|
29
|
+
begin
|
30
|
+
json.each { |item| content << item.fetch_values(*query_description[:raw_query]['columns'].split(',')) }
|
31
|
+
rescue KeyError => e
|
32
|
+
raise MalformedAttributeContentError.new(e.message, 'columns', query_description[:raw_query]['columns'])
|
33
|
+
end
|
34
|
+
|
35
|
+
result = {}
|
36
|
+
result[:header] = [query_description[:raw_query]['columns'].split(',')]
|
37
|
+
result[:content] = content
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def url_parameters(query_desc)
|
45
|
+
url_vars = {}
|
46
|
+
url_vars.merge!(query_desc[:raw_query].select do |k, _v|
|
47
|
+
k =~ /^(?:limit|dashboardId|panelId|query|state|folderId|dashboardQuery|dashboardTag)/
|
48
|
+
end)
|
49
|
+
url_vars['from'] = query_desc[:from] if query_desc[:from]
|
50
|
+
url_vars['to'] = query_desc[:to] if query_desc[:to]
|
51
|
+
url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.to_s] })
|
52
|
+
return '' if url_params.empty?
|
53
|
+
|
54
|
+
"?#{url_params}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|