ruby-grafana-reporter 0.1.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +86 -245
- data/bin/ruby-grafana-reporter +3 -2
- data/lib/VERSION.rb +6 -3
- data/lib/grafana/abstract_datasource.rb +116 -0
- data/lib/grafana/dashboard.rb +75 -66
- data/lib/grafana/errors.rb +81 -61
- data/lib/grafana/grafana.rb +130 -131
- data/lib/grafana/grafana_alerts_datasource.rb +57 -0
- data/lib/grafana/grafana_annotations_datasource.rb +56 -0
- data/lib/grafana/grafana_property_datasource.rb +25 -0
- data/lib/grafana/graphite_datasource.rb +44 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/panel.rb +47 -39
- data/lib/grafana/prometheus_datasource.rb +39 -0
- data/lib/grafana/sql_datasource.rb +65 -0
- data/lib/grafana/variable.rb +218 -259
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +401 -0
- data/lib/grafana_reporter/abstract_report.rb +163 -109
- data/lib/grafana_reporter/alerts_table_query.rb +44 -0
- data/lib/grafana_reporter/annotations_table_query.rb +43 -0
- data/lib/grafana_reporter/application/application.rb +162 -229
- data/lib/grafana_reporter/application/errors.rb +33 -30
- data/lib/grafana_reporter/application/webservice.rb +242 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +89 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +76 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +77 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +72 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +98 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +23 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +172 -159
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +92 -0
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +88 -0
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
- data/lib/grafana_reporter/configuration.rb +310 -326
- data/lib/grafana_reporter/console_configuration_wizard.rb +319 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +87 -0
- data/lib/grafana_reporter/errors.rb +81 -38
- data/lib/grafana_reporter/help.rb +447 -0
- data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
- data/lib/grafana_reporter/panel_image_query.rb +29 -0
- data/lib/grafana_reporter/panel_property_query.rb +22 -0
- data/lib/grafana_reporter/query_value_query.rb +79 -0
- data/lib/grafana_reporter/report_webhook.rb +35 -0
- data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +29 -27
- metadata +48 -60
- data/lib/grafana/abstract_panel_query.rb +0 -20
- data/lib/grafana/abstract_query.rb +0 -127
- data/lib/grafana/abstract_sql_query.rb +0 -42
- data/lib/grafana/panel_image_query.rb +0 -49
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -99
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
- data/lib/grafana_reporter/asciidoctor/errors.rb +0 -37
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -86
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -86
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -67
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -65
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -58
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -75
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -70
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -18
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -41
- data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -202
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -67
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -65
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -57
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -32
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -23
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -43
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -36
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -309
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -34
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -32
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# Implements the hook
|
6
|
+
# include::grafana_environment[]
|
7
|
+
#
|
8
|
+
# Shows all available variables, which are accessible during this run of the asciidoctor
|
9
|
+
# grafana reporter in a asciidoctor readable form.
|
10
|
+
#
|
11
|
+
# This processor is very helpful during report template design, to find out the available
|
12
|
+
# variables, that can be accessed.
|
13
|
+
#
|
14
|
+
# == Used document parameters
|
15
|
+
# All, to be listed as the available environment.
|
16
|
+
class ShowEnvironmentIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
|
17
|
+
include ProcessorMixin
|
18
|
+
|
19
|
+
# :nodoc:
|
20
|
+
def handles?(target)
|
21
|
+
target.start_with? 'grafana_environment'
|
22
|
+
end
|
23
|
+
|
24
|
+
# :nodoc:
|
25
|
+
def process(doc, reader, _target, _attrs)
|
26
|
+
# return if @report.cancel
|
27
|
+
@report.next_step
|
28
|
+
@report.logger.debug('Processing ShowEnvironmentIncludeProcessor')
|
29
|
+
|
30
|
+
vars = ['== Accessible Variables',
|
31
|
+
'|===']
|
32
|
+
doc.attributes.sort.each do |k, v|
|
33
|
+
vars << "| `+{#{k}}+` | #{v}"
|
34
|
+
end
|
35
|
+
vars << '|==='
|
36
|
+
|
37
|
+
reader.unshift_lines vars
|
38
|
+
end
|
39
|
+
|
40
|
+
# @see ProcessorMixin#build_demo_entry
|
41
|
+
def build_demo_entry(_panel)
|
42
|
+
'include::grafana_environment[]'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# Implements the hook
|
6
|
+
# include::grafana_help[]
|
7
|
+
#
|
8
|
+
# Shows all available options for the asciidoctor grafana reporter in a asciidoctor readable form.
|
9
|
+
#
|
10
|
+
# == Used document parameters
|
11
|
+
# None
|
12
|
+
class ShowHelpIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
|
13
|
+
include ProcessorMixin
|
14
|
+
|
15
|
+
# :nodoc:
|
16
|
+
def handles?(target)
|
17
|
+
target.start_with? 'grafana_help'
|
18
|
+
end
|
19
|
+
|
20
|
+
# :nodoc:
|
21
|
+
def process(_doc, reader, _target, _attrs)
|
22
|
+
# return if @report.cancel
|
23
|
+
@report.next_step
|
24
|
+
@report.logger.debug('Processing ShowHelpIncludeProcessor')
|
25
|
+
|
26
|
+
reader.unshift_lines GrafanaReporter::Help.new.asciidoctor.split("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see ProcessorMixin#build_demo_entry
|
30
|
+
def build_demo_entry(_panel)
|
31
|
+
'include::grafana_help[]'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# Implements the hook
|
6
|
+
# include::grafana_sql_table:<datasource_id>[<options>]
|
7
|
+
#
|
8
|
+
# Returns the results of the SQL query as a asciidoctor table.
|
9
|
+
#
|
10
|
+
# == Used document parameters
|
11
|
+
# +grafana_default_instance+ - name of grafana instance, 'default' if not specified
|
12
|
+
#
|
13
|
+
# +from+ - 'from' time for the sql query
|
14
|
+
#
|
15
|
+
# +to+ - 'to' time for the sql query
|
16
|
+
#
|
17
|
+
# All other variables starting with +var-+ will be used to replace grafana templating strings
|
18
|
+
# in the given SQL query.
|
19
|
+
#
|
20
|
+
# == Supported options
|
21
|
+
# +sql+ - sql statement (*mandatory*)
|
22
|
+
#
|
23
|
+
# +instance+ - name of grafana instance, 'default' if not specified
|
24
|
+
#
|
25
|
+
# +from+ - 'from' time for the sql query
|
26
|
+
#
|
27
|
+
# +to+ - 'to' time for the sql query
|
28
|
+
#
|
29
|
+
# +format+ - see {QueryMixin#format_columns}
|
30
|
+
#
|
31
|
+
# +replace_values+ - see {QueryMixin#replace_values}
|
32
|
+
#
|
33
|
+
# +filter_columns+ - see {QueryMixin#filter_columns}
|
34
|
+
class SqlTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
|
35
|
+
include ProcessorMixin
|
36
|
+
|
37
|
+
# :nodoc:
|
38
|
+
def handles?(target)
|
39
|
+
target.start_with? 'grafana_sql_table:'
|
40
|
+
end
|
41
|
+
|
42
|
+
# :nodoc:
|
43
|
+
def process(doc, reader, target, attrs)
|
44
|
+
return if @report.cancel
|
45
|
+
|
46
|
+
@report.next_step
|
47
|
+
instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
|
48
|
+
attrs['result_type'] = 'sql_table'
|
49
|
+
@report.logger.debug("Processing SqlTableIncludeProcessor (instance: #{instance},"\
|
50
|
+
" datasource: #{target.split(':')[1]}, sql: #{attrs['sql']})")
|
51
|
+
|
52
|
+
begin
|
53
|
+
# catch properly if datasource could not be identified
|
54
|
+
query = QueryValueQuery.new(@report.grafana(instance))
|
55
|
+
query.datasource = @report.grafana(instance).datasource_by_id(target.split(':')[1].to_i)
|
56
|
+
query.raw_query = attrs['sql']
|
57
|
+
query.merge_hash_variables(doc.attributes, attrs)
|
58
|
+
@report.logger.debug("from: #{query.from}, to: #{query.to}")
|
59
|
+
|
60
|
+
reader.unshift_lines query.execute
|
61
|
+
rescue GrafanaReporterError => e
|
62
|
+
@report.logger.error(e.message)
|
63
|
+
reader.unshift_line "|#{e.message}"
|
64
|
+
rescue StandardError => e
|
65
|
+
@report.logger.fatal(e.message)
|
66
|
+
reader.unshift_line "|#{e.message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
reader
|
70
|
+
end
|
71
|
+
|
72
|
+
# @see ProcessorMixin#build_demo_entry
|
73
|
+
def build_demo_entry(panel)
|
74
|
+
return nil unless panel
|
75
|
+
return nil unless panel.model['type'].include?('table')
|
76
|
+
|
77
|
+
ref_id = nil
|
78
|
+
panel.model['targets'].each do |item|
|
79
|
+
if !item['hide'] && !panel.query(item['refId']).to_s.empty?
|
80
|
+
ref_id = item['refId']
|
81
|
+
break
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return nil unless ref_id
|
85
|
+
|
86
|
+
"|===\ninclude::grafana_sql_table:#{panel.dashboard.grafana.datasource_by_name(panel.model['datasource']).id}"\
|
87
|
+
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",filter_columns=\"time\","\
|
88
|
+
"dashboard=\"#{panel.dashboard.id}\",from=\"now-1h\",to=\"now\"]\n|==="
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# Implements the hook
|
6
|
+
# grafana_sql_value:<datasource_id>[<options>]
|
7
|
+
#
|
8
|
+
# Returns the first value of the resulting SQL query.
|
9
|
+
#
|
10
|
+
# == Used document parameters
|
11
|
+
# +grafana_default_instance+ - name of grafana instance, 'default' if not specified
|
12
|
+
#
|
13
|
+
# +from+ - 'from' time for the sql query
|
14
|
+
#
|
15
|
+
# +to+ - 'to' time for the sql query
|
16
|
+
#
|
17
|
+
# All other variables starting with +var-+ will be used to replace grafana templating strings
|
18
|
+
# in the given SQL query.
|
19
|
+
#
|
20
|
+
# == Supported options
|
21
|
+
# +sql+ - sql statement (*mandatory*)
|
22
|
+
#
|
23
|
+
# +instance+ - name of grafana instance, 'default' if not specified
|
24
|
+
#
|
25
|
+
# +from+ - 'from' time for the sql query
|
26
|
+
#
|
27
|
+
# +to+ - 'to' time for the sql query
|
28
|
+
#
|
29
|
+
# +format+ - see {QueryMixin#format_columns}
|
30
|
+
#
|
31
|
+
# +replace_values+ - see {QueryMixin#replace_values}
|
32
|
+
#
|
33
|
+
# +filter_columns+ - see {QueryMixin#filter_columns}
|
34
|
+
class SqlValueInlineMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
|
35
|
+
include ProcessorMixin
|
36
|
+
use_dsl
|
37
|
+
|
38
|
+
named :grafana_sql_value
|
39
|
+
|
40
|
+
# @see GrafanaReporter::Asciidoctor::SqlFirstValueQuery
|
41
|
+
def process(parent, target, attrs)
|
42
|
+
return if @report.cancel
|
43
|
+
|
44
|
+
@report.next_step
|
45
|
+
instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
|
46
|
+
attrs['result_type'] = 'sql_value'
|
47
|
+
@report.logger.debug("Processing SqlValueInlineMacro (instance: #{instance}, datasource: #{target},"\
|
48
|
+
" sql: #{attrs['sql']})")
|
49
|
+
|
50
|
+
begin
|
51
|
+
# catch properly if datasource could not be identified
|
52
|
+
query = QueryValueQuery.new(@report.grafana(instance))
|
53
|
+
query.datasource = @report.grafana(instance).datasource_by_id(target)
|
54
|
+
query.raw_query = attrs['sql']
|
55
|
+
query.merge_hash_variables(parent.document.attributes, attrs)
|
56
|
+
@report.logger.debug("from: #{query.from}, to: #{query.to}")
|
57
|
+
|
58
|
+
create_inline(parent, :quoted, query.execute)
|
59
|
+
rescue GrafanaReporterError => e
|
60
|
+
@report.logger.error(e.message)
|
61
|
+
create_inline(parent, :quoted, e.message)
|
62
|
+
rescue StandardError => e
|
63
|
+
@report.logger.fatal(e.message)
|
64
|
+
create_inline(parent, :quoted, e.message)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @see ProcessorMixin#build_demo_entry
|
69
|
+
def build_demo_entry(panel)
|
70
|
+
return nil unless panel
|
71
|
+
return nil unless panel.model['type'] == 'singlestat'
|
72
|
+
|
73
|
+
ref_id = nil
|
74
|
+
panel.model['targets'].each do |item|
|
75
|
+
if !item['hide'] && !panel.query(item['refId']).to_s.empty?
|
76
|
+
ref_id = item['refId']
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
return nil unless ref_id
|
81
|
+
|
82
|
+
"grafana_sql_value:#{panel.dashboard.grafana.datasource_by_name(panel.model['datasource']).id}"\
|
83
|
+
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",from=\"now-1h\","\
|
84
|
+
'to="now"]'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'processor_mixin'
|
4
|
+
|
5
|
+
module GrafanaReporter
|
6
|
+
module Asciidoctor
|
7
|
+
# Implements the hook
|
8
|
+
# include::grafana_value_as_variable[<options>]
|
9
|
+
#
|
10
|
+
# Returns an attribute definition in asciidoctor format. This is needed if you want to refer to values of
|
11
|
+
# a grafana query within a variable in asciidoctor. As this works without this function for the
|
12
|
+
# `IncludeProcessor`s values, it will not work for all the other processors.
|
13
|
+
#
|
14
|
+
# This method is just a proxy for all other hooks and will forward parameters accordingly.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# include:grafana_value_as_variable[call="grafana_sql_value:1",variable_name="my_variable",sql="SELECT 'looks good'",<any_other_option>]
|
19
|
+
#
|
20
|
+
# This will call the {SqlValueInlineMacro} with `datasource_id` set to `1` and store the result in the
|
21
|
+
# variable. The resulting asciidoctor variable definition will be created as:
|
22
|
+
#
|
23
|
+
# :my_variable: looks good
|
24
|
+
#
|
25
|
+
# and can be refered to in your document easily as
|
26
|
+
#
|
27
|
+
# {my_variable}
|
28
|
+
#
|
29
|
+
# == Supported options
|
30
|
+
# +call+ - regular call to the reporter hook (*mandatory*)
|
31
|
+
#
|
32
|
+
# +variable_name+ - name of the variable, to which the result shall be assigned (*mandatory*)
|
33
|
+
class ValueAsVariableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
|
34
|
+
include ProcessorMixin
|
35
|
+
|
36
|
+
# :nodoc:
|
37
|
+
def handles?(target)
|
38
|
+
target.start_with? 'grafana_value_as_variable'
|
39
|
+
end
|
40
|
+
|
41
|
+
# :nodoc:
|
42
|
+
def process(doc, reader, target, attrs)
|
43
|
+
return if @report.cancel
|
44
|
+
|
45
|
+
# increase step for this processor as well as it is also counted in the step counter
|
46
|
+
@report.next_step
|
47
|
+
|
48
|
+
call_attr = attrs.delete('call')
|
49
|
+
call, target = call_attr.split(':') if call_attr
|
50
|
+
attribute = attrs.delete('variable_name')
|
51
|
+
@report.logger.debug("Processing ValueAsVariableIncludeProcessor (call: #{call}, target: #{target},"\
|
52
|
+
" variable_name: #{attribute}, attrs: #{attrs})")
|
53
|
+
if !call || !attribute
|
54
|
+
@report.logger.error('ValueAsVariableIncludeProcessor: Missing mandatory attribute \'call\' or '\
|
55
|
+
'\'variable_name\'.')
|
56
|
+
# increase counter, as error occured and no sub call is being processed
|
57
|
+
@report.next_step
|
58
|
+
return reader
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: remove dirty hack to allow the document as parameter for other processors
|
62
|
+
def doc.document
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# TODO: properly show error messages also in document
|
67
|
+
ext = doc.extensions.find_inline_macro_extension(call) if doc.extensions.inline_macros?
|
68
|
+
if !ext
|
69
|
+
@report.logger.error('ValueAsVariableIncludeProcessor: Could not find inline macro extension for '\
|
70
|
+
"'#{call}'.")
|
71
|
+
# increase counter, as error occured and no sub call is being processed
|
72
|
+
@report.next_step
|
73
|
+
else
|
74
|
+
@report.logger.debug('ValueAsVariableIncludeProcessor: Calling sub-method.')
|
75
|
+
item = ext.process_method.call(doc, target, attrs)
|
76
|
+
if !item.text.to_s.empty?
|
77
|
+
result = ":#{attribute}: #{item.text}"
|
78
|
+
@report.logger.debug("ValueAsVariableIncludeProcessor: Adding '#{result}' to document.")
|
79
|
+
reader.unshift_line(result)
|
80
|
+
else
|
81
|
+
@report.logger.debug("ValueAsVariableIncludeProcessor: Not adding variable '#{attribute}'"\
|
82
|
+
' as query result was empty.')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
reader
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -1,326 +1,310 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
# @
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
# @param instance [String] grafana instance name, for which the value shall be retrieved.
|
73
|
-
# @return [
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
# @
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
# @return [
|
111
|
-
def
|
112
|
-
get_config('grafana-reporter:
|
113
|
-
end
|
114
|
-
|
115
|
-
# @return [
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
path.
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
'grafana-reporter' =>
|
312
|
-
[
|
313
|
-
Hash, 0,
|
314
|
-
{
|
315
|
-
'run-mode' => [String, 0],
|
316
|
-
'test-instance' => [String, 0],
|
317
|
-
'templates-folder' => [String, 0],
|
318
|
-
'reports-folder' => [String, 0],
|
319
|
-
'report-retention' => [Integer, 0],
|
320
|
-
'webservice-port' => [Integer, 0]
|
321
|
-
}
|
322
|
-
]
|
323
|
-
}
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# In this namespace all objects needed for the grafana reporter are collected.
|
4
|
+
module GrafanaReporter
|
5
|
+
# Used to store the whole settings, which are necessary to run the reporter.
|
6
|
+
# It can read configuration files, but might also be configured programmatically.
|
7
|
+
#
|
8
|
+
# This class also contains a function {#validate}, which ensures that the
|
9
|
+
# provided settings are set properly.
|
10
|
+
#
|
11
|
+
# Using this class is embedded in the {Application::Application#configure_and_run}.
|
12
|
+
#
|
13
|
+
class Configuration
|
14
|
+
# @return [AbstractReport] specific report class, which should be used.
|
15
|
+
attr_accessor :report_class
|
16
|
+
|
17
|
+
# Returned by {#mode} if only a connection test shall be executed.
|
18
|
+
MODE_CONNECTION_TEST = 'test'
|
19
|
+
# Returned by {#mode} if only one configured report shall be rendered.
|
20
|
+
MODE_SINGLE_RENDER = 'single-render'
|
21
|
+
# Returned by {#mode} if the default webservice shall be started.
|
22
|
+
MODE_SERVICE = 'webservice'
|
23
|
+
|
24
|
+
# Used to access the configuration hash. To make sure, that the configuration is
|
25
|
+
# valid, call {#validate}.
|
26
|
+
attr_reader :config
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@config = {}
|
30
|
+
@logger = ::Logger.new($stderr, level: :info)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :logger
|
34
|
+
|
35
|
+
# Used to overwrite the current configuration.
|
36
|
+
def config=(new_config)
|
37
|
+
@config = new_config
|
38
|
+
update_configuration
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] mode, in which the reporting shall be executed. One of {MODE_CONNECTION_TEST},
|
42
|
+
# {MODE_SINGLE_RENDER} and {MODE_SERVICE}.
|
43
|
+
def mode
|
44
|
+
if (get_config('grafana-reporter:run-mode') != MODE_CONNECTION_TEST) &&
|
45
|
+
(get_config('grafana-reporter:run-mode') != MODE_SINGLE_RENDER)
|
46
|
+
return MODE_SERVICE
|
47
|
+
end
|
48
|
+
|
49
|
+
get_config('grafana-reporter:run-mode')
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String] full path of configured report template. Only needed in {MODE_SINGLE_RENDER}.
|
53
|
+
def template
|
54
|
+
return nil if get_config('default-document-attributes:var-template').nil?
|
55
|
+
|
56
|
+
"#{templates_folder}#{get_config('default-document-attributes:var-template')}.adoc"
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] destination filename for the report in {MODE_SINGLE_RENDER}.
|
60
|
+
def to_file
|
61
|
+
return get_config('to_file') || true if mode == MODE_SINGLE_RENDER
|
62
|
+
|
63
|
+
get_config('to_file')
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Array<String>] names of the configured grafana_instances.
|
67
|
+
def grafana_instances
|
68
|
+
instances = get_config('grafana')
|
69
|
+
instances.keys
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param instance [String] grafana instance name, for which the value shall be retrieved.
|
73
|
+
# @return [String] configured 'host' for the requested grafana instance.
|
74
|
+
def grafana_host(instance = 'default')
|
75
|
+
host = get_config("grafana:#{instance}:host")
|
76
|
+
raise GrafanaInstanceWithoutHostError, instance if host.nil?
|
77
|
+
|
78
|
+
host
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param instance [String] grafana instance name, for which the value shall be retrieved.
|
82
|
+
# @return [String] configured 'api_key' for the requested grafana instance.
|
83
|
+
def grafana_api_key(instance = 'default')
|
84
|
+
get_config("grafana:#{instance}:api_key")
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String] configured folder, in which the report templates are stored including trailing slash.
|
88
|
+
# By default: current folder.
|
89
|
+
def templates_folder
|
90
|
+
result = get_config('grafana-reporter:templates-folder') || '.'
|
91
|
+
return result.sub(%r{/*$}, '/') unless result.empty?
|
92
|
+
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns configured folder, in which temporary images during report generation
|
97
|
+
# shall be stored including trailing slash. Folder has to be a subfolder of
|
98
|
+
# {#templates_folder}. By default: current folder.
|
99
|
+
# @return [String] configured folder, in which temporary images shall be stored.
|
100
|
+
def images_folder
|
101
|
+
img_path = templates_folder
|
102
|
+
img_path = if img_path.empty?
|
103
|
+
get_config('default-document-attributes:imagesdir').to_s
|
104
|
+
else
|
105
|
+
img_path + get_config('default-document-attributes:imagesdir').to_s
|
106
|
+
end
|
107
|
+
img_path.empty? ? './' : img_path.sub(%r{/*$}, '/')
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [String] name of grafana instance, against which a test shall be executed
|
111
|
+
def test_instance
|
112
|
+
get_config('grafana-reporter:test-instance')
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String] configured folder, in which the reports shall be stored including trailing slash.
|
116
|
+
# By default: current folder.
|
117
|
+
def reports_folder
|
118
|
+
result = get_config('grafana-reporter:reports-folder') || '.'
|
119
|
+
return result.sub(%r{/*$}, '/') unless result.empty?
|
120
|
+
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [Integer] how many hours a generated report shall be retained, before it shall be deleted.
|
125
|
+
# By default: 24.
|
126
|
+
def report_retention
|
127
|
+
get_config('grafana-reporter:report-retention') || 24
|
128
|
+
end
|
129
|
+
|
130
|
+
# @return [Integer] port, on which the webserver shall run. By default: 8815.
|
131
|
+
def webserver_port
|
132
|
+
get_config('grafana-reporter:webservice-port') || 8815
|
133
|
+
end
|
134
|
+
|
135
|
+
# The configuration made with the setting 'default-document-attributes' will
|
136
|
+
# be passed 1:1 to the asciidoctor report service. It can be used to preconfigure
|
137
|
+
# whatever is essential for the needed report renderings.
|
138
|
+
# @return [Hash] configured document attributes
|
139
|
+
def default_document_attributes
|
140
|
+
get_config('default-document-attributes') || {}
|
141
|
+
end
|
142
|
+
|
143
|
+
# This function shall be called, before the configuration object is used in the
|
144
|
+
# {Application::Application#run}. It ensures, that everything is setup properly
|
145
|
+
# and all necessary folders exist. Appropriate errors are raised in case of errors.
|
146
|
+
# @param explicit [Boolean] true, if validation shall expect explicit (wizard) configuration file
|
147
|
+
# @return [void]
|
148
|
+
def validate(explicit = false)
|
149
|
+
check_deprecation
|
150
|
+
validate_schema(schema(explicit), @config)
|
151
|
+
|
152
|
+
# check if set folders exist
|
153
|
+
raise FolderDoesNotExistError.new(reports_folder, 'reports-folder') unless File.directory?(reports_folder)
|
154
|
+
raise FolderDoesNotExistError.new(templates_folder, 'templates-folder') unless File.directory?(templates_folder)
|
155
|
+
raise FolderDoesNotExistError.new(images_folder, 'images-folder') unless File.directory?(images_folder)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Can be used to configure or overwrite single parameters.
|
159
|
+
#
|
160
|
+
# @param path [String] path of the paramter to set, e.g. +grafana-reporter:webservice-port+
|
161
|
+
# @param value [Object] value to set
|
162
|
+
def set_param(path, value)
|
163
|
+
return if path.nil?
|
164
|
+
|
165
|
+
levels = path.split(':')
|
166
|
+
last_level = levels.pop
|
167
|
+
|
168
|
+
cur_pos = @config
|
169
|
+
levels.each do |subpath|
|
170
|
+
cur_pos[subpath] = {} unless cur_pos[subpath]
|
171
|
+
cur_pos = cur_pos[subpath]
|
172
|
+
end
|
173
|
+
|
174
|
+
cur_pos[last_level] = value
|
175
|
+
update_configuration
|
176
|
+
end
|
177
|
+
|
178
|
+
# Merge the given configuration object settings with the current config, i.e. overwrite and add all
|
179
|
+
# settings from the given config, but keep the not specified configs from the current object.
|
180
|
+
#
|
181
|
+
# param other_config [Configuration] other configuration object
|
182
|
+
def merge!(other_config)
|
183
|
+
config.merge!(other_config.config) { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2) : v2 }
|
184
|
+
update_configuration
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def check_deprecation
|
190
|
+
return if report_class
|
191
|
+
|
192
|
+
logger.warn('DEPRECATION WARNING: Your configuration explicitly needs to specify the '\
|
193
|
+
'\'grafana-reporter:report-class\' value. Currently this defaults to '\
|
194
|
+
'\'GrafanaReporter::Asciidoctor::Report\'. You can get rid of this warning, if you '\
|
195
|
+
'explicitly set this configuration in your configuration file. Setting this default will be '\
|
196
|
+
'removed in a future version.')
|
197
|
+
set_param('grafana-reporter:report-class', 'GrafanaReporter::Asciidoctor::Report')
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_configuration
|
201
|
+
debug_level = get_config('grafana-reporter:debug-level')
|
202
|
+
rep_class = get_config('grafana-reporter:report-class')
|
203
|
+
|
204
|
+
@logger.level = Object.const_get("::Logger::Severity::#{debug_level}") if debug_level =~ /DEBUG|INFO|WARN|
|
205
|
+
ERROR|FATAL|UNKNOWN/x
|
206
|
+
self.report_class = Object.const_get(rep_class) if rep_class
|
207
|
+
::Grafana::WebRequest.ssl_cert = get_config('grafana-reporter:ssl-cert')
|
208
|
+
|
209
|
+
# register callbacks
|
210
|
+
callbacks = get_config('grafana-reporter:callbacks')
|
211
|
+
return unless callbacks
|
212
|
+
|
213
|
+
callbacks.each do |url, event|
|
214
|
+
AbstractReport.add_event_listener(event.to_sym, ReportWebhook.new(url))
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_config(path)
|
219
|
+
return if path.nil?
|
220
|
+
|
221
|
+
cur_pos = @config
|
222
|
+
path.split(':').each do |subpath|
|
223
|
+
cur_pos = cur_pos[subpath] if cur_pos
|
224
|
+
end
|
225
|
+
cur_pos
|
226
|
+
end
|
227
|
+
|
228
|
+
def validate_schema(schema, subject)
|
229
|
+
return nil if subject.nil?
|
230
|
+
|
231
|
+
schema.each do |key, config|
|
232
|
+
type, min_occurence, next_level = config
|
233
|
+
|
234
|
+
validate_schema(next_level, subject[key]) if next_level
|
235
|
+
|
236
|
+
if key.nil?
|
237
|
+
# apply to all on this level
|
238
|
+
raise ConfigurationError, "Unhandled configuration data type '#{subject.class}'." unless subject.is_a?(Hash)
|
239
|
+
|
240
|
+
if subject.length < min_occurence
|
241
|
+
raise ConfigurationDoesNotMatchSchemaError.new(key, 'occur', min_occurence, subject.length)
|
242
|
+
end
|
243
|
+
|
244
|
+
subject.each do |k, _v|
|
245
|
+
sub_scheme = {}
|
246
|
+
sub_scheme[k] = schema[nil]
|
247
|
+
validate_schema(sub_scheme, subject)
|
248
|
+
end
|
249
|
+
|
250
|
+
# apply to single item
|
251
|
+
elsif subject.is_a?(Hash)
|
252
|
+
if !subject.key?(key) && min_occurence.positive?
|
253
|
+
raise ConfigurationDoesNotMatchSchemaError.new(key, 'occur', min_occurence, 0)
|
254
|
+
end
|
255
|
+
if !subject[key].is_a?(type) && subject.key?(key)
|
256
|
+
raise ConfigurationDoesNotMatchSchemaError.new(key, 'be a', type, subject[key].class)
|
257
|
+
end
|
258
|
+
|
259
|
+
else
|
260
|
+
raise ConfigurationError, "Unhandled configuration data type '#{subject.class}'."
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# validate also if subject has further configurations, which are not known by the reporter
|
265
|
+
subject.each do |item, _subitems|
|
266
|
+
schema_config = schema[item] || schema[nil]
|
267
|
+
if schema_config.nil?
|
268
|
+
logger.warn("Item '#{item}' in configuration is unknown to the reporter and will be ignored")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def schema(explicit)
|
274
|
+
{
|
275
|
+
'grafana' =>
|
276
|
+
[
|
277
|
+
Hash, 1,
|
278
|
+
{
|
279
|
+
nil =>
|
280
|
+
[
|
281
|
+
Hash, 1,
|
282
|
+
{
|
283
|
+
'host' => [String, 1],
|
284
|
+
'api_key' => [String, 0]
|
285
|
+
}
|
286
|
+
]
|
287
|
+
}
|
288
|
+
],
|
289
|
+
'default-document-attributes' => [Hash, explicit ? 1 : 0],
|
290
|
+
'to_file' => [String, 0],
|
291
|
+
'grafana-reporter' =>
|
292
|
+
[
|
293
|
+
Hash, 1,
|
294
|
+
{
|
295
|
+
'debug-level' => [String, 0],
|
296
|
+
'run-mode' => [String, 0],
|
297
|
+
'test-instance' => [String, 0],
|
298
|
+
'templates-folder' => [String, explicit ? 1 : 0],
|
299
|
+
'report-class' => [String, 1],
|
300
|
+
'reports-folder' => [String, explicit ? 1 : 0],
|
301
|
+
'report-retention' => [Integer, explicit ? 1 : 0],
|
302
|
+
'ssl-cert' => [String, 0],
|
303
|
+
'webservice-port' => [Integer, explicit ? 1 : 0],
|
304
|
+
'callbacks' => [Hash, 0, { nil => [String, 1] }]
|
305
|
+
}
|
306
|
+
]
|
307
|
+
}
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|