ruby-grafana-reporter 0.2.0 → 0.4.1
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 +105 -86
- data/bin/ruby-grafana-reporter +5 -5
- data/lib/VERSION.rb +3 -2
- data/lib/grafana/abstract_datasource.rb +136 -0
- data/lib/grafana/dashboard.rb +21 -23
- data/lib/grafana/errors.rb +8 -1
- data/lib/grafana/grafana.rb +61 -65
- 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 +50 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/panel.rb +9 -3
- data/lib/grafana/prometheus_datasource.rb +45 -0
- data/lib/grafana/sql_datasource.rb +71 -0
- data/lib/grafana/unsupported_datasource.rb +7 -0
- data/lib/grafana/variable.rb +3 -2
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +359 -0
- data/lib/grafana_reporter/abstract_report.rb +119 -17
- 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 +49 -297
- data/lib/grafana_reporter/application/webservice.rb +49 -14
- 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 +77 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +79 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +73 -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 +64 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +47 -76
- 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 +108 -43
- data/lib/grafana_reporter/console_configuration_wizard.rb +311 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +101 -0
- data/lib/grafana_reporter/erb/report.rb +43 -0
- data/lib/grafana_reporter/errors.rb +41 -0
- data/lib/grafana_reporter/help.rb +443 -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 +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_extension.rb +8 -0
- data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +13 -0
- metadata +47 -43
- 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 -104
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -99
- 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 -202
- 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 -61
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -34
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -25
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -38
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -310
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -37
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -39
@@ -9,6 +9,13 @@ module GrafanaReporter
|
|
9
9
|
# Objects of this class are also stored in {Application::Application}, unless
|
10
10
|
# the retention time is over.
|
11
11
|
class AbstractReport
|
12
|
+
# Array of supported event callback symbols
|
13
|
+
EVENT_CALLBACKS = %i[all on_before_create on_after_cancel on_after_finish].freeze
|
14
|
+
|
15
|
+
# Class variable for storing event listeners
|
16
|
+
@@event_listeners = {}
|
17
|
+
@@event_listeners.default = []
|
18
|
+
|
12
19
|
# @return [String] path to the template
|
13
20
|
attr_reader :template
|
14
21
|
|
@@ -28,21 +35,38 @@ module GrafanaReporter
|
|
28
35
|
attr_reader :done
|
29
36
|
|
30
37
|
# @param config [Configuration] configuration object
|
31
|
-
|
32
|
-
# @param destination_file_or_path [String or File] path to the destination report or file object to use
|
33
|
-
# @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
|
34
|
-
def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
|
38
|
+
def initialize(config)
|
35
39
|
@config = config
|
36
40
|
@logger = Logger::TwoWayDelegateLogger.new
|
37
41
|
@logger.additional_logger = @config.logger
|
38
|
-
@
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
@grafana_instances = {}
|
43
|
+
|
44
|
+
init_before_create
|
45
|
+
end
|
46
|
+
|
47
|
+
# Registers a new event listener object.
|
48
|
+
# @param event [Symbol] one of EVENT_CALLBACKS
|
49
|
+
# @param listener [Object] object responding to #callback(event_symbol, object)
|
50
|
+
def self.add_event_listener(event, listener)
|
51
|
+
@@event_listeners[event] = [] if @@event_listeners[event] == []
|
52
|
+
@@event_listeners[event].push(listener)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes all registeres event listener objects
|
56
|
+
def self.clear_event_listeners
|
57
|
+
@@event_listeners = {}
|
58
|
+
@@event_listeners.default = []
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param instance [String] requested grafana instance
|
62
|
+
# @return [Grafana::Grafana] the requested grafana instance.
|
63
|
+
def grafana(instance)
|
64
|
+
unless @grafana_instances[instance]
|
65
|
+
@grafana_instances[instance] = ::Grafana::Grafana.new(@config.grafana_host(instance),
|
66
|
+
@config.grafana_api_key(instance),
|
67
|
+
logger: @logger)
|
68
|
+
end
|
69
|
+
@grafana_instances[instance]
|
46
70
|
end
|
47
71
|
|
48
72
|
# Call to request cancelling the report generation.
|
@@ -50,6 +74,7 @@ module GrafanaReporter
|
|
50
74
|
def cancel!
|
51
75
|
@cancel = true
|
52
76
|
logger.info('Cancelling report generation invoked.')
|
77
|
+
notify(:on_after_cancel)
|
53
78
|
end
|
54
79
|
|
55
80
|
# @return [String] path to the report destination file
|
@@ -81,9 +106,12 @@ module GrafanaReporter
|
|
81
106
|
@error || []
|
82
107
|
end
|
83
108
|
|
84
|
-
# @return [String] status of the report,
|
109
|
+
# @return [String] status of the report as string, either 'not started', 'in progress', 'cancelling',
|
110
|
+
# 'cancelled', 'died' or 'finished'.
|
85
111
|
def status
|
112
|
+
return 'not started' unless @start_time
|
86
113
|
return 'cancelled' if done && cancel
|
114
|
+
return 'cancelling' if !done && cancel
|
87
115
|
return 'finished' if done && error.empty?
|
88
116
|
return 'died' if done && !error.empty?
|
89
117
|
|
@@ -96,17 +124,91 @@ module GrafanaReporter
|
|
96
124
|
logger.internal_messages
|
97
125
|
end
|
98
126
|
|
99
|
-
# @abstract
|
100
127
|
# Is being called to start the report generation.
|
128
|
+
# @param template [String] path to the template to be used, trailing +.adoc+ extension may be omitted
|
129
|
+
# @param destination_file_or_path [String or File] path to the destination report or file object to use
|
130
|
+
# @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
|
101
131
|
# @return [void]
|
102
|
-
def create_report
|
103
|
-
|
132
|
+
def create_report(template, destination_file_or_path = nil, custom_attributes = {})
|
133
|
+
init_before_create
|
134
|
+
@template = template
|
135
|
+
@destination_file_or_path = destination_file_or_path
|
136
|
+
@custom_attributes = custom_attributes
|
137
|
+
|
138
|
+
# automatically add extension, if a file with adoc extension exists
|
139
|
+
@template = "#{@template}.adoc" if File.file?("#{@template}.adoc") && !File.file?(@template.to_s)
|
140
|
+
raise MissingTemplateError, @template.to_s unless File.file?(@template.to_s)
|
141
|
+
|
142
|
+
notify(:on_before_create)
|
143
|
+
@start_time = Time.new
|
144
|
+
logger.info("Report started at #{@start_time}")
|
104
145
|
end
|
105
146
|
|
106
|
-
#
|
147
|
+
# Used to calculate the progress of a report. By default expects +@total_steps+ to contain the total
|
148
|
+
# number of steps, which will be processed with each call of {#next_step}.
|
107
149
|
# @return [Integer] number between 0 and 100, representing the current progress of the report creation.
|
108
150
|
def progress
|
151
|
+
return @current_pos.to_i if @total_steps.to_i.zero?
|
152
|
+
|
153
|
+
@current_pos.to_f / @total_steps
|
154
|
+
end
|
155
|
+
|
156
|
+
# Increments the progress.
|
157
|
+
# @return [Integer] number of the current progress position.
|
158
|
+
def next_step
|
159
|
+
@current_pos += 1
|
160
|
+
@current_pos
|
161
|
+
end
|
162
|
+
|
163
|
+
# @abstract
|
164
|
+
# Provided class objects need to implement a method +build_demo_entry(panel)+.
|
165
|
+
# @return [Array<Class>] array of class objects, which shall be included in a demo report
|
166
|
+
def self.demo_report_classes
|
109
167
|
raise NotImplementedError
|
110
168
|
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
# Called, if the report generation has died with an error.
|
173
|
+
# @param error [StandardError] occured error
|
174
|
+
# @return [void]
|
175
|
+
def died_with_error(error)
|
176
|
+
@error = [error.message] << [error.backtrace]
|
177
|
+
done!
|
178
|
+
end
|
179
|
+
|
180
|
+
def init_before_create
|
181
|
+
@done = false
|
182
|
+
@start_time = nil
|
183
|
+
@end_time = nil
|
184
|
+
@cancel = false
|
185
|
+
@current_pos = 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def done!
|
189
|
+
return if @done
|
190
|
+
|
191
|
+
@done = true
|
192
|
+
@end_time = Time.new
|
193
|
+
@start_time ||= @end_time
|
194
|
+
logger.info("Report creation ended after #{@end_time.to_i - @start_time.to_i} seconds with status '#{status}'")
|
195
|
+
notify(:on_after_finish)
|
196
|
+
end
|
197
|
+
|
198
|
+
def notify(event)
|
199
|
+
(@@event_listeners[:all] + @@event_listeners[event]).each do |listener|
|
200
|
+
logger.debug("Informing event listener '#{listener.class}' about event '#{event}' for report '#{object_id}'.")
|
201
|
+
begin
|
202
|
+
res = listener.callback(event, self)
|
203
|
+
logger.debug("Event listener '#{listener.class}' for event '#{event}' and report '#{object_id}' returned "\
|
204
|
+
"with result '#{res}'.")
|
205
|
+
rescue StandardError => e
|
206
|
+
msg = "Event listener '#{listener.class}' for event '#{event}' and report '#{object_id}' returned with "\
|
207
|
+
"error: #{e.message} - #{e.backtrace}."
|
208
|
+
puts msg
|
209
|
+
logger.error(msg)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
111
213
|
end
|
112
214
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
# This class is used to query alerts from grafana.
|
5
|
+
class AlertsTableQuery < AbstractQuery
|
6
|
+
# Check if mandatory {Grafana::Variable} +columns+ is specified in variables.
|
7
|
+
#
|
8
|
+
# The value of the +columns+ variable has to be a comma separated list of column titles, which
|
9
|
+
# need to be included in the following list:
|
10
|
+
# - limit
|
11
|
+
# - dashboardId
|
12
|
+
# - panelId
|
13
|
+
# - query
|
14
|
+
# - state
|
15
|
+
# - folderId
|
16
|
+
# - dashboardQuery
|
17
|
+
# - dashboardTag
|
18
|
+
# @return [void]
|
19
|
+
def pre_process
|
20
|
+
raise MissingMandatoryAttributeError, 'columns' unless @raw_query['columns']
|
21
|
+
|
22
|
+
@from = translate_date(@from, @variables['grafana_report_timestamp'], false, @variables['from_timezone'] ||
|
23
|
+
@variables['grafana_default_from_timezone'])
|
24
|
+
@to = translate_date(@to, @variables['grafana_report_timestamp'], true, @variables['to_timezone'] ||
|
25
|
+
@variables['grafana_default_to_timezone'])
|
26
|
+
@datasource = Grafana::GrafanaAlertsDatasource.new(nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Filter the query result for the given columns and sets the result in the preformatted SQL
|
30
|
+
# result stlye.
|
31
|
+
#
|
32
|
+
# Additionally it applies {AbstractQuery#format_columns}, {AbstractQuery#replace_values} and
|
33
|
+
# {AbstractQuery#filter_columns}.
|
34
|
+
# @return [void]
|
35
|
+
def post_process
|
36
|
+
@result = format_columns(@result, @variables['format'])
|
37
|
+
@result = replace_values(@result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
|
38
|
+
@result = filter_columns(@result, @variables['filter_columns'])
|
39
|
+
|
40
|
+
# TODO: move formatting to Asciidoctor namespace
|
41
|
+
@result = @result[:content].map { |row| "| #{row.map { |item| item.to_s.gsub('|', '\\|') }.join(' | ')}" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
# This class is used to query annotations from grafana.
|
5
|
+
class AnnotationsTableQuery < AbstractQuery
|
6
|
+
# Check if mandatory {Grafana::Variable} +columns+ is specified in variables.
|
7
|
+
#
|
8
|
+
# The value of the +columns+ variable has to be a comma separated list of column titles, which
|
9
|
+
# need to be included in the following list:
|
10
|
+
# - limit
|
11
|
+
# - alertId
|
12
|
+
# - userId
|
13
|
+
# - type
|
14
|
+
# - tags
|
15
|
+
# - dashboardId
|
16
|
+
# - panelId
|
17
|
+
# @return [void]
|
18
|
+
def pre_process
|
19
|
+
raise MissingMandatoryAttributeError, 'columns' unless @raw_query['columns']
|
20
|
+
|
21
|
+
@from = translate_date(@from, @variables['grafana_report_timestamp'], false, @variables['from_timezone'] ||
|
22
|
+
@variables['grafana_default_from_timezone'])
|
23
|
+
@to = translate_date(@to, @variables['grafana_report_timestamp'], true, @variables['to_timezone'] ||
|
24
|
+
@variables['grafana_default_to_timezone'])
|
25
|
+
@datasource = Grafana::GrafanaAnnotationsDatasource.new(nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Filters the query result for the given columns and sets the result
|
29
|
+
# in the preformatted SQL result style.
|
30
|
+
#
|
31
|
+
# Additionally it applies {AbstractQuery#format_columns}, {AbstractQuery#replace_values} and
|
32
|
+
# {AbstractQuery#filter_columns}.
|
33
|
+
# @return [void]
|
34
|
+
def post_process
|
35
|
+
@result = format_columns(@result, @variables['format'])
|
36
|
+
@result = replace_values(@result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
|
37
|
+
@result = filter_columns(@result, @variables['filter_columns'])
|
38
|
+
|
39
|
+
# TODO: move formatting to Asciidoctor namespace
|
40
|
+
@result = result[:content].map { |row| "| #{row.map { |item| item.to_s.gsub('|', '\\|') }.join(' | ')}" }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -13,61 +13,79 @@ module GrafanaReporter
|
|
13
13
|
# It can be run to test the grafana connection, render a single template
|
14
14
|
# or run as a service.
|
15
15
|
class Application
|
16
|
-
# Default file name for grafana reporter configuration file
|
17
|
-
CONFIG_FILE = 'grafana_reporter.config'
|
18
|
-
|
19
|
-
def initialize
|
20
|
-
@logger = ::Logger.new($stdout, level: :unknown)
|
21
|
-
end
|
22
16
|
|
23
17
|
# Contains the {Configuration} object of the application.
|
24
18
|
attr_accessor :config
|
25
19
|
|
20
|
+
# Stores the {Webservice} object of the application
|
21
|
+
attr_reader :webservice
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@config = Configuration.new
|
25
|
+
@webservice = Webservice.new
|
26
|
+
end
|
27
|
+
|
26
28
|
# This is the main method, which is called, if the application is
|
27
29
|
# run in standalone mode.
|
28
30
|
# @param params [Array<String>] command line parameters, mainly ARGV can be used.
|
29
31
|
# @return [Integer] 0 if everything is fine, -1 if execution aborted.
|
30
32
|
def configure_and_run(params = [])
|
31
|
-
config_file =
|
32
|
-
|
33
|
-
|
34
|
-
cli_config ['default-document-attributes'] = {}
|
33
|
+
config_file = Configuration::DEFAULT_CONFIG_FILE_NAME
|
34
|
+
tmp_config = Configuration.new
|
35
|
+
action_wizard = false
|
35
36
|
|
36
37
|
parser = OptionParser.new do |opts|
|
37
|
-
opts.banner =
|
38
|
+
opts.banner = if ENV['OCRA_EXECUTABLE']
|
39
|
+
"Usage: #{ENV['OCRA_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '')} [options]"
|
40
|
+
else
|
41
|
+
"Usage: #{Gem.ruby} #{$PROGRAM_NAME} [options]"
|
42
|
+
end
|
38
43
|
|
39
44
|
opts.on('-c', '--config CONFIG_FILE_NAME', 'Specify custom configuration file,'\
|
40
|
-
" instead of #{
|
45
|
+
" instead of #{Configuration::DEFAULT_CONFIG_FILE_NAME}.") do |file_name|
|
41
46
|
config_file = file_name
|
42
47
|
end
|
43
48
|
|
49
|
+
opts.on('-r', '--register FILE', 'Register a custom plugin, e.g. your own Datasource implementation') do |plugin|
|
50
|
+
require plugin
|
51
|
+
end
|
52
|
+
|
44
53
|
opts.on('-d', '--debug LEVEL', 'Specify detail level: FATAL, ERROR, WARN, INFO, DEBUG.') do |level|
|
45
|
-
|
46
|
-
@logger.level = Object.const_get("::Logger::Severity::#{level}")
|
47
|
-
end
|
54
|
+
tmp_config.set_param('grafana-reporter:debug-level', level)
|
48
55
|
end
|
49
56
|
|
50
57
|
opts.on('-o', '--output FILE', 'Output filename if only a single file is rendered') do |file|
|
51
|
-
|
58
|
+
tmp_config.set_param('to_file', file)
|
52
59
|
end
|
53
60
|
|
54
|
-
opts.on('-s', '--set VARIABLE,VALUE', Array, 'Set a variable value, which will be passed to the
|
55
|
-
|
56
|
-
|
61
|
+
opts.on('-s', '--set VARIABLE,VALUE', Array, 'Set a variable value, which will be passed to the '\
|
62
|
+
'rendering') do |list|
|
63
|
+
raise ParameterValueError, list.length unless list.length == 2
|
64
|
+
|
65
|
+
tmp_config.set_param("default-document-attributes:#{list[0]}", list[1])
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on('--ssl-cert FILE', 'Manually specify a SSL cert file for HTTPS connection to grafana. Only '\
|
69
|
+
'needed if not working properly otherwise.') do |file|
|
70
|
+
if File.file?(file)
|
71
|
+
tmp_config.set_param('grafana-reporter:ssl-cert', file)
|
72
|
+
else
|
73
|
+
config.logger.warn("SSL certificate file #{file} does not exist. Setting will be ignored.")
|
74
|
+
end
|
57
75
|
end
|
58
76
|
|
59
77
|
opts.on('--test GRAFANA_INSTANCE', 'test current configuration against given GRAFANA_INSTANCE') do |instance|
|
60
|
-
|
61
|
-
|
78
|
+
tmp_config.set_param('grafana-reporter:run-mode', 'test')
|
79
|
+
tmp_config.set_param('grafana-reporter:test-instance', instance)
|
62
80
|
end
|
63
81
|
|
64
82
|
opts.on('-t', '--template TEMPLATE', 'Render a single ASCIIDOC template to PDF and exit') do |template|
|
65
|
-
|
66
|
-
|
83
|
+
tmp_config.set_param('grafana-reporter:run-mode', 'single-render')
|
84
|
+
tmp_config.set_param('default-document-attributes:var-template', template)
|
67
85
|
end
|
68
86
|
|
69
87
|
opts.on('-w', '--wizard', 'Configuration wizard to prepare environment for the reporter.') do
|
70
|
-
|
88
|
+
action_wizard = true
|
71
89
|
end
|
72
90
|
|
73
91
|
opts.on('-v', '--version', 'Version information') do
|
@@ -83,31 +101,22 @@ module GrafanaReporter
|
|
83
101
|
|
84
102
|
begin
|
85
103
|
parser.parse!(params)
|
104
|
+
return ConsoleConfigurationWizard.new.start_wizard(config_file, tmp_config) if action_wizard
|
86
105
|
rescue ApplicationError => e
|
87
106
|
puts e.message
|
88
107
|
return -1
|
89
108
|
end
|
90
109
|
|
91
110
|
# abort if config file does not exist
|
92
|
-
unless File.
|
111
|
+
unless File.file?(config_file)
|
93
112
|
puts "Config file '#{config_file}' does not exist. Consider calling the configuration wizard"\
|
94
113
|
' with option \'-w\' or use \'-h\' to see help message. Aborting.'
|
95
114
|
return -1
|
96
115
|
end
|
97
116
|
|
98
|
-
# read config file
|
99
|
-
@config = GrafanaReporter::Configuration.new
|
100
|
-
config.logger = @logger
|
101
|
-
config_hash = nil
|
102
|
-
begin
|
103
|
-
config_hash = YAML.load_file(config_file)
|
104
|
-
rescue StandardError => e
|
105
|
-
raise ConfigurationError, "Could not read config file '#{config_file}' (Error: #{e.message})"
|
106
|
-
end
|
107
|
-
|
108
117
|
# merge command line configuration with read config file
|
109
|
-
|
110
|
-
config.
|
118
|
+
@config.load_config_from_file(config_file)
|
119
|
+
@config.merge!(tmp_config)
|
111
120
|
|
112
121
|
run
|
113
122
|
end
|
@@ -131,274 +140,17 @@ module GrafanaReporter
|
|
131
140
|
|
132
141
|
when Configuration::MODE_SINGLE_RENDER
|
133
142
|
begin
|
134
|
-
config.report_class.new(config
|
143
|
+
config.report_class.new(config).create_report(config.template, config.to_file)
|
135
144
|
rescue StandardError => e
|
136
|
-
puts e.message
|
145
|
+
puts "#{e.message}\n#{e.backtrace.join("\n")}"
|
137
146
|
end
|
138
147
|
|
139
148
|
when Configuration::MODE_SERVICE
|
140
|
-
|
149
|
+
@webservice.run(config)
|
141
150
|
end
|
142
151
|
|
143
152
|
0
|
144
153
|
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
# Provides a command line configuration wizard for setting up the necessary configuration
|
149
|
-
# file.
|
150
|
-
def config_wizard
|
151
|
-
if File.exist?(CONFIG_FILE)
|
152
|
-
input = nil
|
153
|
-
until input
|
154
|
-
input = user_input("Configuration file '#{CONFIG_FILE}' already exists. Do you want to overwrite it?", 'yN')
|
155
|
-
return if input =~ /^(?:n|N|yN)$/
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
puts 'This wizard will guide you through an initial configuration for'\
|
160
|
-
' the ruby-grafana-reporter. The configuration file will be created'\
|
161
|
-
' in the current folder. Please make sure to specify necessary paths'\
|
162
|
-
' either with a relative or an absolute path properly.'
|
163
|
-
puts
|
164
|
-
port = ui_config_port
|
165
|
-
grafana = ui_config_grafana
|
166
|
-
templates = ui_config_templates_folder
|
167
|
-
reports = ui_config_reports_folder
|
168
|
-
images = ui_config_images_folder(templates)
|
169
|
-
retention = ui_config_retention
|
170
|
-
|
171
|
-
config_yaml = %(# This configuration has been built with the configuration wizard.
|
172
|
-
|
173
|
-
#{grafana}
|
174
|
-
|
175
|
-
grafana-reporter:
|
176
|
-
templates-folder: #{templates}
|
177
|
-
reports-folder: #{reports}
|
178
|
-
report-retention: #{retention}
|
179
|
-
webservice-port: #{port}
|
180
|
-
|
181
|
-
default-document-attributes:
|
182
|
-
imagesdir: #{images}
|
183
|
-
# feel free to add here additional asciidoctor document attributes which are applied to all your templates
|
184
|
-
)
|
185
|
-
|
186
|
-
begin
|
187
|
-
File.write(CONFIG_FILE, config_yaml, mode: 'w')
|
188
|
-
puts 'Configuration file successfully created.'
|
189
|
-
rescue StandardError => e
|
190
|
-
raise e
|
191
|
-
end
|
192
|
-
|
193
|
-
config = Configuration.new
|
194
|
-
begin
|
195
|
-
config.config = YAML.load_file(CONFIG_FILE)
|
196
|
-
puts 'Configuration file validated successfully.'
|
197
|
-
rescue StandardError => e
|
198
|
-
raise ConfigurationError, "Could not read config file '#{CONFIG_FILE}' (Error: #{e.message})\n"\
|
199
|
-
"Source:\n#{File.read(CONFIG_FILE)}"
|
200
|
-
end
|
201
|
-
|
202
|
-
# create a demo report
|
203
|
-
unless Dir.exist?(config.templates_folder)
|
204
|
-
puts "Skip creation of DEMO template, as folder '#{config.templates_folder}' does not exist."
|
205
|
-
return
|
206
|
-
end
|
207
|
-
demo_report = %(= First Grafana Report Template
|
208
|
-
|
209
|
-
include::grafana_help[]
|
210
|
-
|
211
|
-
include::grafana_environment[])
|
212
|
-
|
213
|
-
demo_report_file = "#{config.templates_folder}demo_report.adoc"
|
214
|
-
if File.exist?(demo_report_file)
|
215
|
-
puts "Skip creation of DEMO template, as file '#{demo_report_file}' already exists."
|
216
|
-
else
|
217
|
-
begin
|
218
|
-
File.write(demo_report_file, demo_report, mode: 'w')
|
219
|
-
puts "DEMO template '#{demo_report_file}' successfully created."
|
220
|
-
rescue StandardError => e
|
221
|
-
raise e
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
puts
|
226
|
-
puts 'Now everything is setup properly. Run the grafana reporter without any command to start the service.'
|
227
|
-
puts
|
228
|
-
puts ' ruby-grafana-reporter'
|
229
|
-
puts
|
230
|
-
puts "Open 'http://localhost:#{config.webserver_port}/render?var-template=demo_report' in a webbrowser to"
|
231
|
-
puts 'verify your configuration.'
|
232
|
-
end
|
233
|
-
|
234
|
-
def ui_config_grafana
|
235
|
-
valid = false
|
236
|
-
url = nil
|
237
|
-
api_key = nil
|
238
|
-
datasources = ''
|
239
|
-
until valid
|
240
|
-
url ||= user_input('Specify grafana host', 'http://localhost:3000')
|
241
|
-
print "Testing connection to '#{url}' #{api_key ? '_with_' : '_without_'} API key..."
|
242
|
-
begin
|
243
|
-
res = Grafana::Grafana.new(url,
|
244
|
-
api_key,
|
245
|
-
logger: @logger).test_connection
|
246
|
-
rescue StandardError => e
|
247
|
-
puts
|
248
|
-
puts e.message
|
249
|
-
end
|
250
|
-
puts 'done.'
|
251
|
-
|
252
|
-
case res
|
253
|
-
when 'Admin'
|
254
|
-
valid = true
|
255
|
-
|
256
|
-
when 'NON-Admin'
|
257
|
-
print 'Access to grafana is permitted as NON-Admin. Do you want to use an [a]pi key,'\
|
258
|
-
' configure [d]atasource manually, [r]e-enter api key or [i]gnore? [adRi]: '
|
259
|
-
|
260
|
-
case gets
|
261
|
-
when /(?:i|I)$/
|
262
|
-
valid = true
|
263
|
-
|
264
|
-
when /(?:a|A)$/
|
265
|
-
print 'Enter API key: '
|
266
|
-
api_key = gets.sub(/\n$/, '')
|
267
|
-
|
268
|
-
when /(?:r|R|adRi)$/
|
269
|
-
api_key = nil
|
270
|
-
|
271
|
-
when /(?:d|D)$/
|
272
|
-
valid = true
|
273
|
-
datasources = ui_config_datasources
|
274
|
-
|
275
|
-
end
|
276
|
-
|
277
|
-
else
|
278
|
-
print "Grafana could not be accessed at '#{url}'. Do you want do [r]e-enter url, or"\
|
279
|
-
' [i]gnore and proceed? [Ri]: '
|
280
|
-
|
281
|
-
case gets
|
282
|
-
when /(?:i|I)$/
|
283
|
-
valid = true
|
284
|
-
|
285
|
-
else
|
286
|
-
url = nil
|
287
|
-
api_key = nil
|
288
|
-
|
289
|
-
end
|
290
|
-
|
291
|
-
end
|
292
|
-
end
|
293
|
-
%(grafana:
|
294
|
-
default:
|
295
|
-
host: #{url}#{api_key ? "\n api_key: #{api_key}" : ''}#{datasources ? "\n#{datasources}" : ''}
|
296
|
-
)
|
297
|
-
end
|
298
|
-
|
299
|
-
def ui_config_datasources
|
300
|
-
finished = false
|
301
|
-
datasources = []
|
302
|
-
until finished
|
303
|
-
item = {}
|
304
|
-
print "Datasource ###{datasources.length + 1}) Enter datasource name as configured in grafana: "
|
305
|
-
item[:ds_name] = gets.sub(/\n$/, '')
|
306
|
-
print "Datasource ###{datasources.length + 1}) Enter datasource id: "
|
307
|
-
item[:ds_id] = gets.sub(/\n$/, '')
|
308
|
-
|
309
|
-
puts
|
310
|
-
selection = user_input("Datasource name: '#{item[:ds_name]}', Datasource id: '#{item[:ds_id]}'."\
|
311
|
-
' [A]ccept, [r]etry or [c]ancel?', 'Arc')
|
312
|
-
|
313
|
-
case selection
|
314
|
-
when /(?:Arc|A|a)$/
|
315
|
-
datasources << item
|
316
|
-
another = user_input('Add [a]nother datasource or [d]one?', 'aD')
|
317
|
-
finished = true if another =~ /(?:d|D)$/
|
318
|
-
|
319
|
-
when /(?:c|C)$/
|
320
|
-
finished = true
|
321
|
-
|
322
|
-
end
|
323
|
-
end
|
324
|
-
" datasources:\n#{datasources.collect { |el| " #{el[:ds_name]}: #{el[:ds_id]}" }.join('\n')}"
|
325
|
-
end
|
326
|
-
|
327
|
-
def ui_config_port
|
328
|
-
input = nil
|
329
|
-
until input
|
330
|
-
input = user_input('Specify port on which reporter shall run', '8815')
|
331
|
-
input = nil unless input =~ /[0-9]+/
|
332
|
-
end
|
333
|
-
input
|
334
|
-
end
|
335
|
-
|
336
|
-
def ui_config_templates_folder
|
337
|
-
input = nil
|
338
|
-
until input
|
339
|
-
input = user_input('Specify path where templates shall be stored', './templates')
|
340
|
-
input = nil unless validate_config_folder(input)
|
341
|
-
end
|
342
|
-
input
|
343
|
-
end
|
344
|
-
|
345
|
-
def ui_config_reports_folder
|
346
|
-
input = nil
|
347
|
-
until input
|
348
|
-
input = user_input('Specify path where created reports shall be stored', './reports')
|
349
|
-
input = nil unless validate_config_folder(input)
|
350
|
-
end
|
351
|
-
input
|
352
|
-
end
|
353
|
-
|
354
|
-
def ui_config_images_folder(parent)
|
355
|
-
input = nil
|
356
|
-
until input
|
357
|
-
input = user_input('Specify path where rendered images shall be stored (relative to templates folder)',
|
358
|
-
'./images')
|
359
|
-
input = nil unless validate_config_folder(File.join(parent, input))
|
360
|
-
end
|
361
|
-
input
|
362
|
-
end
|
363
|
-
|
364
|
-
def ui_config_retention
|
365
|
-
input = nil
|
366
|
-
until input
|
367
|
-
input = user_input('Specify report retention duration in hours', '24')
|
368
|
-
input = nil unless input =~ /[0-9]+/
|
369
|
-
end
|
370
|
-
input
|
371
|
-
end
|
372
|
-
|
373
|
-
def user_input(text, default)
|
374
|
-
print "#{text} [#{default}]: "
|
375
|
-
input = gets.gsub(/\n$/, '')
|
376
|
-
input = default if input.empty?
|
377
|
-
input
|
378
|
-
end
|
379
|
-
|
380
|
-
def validate_config_folder(folder)
|
381
|
-
return true if Dir.exist?(folder)
|
382
|
-
|
383
|
-
print "Directory '#{folder} does not exist: [c]reate, [r]e-enter path or [i]gnore? [cRi]: "
|
384
|
-
case gets
|
385
|
-
when /^(?:c|C)$/
|
386
|
-
begin
|
387
|
-
Dir.mkdir(folder)
|
388
|
-
puts "Directory '#{folder}' successfully created."
|
389
|
-
return true
|
390
|
-
rescue StandardError => e
|
391
|
-
puts "WARN: Directory '#{folder}' could not be created. Please create it manually."
|
392
|
-
puts e.message
|
393
|
-
end
|
394
|
-
|
395
|
-
when /^(?:i|I)$/
|
396
|
-
puts "WARN: Directory '#{folder}' does not exist. Please create manually."
|
397
|
-
return true
|
398
|
-
end
|
399
|
-
|
400
|
-
false
|
401
|
-
end
|
402
154
|
end
|
403
155
|
end
|
404
156
|
end
|