ruby-grafana-reporter 0.2.2 → 0.4.3

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