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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +86 -245
  3. data/bin/ruby-grafana-reporter +3 -2
  4. data/lib/VERSION.rb +6 -3
  5. data/lib/grafana/abstract_datasource.rb +116 -0
  6. data/lib/grafana/dashboard.rb +75 -66
  7. data/lib/grafana/errors.rb +81 -61
  8. data/lib/grafana/grafana.rb +130 -131
  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 +44 -0
  13. data/lib/grafana/image_rendering_datasource.rb +44 -0
  14. data/lib/grafana/panel.rb +47 -39
  15. data/lib/grafana/prometheus_datasource.rb +39 -0
  16. data/lib/grafana/sql_datasource.rb +65 -0
  17. data/lib/grafana/variable.rb +218 -259
  18. data/lib/grafana/webrequest.rb +71 -0
  19. data/lib/grafana_reporter/abstract_query.rb +401 -0
  20. data/lib/grafana_reporter/abstract_report.rb +163 -109
  21. data/lib/grafana_reporter/alerts_table_query.rb +44 -0
  22. data/lib/grafana_reporter/annotations_table_query.rb +43 -0
  23. data/lib/grafana_reporter/application/application.rb +162 -229
  24. data/lib/grafana_reporter/application/errors.rb +33 -30
  25. data/lib/grafana_reporter/application/webservice.rb +242 -0
  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 +76 -0
  29. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +77 -0
  30. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +72 -0
  31. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +98 -0
  32. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
  33. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +23 -0
  34. data/lib/grafana_reporter/asciidoctor/report.rb +172 -159
  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 +310 -326
  41. data/lib/grafana_reporter/console_configuration_wizard.rb +319 -0
  42. data/lib/grafana_reporter/demo_report_wizard.rb +87 -0
  43. data/lib/grafana_reporter/errors.rb +81 -38
  44. data/lib/grafana_reporter/help.rb +447 -0
  45. data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
  46. data/lib/grafana_reporter/panel_image_query.rb +29 -0
  47. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  48. data/lib/grafana_reporter/query_value_query.rb +79 -0
  49. data/lib/grafana_reporter/report_webhook.rb +35 -0
  50. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +29 -27
  51. metadata +48 -60
  52. data/lib/grafana/abstract_panel_query.rb +0 -20
  53. data/lib/grafana/abstract_query.rb +0 -127
  54. data/lib/grafana/abstract_sql_query.rb +0 -42
  55. data/lib/grafana/panel_image_query.rb +0 -49
  56. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -99
  57. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
  58. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -37
  59. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -86
  60. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -86
  61. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -67
  62. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -65
  63. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -58
  64. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -75
  65. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -70
  66. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -18
  67. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -41
  68. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -202
  69. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -67
  70. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -65
  71. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -57
  72. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -32
  73. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -23
  74. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -43
  75. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -36
  76. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -309
  77. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -34
  78. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -32
