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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +105 -86
  3. data/bin/ruby-grafana-reporter +5 -5
  4. data/lib/VERSION.rb +3 -2
  5. data/lib/grafana/abstract_datasource.rb +136 -0
  6. data/lib/grafana/dashboard.rb +21 -23
  7. data/lib/grafana/errors.rb +8 -1
  8. data/lib/grafana/grafana.rb +61 -65
  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 +25 -0
  12. data/lib/grafana/graphite_datasource.rb +50 -0
  13. data/lib/grafana/image_rendering_datasource.rb +44 -0
  14. data/lib/grafana/panel.rb +9 -3
  15. data/lib/grafana/prometheus_datasource.rb +45 -0
  16. data/lib/grafana/sql_datasource.rb +71 -0
  17. data/lib/grafana/unsupported_datasource.rb +7 -0
  18. data/lib/grafana/variable.rb +3 -2
  19. data/lib/grafana/webrequest.rb +71 -0
  20. data/lib/grafana_reporter/abstract_query.rb +359 -0
  21. data/lib/grafana_reporter/abstract_report.rb +119 -17
  22. data/lib/grafana_reporter/alerts_table_query.rb +44 -0
  23. data/lib/grafana_reporter/annotations_table_query.rb +43 -0
  24. data/lib/grafana_reporter/application/application.rb +49 -297
  25. data/lib/grafana_reporter/application/webservice.rb +49 -14
  26. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +90 -0
  27. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +89 -0
  28. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +77 -0
  29. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +79 -0
  30. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +73 -0
  31. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +99 -0
  32. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
  33. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +64 -0
  34. data/lib/grafana_reporter/asciidoctor/report.rb +47 -76
  35. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
  36. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
  37. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +92 -0
  38. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +88 -0
  39. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
  40. data/lib/grafana_reporter/configuration.rb +108 -43
  41. data/lib/grafana_reporter/console_configuration_wizard.rb +311 -0
  42. data/lib/grafana_reporter/demo_report_wizard.rb +101 -0
  43. data/lib/grafana_reporter/erb/report.rb +43 -0
  44. data/lib/grafana_reporter/errors.rb +41 -0
  45. data/lib/grafana_reporter/help.rb +443 -0
  46. data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
  47. data/lib/grafana_reporter/panel_image_query.rb +29 -0
  48. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  49. data/lib/grafana_reporter/query_value_query.rb +79 -0
  50. data/lib/grafana_reporter/report_webhook.rb +35 -0
  51. data/lib/ruby_grafana_extension.rb +8 -0
  52. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +13 -0
  53. metadata +47 -43
  54. data/lib/grafana/abstract_panel_query.rb +0 -22
  55. data/lib/grafana/abstract_query.rb +0 -132
  56. data/lib/grafana/abstract_sql_query.rb +0 -51
  57. data/lib/grafana/panel_image_query.rb +0 -52
  58. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -104
  59. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -99
  60. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
  61. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
  62. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
  63. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
  64. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
  65. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
  66. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
  67. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
  68. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
  69. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
  70. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -202
  71. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
  72. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
  73. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -61
  74. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -34
  75. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -25
  76. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
  77. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -38
  78. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -310
  79. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -37
  80. 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
- # @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,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
- raise NotImplementedError
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
- # @abstract
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 = CONFIG_FILE
32
- cli_config = {}
33
- cli_config ['grafana-reporter'] = {}
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 = "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
38
43
 
39
44
  opts.on('-c', '--config CONFIG_FILE_NAME', 'Specify custom configuration file,'\
40
- " instead of #{CONFIG_FILE}.") do |file_name|
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
- if level =~ /(?:FATAL|ERROR|WARN|INFO|DEBUG)/
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
- cli_config['to_file'] = file
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 rendering') do |list|
55
- raise ParameterValueError.new(list.length) unless list.length == 2
56
- cli_config['default-document-attributes'][list[0]] = list[1]
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
- cli_config['grafana-reporter']['run-mode'] = 'test'
61
- cli_config['grafana-reporter']['test-instance'] = instance
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
- cli_config['grafana-reporter']['run-mode'] = 'single-render'
66
- cli_config['default-document-attributes']['var-template'] = template
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
- return config_wizard
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.exist?(config_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
- config_hash.merge!(cli_config) { |_key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2) : v2 }
110
- config.config = config_hash
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, config.template, config.to_file).create_report
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
- Webservice.new(config).run
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