@@ -1,109 +1,163 @@
1
- module GrafanaReporter
2
- # @abstract Override {#create_report} and {#progress}.
3
- #
4
- # This class is used to build a report on basis of a given configuration and
5
- # template.
6
- #
7
- # Objects of this class are also stored in {Application::Application}, unless
8
- # the retention time is over.
9
- class AbstractReport
10
- # @return [String] path to the template
11
- attr_reader :template
12
-
13
- # @return [Time] time, when the report generation started
14
- attr_reader :start_time
15
-
16
- # @return [Time] time, when the report generation ended
17
- attr_reader :end_time
18
-
19
- # @return [Logger] logger object used during report generation
20
- attr_reader :logger
21
-
22
- # @return [Boolean] true, if the report is or shall be cancelled
23
- attr_reader :cancel
24
-
25
- # @return [Boolen] true, if the report generation is finished (successfull or not)
26
- attr_reader :done
27
-
28
- # @param config [Configuration] configuration object
29
- # @param template [String] path to the template to be used
30
- # @param destination_file_or_path [String or File] path to the destination report or file object to use
31
- # @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
32
- def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
33
- @config = config
34
- @logger = Logger::TwoWayDelegateLogger.new
35
- @logger.additional_logger = @config.logger
36
- @done = false
37
- @template = template
38
- @destination_file_or_path = destination_file_or_path
39
- @custom_attributes = custom_attributes
40
- @start_time = nil
41
- @end_time = nil
42
- @cancel = false
43
- raise MissingTemplateError, @template.to_s unless File.exist?(@template.to_s)
44
- end
45
-
46
- # Call to request cancelling the report generation.
47
- # @return [void]
48
- def cancel!
49
- @cancel = true
50
- logger.info('Cancelling report generation invoked.')
51
- end
52
-
53
- # @return [String] path to the report destination file
54
- def path
55
- @destination_file_or_path.respond_to?(:path) ? @destination_file_or_path.path : @destination_file_or_path
56
- end
57
-
58
- # Deletes the report file object.
59
- # @return [void]
60
- def delete_file
61
- if @destination_file_or_path.is_a?(Tempfile)
62
- @destination_file_or_path.unlink
63
- elsif @destination_file_or_path.is_a?(File)
64
- @destination_file_or_path.delete
65
- end
66
- @destination_file_or_path = nil
67
- end
68
-
69
- # @return [Float] time in seconds, that the report generation took
70
- def execution_time
71
- return nil if start_time.nil?
72
- return end_time - start_time unless end_time.nil?
73
-
74
- Time.now - start_time
75
- end
76
-
77
- # @return [Array] error messages during report generation.
78
- def error
79
- @error || []
80
- end
81
-
82
- # @return [String] status of the report, one of 'in progress', 'cancelled', 'died' or 'finished'.
83
- def status
84
- return 'cancelled' if done && cancel
85
- return 'finished' if done && error.empty?
86
- return 'died' if done && !error.empty?
87
-
88
- 'in progress'
89
- end
90
-
91
- # @return [String] string containing all messages ([Logger::Severity::DEBUG]) of the logger during report generation.
92
- def full_log
93
- logger.internal_messages
94
- end
95
-
96
- # @abstract
97
- # Is being called to start the report generation.
98
- # @return [void]
99
- def create_report
100
- raise NotImplementedError
101
- end
102
-
103
- # @abstract
104
- # @return [Integer] number between 0 and 100, representing the current progress of the report creation.
105
- def progress
106
- raise NotImplementedError
107
- end
108
- end
109
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # @abstract Override {#create_report} and {#progress}.
5
+ #
6
+ # This class is used to build a report on basis of a given configuration and
7
+ # template.
8
+ #
9
+ # Objects of this class are also stored in {Application::Application}, unless
10
+ # the retention time is over.
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
+
19
+ # @return [String] path to the template
20
+ attr_reader :template
21
+
22
+ # @return [Time] time, when the report generation started
23
+ attr_reader :start_time
24
+
25
+ # @return [Time] time, when the report generation ended
26
+ attr_reader :end_time
27
+
28
+ # @return [Logger] logger object used during report generation
29
+ attr_reader :logger
30
+
31
+ # @return [Boolean] true, if the report is or shall be cancelled
32
+ attr_reader :cancel
33
+
34
+ # @return [Boolen] true, if the report generation is finished (successfull or not)
35
+ attr_reader :done
36
+
37
+ # @param config [Configuration] configuration object
38
+ # @param template [String] path to the template to be used
39
+ # @param destination_file_or_path [String or File] path to the destination report or file object to use
40
+ # @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
41
+ def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
42
+ @config = config
43
+ @logger = Logger::TwoWayDelegateLogger.new
44
+ @logger.additional_logger = @config.logger
45
+ @done = false
46
+ @template = template
47
+ @destination_file_or_path = destination_file_or_path
48
+ @custom_attributes = custom_attributes
49
+ @start_time = nil
50
+ @end_time = nil
51
+ @cancel = false
52
+ raise MissingTemplateError, @template.to_s unless File.exist?(@template.to_s)
53
+ end
54
+
55
+ # Registers a new event listener object.
56
+ # @param event [Symbol] one of EVENT_CALLBACKS
57
+ # @param listener [Object] object responding to #callback(event_symbol, object)
58
+ def self.add_event_listener(event, listener)
59
+ @@event_listeners[event] = [] if @@event_listeners[event] == []
60
+ @@event_listeners[event].push(listener)
61
+ end
62
+
63
+ # Removes all registeres event listener objects
64
+ def self.clear_event_listeners
65
+ @@event_listeners = {}
66
+ @@event_listeners.default = []
67
+ end
68
+
69
+ # Call to request cancelling the report generation.
70
+ # @return [void]
71
+ def cancel!
72
+ @cancel = true
73
+ logger.info('Cancelling report generation invoked.')
74
+ notify(:on_after_cancel)
75
+ end
76
+
77
+ # @return [String] path to the report destination file
78
+ def path
79
+ @destination_file_or_path.respond_to?(:path) ? @destination_file_or_path.path : @destination_file_or_path
80
+ end
81
+
82
+ # Deletes the report file object.
83
+ # @return [void]
84
+ def delete_file
85
+ if @destination_file_or_path.is_a?(Tempfile)
86
+ @destination_file_or_path.unlink
87
+ elsif @destination_file_or_path.is_a?(File)
88
+ @destination_file_or_path.delete
89
+ end
90
+ @destination_file_or_path = nil
91
+ end
92
+
93
+ # @return [Float] time in seconds, that the report generation took
94
+ def execution_time
95
+ return nil if start_time.nil?
96
+ return end_time - start_time unless end_time.nil?
97
+
98
+ Time.now - start_time
99
+ end
100
+
101
+ # @return [Array] error messages during report generation.
102
+ def error
103
+ @error || []
104
+ end
105
+
106
+ # @return [String] status of the report as string, either 'not started', 'in progress', 'cancelling',
107
+ # 'cancelled', 'died' or 'finished'.
108
+ def status
109
+ return 'not started' unless @start_time
110
+ return 'cancelled' if done && cancel
111
+ return 'cancelling' if !done && cancel
112
+ return 'finished' if done && error.empty?
113
+ return 'died' if done && !error.empty?
114
+
115
+ 'in progress'
116
+ end
117
+
118
+ # @return [String] string containing all messages ([Logger::Severity::DEBUG]) of the logger during report
119
+ # generation.
120
+ def full_log
121
+ logger.internal_messages
122
+ end
123
+
124
+ # Is being called to start the report generation.
125
+ # @return [void]
126
+ def create_report
127
+ notify(:on_before_create)
128
+ @start_time = Time.new
129
+ logger.info("Report started at #{@start_time}")
130
+ end
131
+
132
+ # @abstract
133
+ # @return [Integer] number between 0 and 100, representing the current progress of the report creation.
134
+ def progress
135
+ raise NotImplementedError
136
+ end
137
+
138
+ private
139
+
140
+ def done!
141
+ @done = true
142
+ @end_time = Time.new
143
+ logger.info("Report creation ended after #{@end_time - @start_time} seconds with status '#{status}'")
144
+ notify(:on_after_finish)
145
+ end
146
+
147
+ def notify(event)
148
+ (@@event_listeners[:all] + @@event_listeners[event]).each do |listener|
149
+ logger.debug("Informing event listener '#{listener.class}' about event '#{event}' for report '#{object_id}'.")
150
+ begin
151
+ res = listener.callback(event, self)
152
+ logger.debug("Event listener '#{listener.class}' for event '#{event}' and report '#{object_id}' returned "\
153
+ "with result '#{res}'.")
154
+ rescue StandardError => e
155
+ msg = "Event listener '#{listener.class}' for event '#{event}' and report '#{object_id}' returned with "\
156
+ "error: #{e.message} - #{e.backtrace}."
157
+ puts msg
158
+ logger.error(msg)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ 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 {QueryMixin#format_columns}, {QueryMixin#replace_values} and
33
+ # {QueryMixin#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 {QueryMixin#format_columns}, {QueryMixin#replace_values} and
32
+ # {QueryMixin#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
@@ -1,229 +1,162 @@
1
- module GrafanaReporter
2
- # This module contains all classes, which are used by the grafana reporter
3
- # application. The application is a set of classes, which allows to run the
4
- # reporter in several ways.
5
- #
6
- # If you intend to use the reporter functionality, without the application,
7
- # it might be helpful to not use the classes from here.
8
- module Application
9
- # This class contains the main application to run the grafana reporter.
10
- #
11
- # It can be run to test the grafana connection, render a single template
12
- # or run as a service.
13
- class Application
14
- def initialize
15
- @logger = ::Logger.new(STDERR, level: :unknown)
16
- @reports = []
17
- end
18
-
19
- # Can be used to set a {Configuration} object to the application.
20
- #
21
- # This is mainly helpful in testing the application or in an
22
- # integrated use.
23
- # @param config {Configuration} configuration to be used by the application
24
- # @return [void]
25
- def config=(config)
26
- @logger = config.logger || @logger
27
- @config = config
28
- end
29
-
30
- # This is the main method, which is called, if the application is
31
- # run in standalone mode.
32
- # @param params [Array] normally the ARGV command line parameters
33
- # @return [Integer] see {#run}
34
- def configure_and_run(params = [])
35
- config = GrafanaReporter::Configuration.new
36
- config.logger.level = ::Logger::Severity::INFO
37
- result = config.configure_by_command_line(params)
38
- return result if result != 0
39
-
40
- self.config = config
41
- run
42
- end
43
-
44
- # Runs the application with the current set {Configuration} object.
45
- # @return [Integer] value smaller than 0, if error. 0 if successfull
46
- def run
47
- begin
48
- @config.validate
49
- rescue ConfigurationError => e
50
- puts e.message
51
- return -2
52
- end
53
-
54
- case @config.mode
55
- when Configuration::MODE_CONNECTION_TEST
56
- res = Grafana::Grafana.new(@config.grafana_host(@config.test_instance), @config.grafana_api_key(@config.test_instance), logger: @logger).test_connection
57
- puts res
58
-
59
- when Configuration::MODE_SINGLE_RENDER
60
- @config.report_class.new(@config, @config.template, @config.to_file).create_report
61
-
62
- when Configuration::MODE_SERVICE
63
- run_webserver
64
- end
65
- 0
66
- end
67
-
68
- private
69
-
70
- def clean_outdated_temporary_reports
71
- clean_time = Time.now - 60 * 60 * @config.report_retention
72
- @reports.select { |report| report.done && clean_time > report.end_time }.each do |report|
73
- @reports.delete(report).delete_file
74
- end
75
- end
76
-
77
- def run_webserver
78
- # start webserver
79
- server = TCPServer.new(@config.webserver_port)
80
- @logger.info("Server listening on port #{@config.webserver_port}...")
81
-
82
- @progress_reporter = Thread.new {}
83
-
84
- loop do
85
- # step 1) accept incoming connection
86
- socket = server.accept
87
-
88
- # step 2) print the request headers (separated by a blank line e.g. \r\n)
89
- request = ''
90
- line = ''
91
- begin
92
- until line == "\r\n"
93
- line = socket.readline
94
- request += line
95
- end
96
- rescue EOFError => e
97
- @logger.debug("Webserver EOFError: #{e.message}")
98
- end
99
-
100
- begin
101
- response = handle_request(request)
102
- socket.write response
103
- rescue WebserviceUnknownPathError => e
104
- @logger.debug(e.message)
105
- socket.write http_response(404, '', e.message)
106
- rescue MissingTemplateError => e
107
- @logger.error(e.message)
108
- socket.write http_response(400, 'Bad Request', e.message)
109
- rescue WebserviceGeneralRenderingError => e
110
- @logger.fatal(e.message)
111
- socket.write http_response(400, 'Bad Request', e.message)
112
- rescue StandardError => e
113
- @logger.fatal(e.message)
114
- socket.write http_response(400, 'Bad Request', e.message)
115
- ensure
116
- socket.close
117
- end
118
-
119
- unless @progress_reporter.alive?
120
- @progress_reporter = Thread.new do
121
- running_reports = @reports.reject(&:done)
122
- until running_reports.empty?
123
- @logger.info("#{running_reports.length} report(s) in progress: #{running_reports.map { |report| (report.progress * 100).to_i.to_s + '% (running ' + report.execution_time.to_i.to_s + ' secs)' }.join(', ')}") unless running_reports.empty?
124
- sleep 5
125
- running_reports = @reports.reject(&:done)
126
- end
127
- # puts "no more running reports - stopping to report progress"
128
- end
129
- end
130
-
131
- clean_outdated_temporary_reports
132
- end
133
- end
134
-
135
- def handle_request(request)
136
- raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.nil?
137
- raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.split("\r\n")[0].nil?
138
-
139
- query_string = request.split("\r\n")[0].gsub(%r{(?:[^\?]+[\?])(.*)(?: HTTP/.*)$}, '\1')
140
- query_parameters = CGI.parse(query_string)
141
-
142
- @logger.debug("Received request: #{request.split("\r\n")[0]}")
143
- @logger.debug('query_parameters: ' + query_parameters.to_s)
144
-
145
- # read URL parameters
146
- attrs = {}
147
- query_parameters.each do |k, v|
148
- attrs[k] = v.length == 1 ? v[0] : v
149
- end
150
-
151
- if request.split("\r\n")[0] =~ %r{^GET /render[\? ]}
152
- # build report
153
- template_file = @config.templates_folder.to_s + attrs['var-template'].to_s + '.adoc'
154
-
155
- file = Tempfile.new('gf_pdf_', @config.reports_folder)
156
- begin
157
- FileUtils.chmod('+r', file.path)
158
- rescue StandardError => e
159
- @logger.debug("File permissions could not be set for #{file.path}: #{e.message}")
160
- end
161
-
162
- report = @config.report_class.new(@config, template_file, file, attrs)
163
- Thread.new do
164
- report.create_report
165
- end
166
- @reports << report
167
-
168
- return http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
169
-
170
- elsif request.split("\r\n")[0] =~ %r{^GET /overview[\? ]}
171
- # show overview for current reports
172
- return get_reports_status_as_html(@reports)
173
-
174
- elsif request.split("\r\n")[0] =~ %r{^GET /view_report[\? ]}
175
- # view report if already available, or show status view
176
- report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
177
- raise WebserviceGeneralRenderingError, 'view_report has been called without valid id' if report.nil?
178
-
179
- # show report status
180
- return get_reports_status_as_html([report]) if !report.done || !report.error.empty?
181
-
182
- # provide report
183
- @logger.debug("Returning PDF report at #{report.path}")
184
- content = File.read(report.path)
185
- return http_response(200, 'OK', content, "Content-Type": 'application/pdf') if content.start_with?("%PDF")
186
- # TODO properly provide file as zip
187
- return http_response(200, 'OK', content, "Content-Type": 'application/octet-stream', "Content-Disposition": "attachment; filename=report.zip")
188
-
189
- elsif request.split("\r\n")[0] =~ %r{^GET /cancel_report[\? ]}
190
- # view report if already available, or show status view
191
- report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
192
- raise WebserviceGeneralRenderingError, 'cancel_report has been called without valid id' if report.nil?
193
-
194
- report.cancel! unless report.done
195
-
196
- # redirect to view_report page
197
- return http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
198
-
199
- elsif request.split("\r\n")[0] =~ %r{^GET /view_log[\? ]}
200
- # view report if already available, or show status view
201
- report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
202
- raise WebserviceGeneralRenderingError, 'view_log has been called without valid id' if report.nil?
203
-
204
- content = report.full_log
205
-
206
- return http_response(200, 'OK', content, "Content-Type": 'text/plain')
207
- end
208
-
209
- raise WebserviceUnknownPathError, request.split("\r\n")[0]
210
- end
211
-
212
- def get_reports_status_as_html(reports)
213
- i = reports.length
214
-
215
- content = '<html><head></head><body><table><thead><th>#</th><th>Start Time</th><th>End Time</th><th>Template</th><th>Execution time</th><th>Status</th><th>Error</th><th>Action</th></thead>' +
216
- reports.reverse.map do |report|
217
- "<tr><td>#{(i -= 1)}</td><td>#{report.start_time}</td><td>#{report.end_time}</td><td>#{report.template}</td><td>#{report.execution_time.to_i} secs</td><td>#{report.status} (#{(report.progress * 100).to_i}%)</td><td>#{report.error.join('<br>')}</td><td>#{!report.done && !report.cancel ? "<a href=\"/cancel_report?report_id=#{report.object_id}\">Cancel</a>&nbsp;" : ''}#{(report.status == 'finished') || (report.status == 'cancelled') ? "<a href=\"/view_report?report_id=#{report.object_id}\">View</a>&nbsp;" : '&nbsp;'}<a href=\"/view_log?report_id=#{report.object_id}\">Log</a></td></tr>"
218
- end.join('') +
219
- '</table></body></html>'
220
-
221
- http_response(200, 'OK', content, "Content-Type": 'text/html')
222
- end
223
-
224
- def http_response(code, text, body, opts = {})
225
- "HTTP/1.1 #{code} #{text}\r\n#{opts.map { |k, v| "#{k}: #{v}" }.join("\r\n")}#{body ? "\r\nContent-Length: #{body.to_s.bytesize}" : ''}\r\n\r\n#{body}"
226
- end
227
- end
228
- end
229
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This module contains all classes, which are used by the grafana reporter
5
+ # application. The application is a set of classes, which allows to run the
6
+ # reporter in several ways.
7
+ #
8
+ # If you intend to use the reporter functionality, without the application,
9
+ # it might be helpful to not use the classes from here.
10
+ module Application
11
+ # This class contains the main application to run the grafana reporter.
12
+ #
13
+ # It can be run to test the grafana connection, render a single template
14
+ # or run as a service.
15
+ class Application
16
+ # Default file name for grafana reporter configuration file
17
+ CONFIG_FILE = 'grafana_reporter.config'
18
+
19
+ # Contains the {Configuration} object of the application.
20
+ attr_accessor :config
21
+
22
+ # Stores the {Webservice} object of the application
23
+ attr_reader :webservice
24
+
25
+ def initialize
26
+ @config = Configuration.new
27
+ @webservice = Webservice.new
28
+ end
29
+
30
+ # This is the main method, which is called, if the application is
31
+ # run in standalone mode.
32
+ # @param params [Array<String>] command line parameters, mainly ARGV can be used.
33
+ # @return [Integer] 0 if everything is fine, -1 if execution aborted.
34
+ def configure_and_run(params = [])
35
+ config_file = CONFIG_FILE
36
+ tmp_config = Configuration.new
37
+ action_wizard = false
38
+
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = if ENV['OCRA_EXECUTABLE']
41
+ "Usage: #{ENV['OCRA_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '')} [options]"
42
+ else
43
+ "Usage: #{Gem.ruby} #{$PROGRAM_NAME} [options]"
44
+ end
45
+
46
+ opts.on('-c', '--config CONFIG_FILE_NAME', 'Specify custom configuration file,'\
47
+ " instead of #{CONFIG_FILE}.") do |file_name|
48
+ config_file = file_name
49
+ end
50
+
51
+ opts.on('-d', '--debug LEVEL', 'Specify detail level: FATAL, ERROR, WARN, INFO, DEBUG.') do |level|
52
+ tmp_config.set_param('grafana-reporter:debug-level', level)
53
+ end
54
+
55
+ opts.on('-o', '--output FILE', 'Output filename if only a single file is rendered') do |file|
56
+ tmp_config.set_param('to_file', file)
57
+ end
58
+
59
+ opts.on('-s', '--set VARIABLE,VALUE', Array, 'Set a variable value, which will be passed to the '\
60
+ 'rendering') do |list|
61
+ raise ParameterValueError, list.length unless list.length == 2
62
+
63
+ tmp_config.set_param("default-document-attributes:#{list[0]}", list[1])
64
+ end
65
+
66
+ opts.on('--ssl-cert FILE', 'Manually specify a SSL cert file for HTTPS connection to grafana. Only '\
67
+ 'needed if not working properly otherwise.') do |file|
68
+ if File.exist?(file)
69
+ tmp_config.set_param('grafana-reporter:ssl-cert', file)
70
+ else
71
+ config.logger.warn("SSL certificate file #{file} does not exist. Setting will be ignored.")
72
+ end
73
+ end
74
+
75
+ opts.on('--test GRAFANA_INSTANCE', 'test current configuration against given GRAFANA_INSTANCE') do |instance|
76
+ tmp_config.set_param('grafana-reporter:run-mode', 'test')
77
+ tmp_config.set_param('grafana-reporter:test-instance', instance)
78
+ end
79
+
80
+ opts.on('-t', '--template TEMPLATE', 'Render a single ASCIIDOC template to PDF and exit') do |template|
81
+ tmp_config.set_param('grafana-reporter:run-mode', 'single-render')
82
+ tmp_config.set_param('default-document-attributes:var-template', template)
83
+ end
84
+
85
+ opts.on('-w', '--wizard', 'Configuration wizard to prepare environment for the reporter.') do
86
+ action_wizard = true
87
+ end
88
+
89
+ opts.on('-v', '--version', 'Version information') do
90
+ puts GRAFANA_REPORTER_VERSION.join('.')
91
+ return -1
92
+ end
93
+
94
+ opts.on('-h', '--help', 'Show this message') do
95
+ puts opts
96
+ return -1
97
+ end
98
+ end
99
+
100
+ begin
101
+ parser.parse!(params)
102
+ return ConsoleConfigurationWizard.new.start_wizard(config_file, tmp_config) if action_wizard
103
+ rescue ApplicationError => e
104
+ puts e.message
105
+ return -1
106
+ end
107
+
108
+ # abort if config file does not exist
109
+ unless File.exist?(config_file)
110
+ puts "Config file '#{config_file}' does not exist. Consider calling the configuration wizard"\
111
+ ' with option \'-w\' or use \'-h\' to see help message. Aborting.'
112
+ return -1
113
+ end
114
+
115
+ # read config file
116
+ config_hash = nil
117
+ begin
118
+ config_hash = YAML.load_file(config_file)
119
+ rescue StandardError => e
120
+ raise ConfigurationError, "Could not read config file '#{config_file}' (Error: #{e.message})"
121
+ end
122
+
123
+ # merge command line configuration with read config file
124
+ @config.config = config_hash
125
+ @config.merge!(tmp_config)
126
+
127
+ run
128
+ end
129
+
130
+ # Runs the application with the current set {Configuration} object.
131
+ # @return [Integer] value smaller than 0, if error. 0 if successfull
132
+ def run
133
+ begin
134
+ config.validate
135
+ rescue ConfigurationError => e
136
+ puts e.message
137
+ return -2
138
+ end
139
+
140
+ case config.mode
141
+ when Configuration::MODE_CONNECTION_TEST
142
+ res = Grafana::Grafana.new(config.grafana_host(config.test_instance),
143
+ config.grafana_api_key(config.test_instance),
144
+ logger: config.logger).test_connection
145
+ puts res
146
+
147
+ when Configuration::MODE_SINGLE_RENDER
148
+ begin
149
+ config.report_class.new(config, config.template, config.to_file).create_report
150
+ rescue StandardError => e
151
+ puts "#{e.message}\n#{e.backtrace.join("\n")}"
152
+ end
153
+
154
+ when Configuration::MODE_SERVICE
155
+ @webservice.run(config)
156
+ end
157
+
158
+ 0
159
+ end
160
+ end
161
+ end
162
+ end