projectlocker_errata 0.0.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 (82) hide show
  1. data/CHANGELOG +908 -0
  2. data/Gemfile +3 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +23 -0
  6. data/README.md +460 -0
  7. data/README_FOR_HEROKU_ADDON.md +94 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +38 -0
  10. data/TESTING.md +41 -0
  11. data/features/metal.feature +18 -0
  12. data/features/rack.feature +60 -0
  13. data/features/rails.feature +272 -0
  14. data/features/rails_with_js_notifier.feature +97 -0
  15. data/features/rake.feature +27 -0
  16. data/features/sinatra.feature +29 -0
  17. data/features/step_definitions/file_steps.rb +10 -0
  18. data/features/step_definitions/metal_steps.rb +23 -0
  19. data/features/step_definitions/rack_steps.rb +23 -0
  20. data/features/step_definitions/rails_application_steps.rb +478 -0
  21. data/features/step_definitions/rake_steps.rb +17 -0
  22. data/features/support/env.rb +18 -0
  23. data/features/support/matchers.rb +35 -0
  24. data/features/support/projectlocker_errata_shim.rb.template +16 -0
  25. data/features/support/rails.rb +201 -0
  26. data/features/support/rake/Rakefile +68 -0
  27. data/features/support/terminal.rb +107 -0
  28. data/features/user_informer.feature +63 -0
  29. data/generators/projectlocker_errata/lib/insert_commands.rb +34 -0
  30. data/generators/projectlocker_errata/lib/rake_commands.rb +24 -0
  31. data/generators/projectlocker_errata/projectlocker_errata_generator.rb +94 -0
  32. data/generators/projectlocker_errata/templates/capistrano_hook.rb +6 -0
  33. data/generators/projectlocker_errata/templates/initializer.rb +6 -0
  34. data/generators/projectlocker_errata/templates/projectlocker_errata_tasks.rake +25 -0
  35. data/install.rb +1 -0
  36. data/lib/projectlocker_errata/backtrace.rb +108 -0
  37. data/lib/projectlocker_errata/capistrano.rb +43 -0
  38. data/lib/projectlocker_errata/configuration.rb +305 -0
  39. data/lib/projectlocker_errata/notice.rb +390 -0
  40. data/lib/projectlocker_errata/rack.rb +54 -0
  41. data/lib/projectlocker_errata/rails/action_controller_catcher.rb +30 -0
  42. data/lib/projectlocker_errata/rails/controller_methods.rb +85 -0
  43. data/lib/projectlocker_errata/rails/error_lookup.rb +33 -0
  44. data/lib/projectlocker_errata/rails/javascript_notifier.rb +47 -0
  45. data/lib/projectlocker_errata/rails/middleware/exceptions_catcher.rb +33 -0
  46. data/lib/projectlocker_errata/rails.rb +40 -0
  47. data/lib/projectlocker_errata/rails3_tasks.rb +98 -0
  48. data/lib/projectlocker_errata/railtie.rb +48 -0
  49. data/lib/projectlocker_errata/rake_handler.rb +65 -0
  50. data/lib/projectlocker_errata/sender.rb +128 -0
  51. data/lib/projectlocker_errata/shared_tasks.rb +47 -0
  52. data/lib/projectlocker_errata/tasks.rb +83 -0
  53. data/lib/projectlocker_errata/user_informer.rb +27 -0
  54. data/lib/projectlocker_errata/utils/blank.rb +53 -0
  55. data/lib/projectlocker_errata/version.rb +3 -0
  56. data/lib/projectlocker_errata.rb +159 -0
  57. data/lib/projectlocker_errata_tasks.rb +64 -0
  58. data/lib/rails/generators/projectlocker_errata/projectlocker_errata_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/projectlocker_errata.gemspec +39 -0
  62. data/rails/init.rb +1 -0
  63. data/resources/README.md +34 -0
  64. data/resources/ca-bundle.crt +3376 -0
  65. data/script/integration_test.rb +38 -0
  66. data/test/airbrake_2_3.xsd +88 -0
  67. data/test/backtrace_test.rb +162 -0
  68. data/test/capistrano_test.rb +34 -0
  69. data/test/catcher_test.rb +333 -0
  70. data/test/configuration_test.rb +236 -0
  71. data/test/helper.rb +263 -0
  72. data/test/javascript_notifier_test.rb +51 -0
  73. data/test/logger_test.rb +79 -0
  74. data/test/notice_test.rb +490 -0
  75. data/test/notifier_test.rb +276 -0
  76. data/test/projectlocker_errata_tasks_test.rb +170 -0
  77. data/test/rack_test.rb +58 -0
  78. data/test/rails_initializer_test.rb +36 -0
  79. data/test/recursion_test.rb +10 -0
  80. data/test/sender_test.rb +288 -0
  81. data/test/user_informer_test.rb +29 -0
  82. metadata +440 -0
@@ -0,0 +1,108 @@
1
+ module ProjectlockerErrata
2
+ # Front end to parsing the backtrace for each notice
3
+ class Backtrace
4
+
5
+ # Handles backtrace parsing line by line
6
+ class Line
7
+
8
+ # regexp (optionnally allowing leading X: for windows support)
9
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
10
+
11
+ # The file portion of the line (such as app/models/user.rb)
12
+ attr_reader :file
13
+
14
+ # The line number portion of the line
15
+ attr_reader :number
16
+
17
+ # The method of the line (such as index)
18
+ attr_reader :method
19
+
20
+ # Parses a single line of a given backtrace
21
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
22
+ # @return [Line] The parsed backtrace line
23
+ def self.parse(unparsed_line)
24
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
25
+ new(file, number, method)
26
+ end
27
+
28
+ def initialize(file, number, method)
29
+ self.file = file
30
+ self.number = number
31
+ self.method = method
32
+ end
33
+
34
+ # Reconstructs the line in a readable fashion
35
+ def to_s
36
+ "#{file}:#{number}:in `#{method}'"
37
+ end
38
+
39
+ def ==(other)
40
+ to_s == other.to_s
41
+ end
42
+
43
+ def inspect
44
+ "<Line:#{to_s}>"
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :file, :number, :method
50
+ end
51
+
52
+ # holder for an Array of Backtrace::Line instances
53
+ attr_reader :lines
54
+
55
+ def self.parse(ruby_backtrace, opts = {})
56
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
57
+
58
+ filters = opts[:filters] || []
59
+ filtered_lines = ruby_lines.to_a.map do |line|
60
+ filters.inject(line) do |line, proc|
61
+ proc.call(line)
62
+ end
63
+ end.compact
64
+
65
+ lines = filtered_lines.collect do |unparsed_line|
66
+ Line.parse(unparsed_line)
67
+ end
68
+
69
+ instance = new(lines)
70
+ end
71
+
72
+ def initialize(lines)
73
+ self.lines = lines
74
+ end
75
+
76
+ def inspect
77
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
78
+ end
79
+
80
+ def to_s
81
+ content = []
82
+ lines.each do |line|
83
+ content << line
84
+ end
85
+ content.join("\n")
86
+ end
87
+
88
+ def ==(other)
89
+ if other.respond_to?(:lines)
90
+ lines == other.lines
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ attr_writer :lines
99
+
100
+ def self.split_multiline_backtrace(backtrace)
101
+ if backtrace.to_a.size == 1
102
+ backtrace.to_a.first.split(/\n\s*/)
103
+ else
104
+ backtrace
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,43 @@
1
+ # Defines deploy:notify_projectlocker_errata which will send information about the deploy to ProjectlockerErrata.
2
+ require 'capistrano'
3
+
4
+ module ProjectlockerErrata
5
+ module Capistrano
6
+ def self.load_into(configuration)
7
+ configuration.load do
8
+ after "deploy", "projectlocker_errata:deploy"
9
+ after "deploy:migrations", "projectlocker_errata:deploy"
10
+
11
+ namespace :projectlocker_errata do
12
+ desc <<-DESC
13
+ Notify ProjectlockerErrata of the deployment by running the notification on the REMOTE machine.
14
+ - Run remotely so we use remote API keys, environment, etc.
15
+ DESC
16
+ task :deploy, :except => { :no_release => true } do
17
+ rails_env = fetch(:rails_env, "production")
18
+ projectlocker_errata_env = fetch(:projectlocker_errata_env, fetch(:rails_env, "production"))
19
+ local_user = ENV['USER'] || ENV['USERNAME']
20
+ executable = RUBY_PLATFORM.downcase.include?('mswin') ? fetch(:rake, 'rake.bat') : fetch(:rake, 'rake')
21
+ directory = configuration.current_release
22
+ notify_command = "cd #{directory}; #{executable} RAILS_ENV=#{rails_env} projectlocker_errata:deploy TO=#{projectlocker_errata_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}"
23
+ notify_command << " DRY_RUN=true" if dry_run
24
+ notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY']
25
+ logger.info "Notifying ProjectlockerErrata of Deploy (#{notify_command})"
26
+ if configuration.dry_run
27
+ logger.info "DRY RUN: Notification not actually run."
28
+ else
29
+ result = ""
30
+ run(notify_command, :once => true) { |ch, stream, data| result << data }
31
+ # TODO: Check if SSL is active on account via result content.
32
+ end
33
+ logger.info "ProjectlockerErrata Notification Complete."
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ if Capistrano::Configuration.instance
42
+ ProjectlockerErrata::Capistrano.load_into(Capistrano::Configuration.instance)
43
+ end
@@ -0,0 +1,305 @@
1
+ module ProjectlockerErrata
2
+ # Used to set up and modify settings for the notifier.
3
+ class Configuration
4
+
5
+ OPTIONS = [:api_key, :js_api_key, :backtrace_filters, :development_environments,
6
+ :development_lookup, :environment_name, :host,
7
+ :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
8
+ :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
9
+ :params_filters, :project_root, :port, :protocol, :proxy_host,
10
+ :proxy_pass, :proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain,
11
+ :framework, :user_information, :rescue_rake_exceptions].freeze
12
+
13
+ # The API key for your project, found on the project edit form.
14
+ attr_accessor :api_key
15
+
16
+ # If you're using the Javascript notifier and would want to separate
17
+ # Javascript notifications into another ProjectlockerErrata project, specify
18
+ # its APi key here.
19
+ # Defaults to #api_key (of the base project)
20
+ attr_writer :js_api_key
21
+
22
+ # The host to connect to (defaults to errors.projectlocker.com).
23
+ attr_accessor :host
24
+
25
+ # The port on which your ProjectlockerErrata server runs (defaults to 443 for secure
26
+ # connections, 80 for insecure connections).
27
+ attr_accessor :port
28
+
29
+ # +true+ for https connections, +false+ for http connections.
30
+ attr_accessor :secure
31
+
32
+ # +true+ to use whatever CAs OpenSSL has installed on your system. +false+ to use the ca-bundle.crt file included in ProjectlockerErrata itself (reccomended and default)
33
+ attr_accessor :use_system_ssl_cert_chain
34
+
35
+ # The HTTP open timeout in seconds (defaults to 2).
36
+ attr_accessor :http_open_timeout
37
+
38
+ # The HTTP read timeout in seconds (defaults to 5).
39
+ attr_accessor :http_read_timeout
40
+
41
+ # The hostname of your proxy server (if using a proxy)
42
+ attr_accessor :proxy_host
43
+
44
+ # The port of your proxy server (if using a proxy)
45
+ attr_accessor :proxy_port
46
+
47
+ # The username to use when logging into your proxy server (if using a proxy)
48
+ attr_accessor :proxy_user
49
+
50
+ # The password to use when logging into your proxy server (if using a proxy)
51
+ attr_accessor :proxy_pass
52
+
53
+ # A list of parameters that should be filtered out of what is sent to ProjectlockerErrata.
54
+ # By default, all "password" attributes will have their contents replaced.
55
+ attr_reader :params_filters
56
+
57
+ # A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
58
+ attr_reader :backtrace_filters
59
+
60
+ # A list of filters for ignoring exceptions. See #ignore_by_filter.
61
+ attr_reader :ignore_by_filters
62
+
63
+ # A list of exception classes to ignore. The array can be appended to.
64
+ attr_reader :ignore
65
+
66
+ # A list of user agents that are being ignored. The array can be appended to.
67
+ attr_reader :ignore_user_agent
68
+
69
+ # A list of environments in which notifications should not be sent.
70
+ attr_accessor :development_environments
71
+
72
+ # +true+ if you want to check for production errors matching development errors, +false+ otherwise.
73
+ attr_accessor :development_lookup
74
+
75
+ # The name of the environment the application is running in
76
+ attr_accessor :environment_name
77
+
78
+ # The path to the project in which the error occurred, such as the Rails.root
79
+ attr_accessor :project_root
80
+
81
+ # The name of the notifier library being used to send notifications (such as "ProjectlockerErrata Notifier")
82
+ attr_accessor :notifier_name
83
+
84
+ # The version of the notifier library being used to send notifications (such as "1.0.2")
85
+ attr_accessor :notifier_version
86
+
87
+ # The url of the notifier library being used to send notifications
88
+ attr_accessor :notifier_url
89
+
90
+ # The logger used by ProjectlockerErrata
91
+ attr_accessor :logger
92
+
93
+ # The text that the placeholder is replaced with. {{error_id}} is the actual error number.
94
+ attr_accessor :user_information
95
+
96
+ # The framework ProjectlockerErrata is configured to use
97
+ attr_accessor :framework
98
+
99
+ # Should ProjectlockerErrata catch exceptions from Rake tasks?
100
+ # (boolean or nil; set to nil to catch exceptions when rake isn't running from a terminal; default is nil)
101
+ attr_accessor :rescue_rake_exceptions
102
+
103
+
104
+ DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
105
+
106
+ DEFAULT_BACKTRACE_FILTERS = [
107
+ lambda { |line|
108
+ if defined?(ProjectlockerErrata.configuration.project_root) && ProjectlockerErrata.configuration.project_root.to_s != ''
109
+ line.sub(/#{ProjectlockerErrata.configuration.project_root}/, "[PROJECT_ROOT]")
110
+ else
111
+ line
112
+ end
113
+ },
114
+ lambda { |line| line.gsub(/^\.\//, "") },
115
+ lambda { |line|
116
+ if defined?(Gem)
117
+ Gem.path.inject(line) do |line, path|
118
+ line.gsub(/#{path}/, "[GEM_ROOT]")
119
+ end
120
+ end
121
+ },
122
+ lambda { |line| line if line !~ %r{lib/projectlocker_errata} }
123
+ ].freeze
124
+
125
+ IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
126
+ 'ActionController::RoutingError',
127
+ 'ActionController::InvalidAuthenticityToken',
128
+ 'CGI::Session::CookieStore::TamperedWithCookie',
129
+ 'ActionController::UnknownAction',
130
+ 'AbstractController::ActionNotFound',
131
+ 'Mongoid::Errors::DocumentNotFound']
132
+
133
+ alias_method :secure?, :secure
134
+ alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
135
+
136
+ def initialize
137
+ @secure = false
138
+ @use_system_ssl_cert_chain= false
139
+ @host = 'errors.projectlocker.com'
140
+ @http_open_timeout = 2
141
+ @http_read_timeout = 5
142
+ @params_filters = DEFAULT_PARAMS_FILTERS.dup
143
+ @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
144
+ @ignore_by_filters = []
145
+ @ignore = IGNORE_DEFAULT.dup
146
+ @ignore_user_agent = []
147
+ @development_environments = %w(development test cucumber)
148
+ @development_lookup = true
149
+ @notifier_name = 'ProjectlockerErrata Notifier'
150
+ @notifier_version = VERSION
151
+ @notifier_url = 'http://rubyforge.org/projects/pl-errata/'
152
+ @framework = 'Standalone'
153
+ @user_information = 'ProjectlockerErrata Error {{error_id}}'
154
+ @rescue_rake_exceptions = nil
155
+ end
156
+
157
+ # Takes a block and adds it to the list of backtrace filters. When the filters
158
+ # run, the block will be handed each line of the backtrace and can modify
159
+ # it as necessary.
160
+ #
161
+ # @example
162
+ # config.filter_bracktrace do |line|
163
+ # line.gsub(/^#{Rails.root}/, "[Rails.root]")
164
+ # end
165
+ #
166
+ # @param [Proc] block The new backtrace filter.
167
+ # @yieldparam [String] line A line in the backtrace.
168
+ def filter_backtrace(&block)
169
+ self.backtrace_filters << block
170
+ end
171
+
172
+ # Takes a block and adds it to the list of ignore filters.
173
+ # When the filters run, the block will be handed the exception.
174
+ # @example
175
+ # config.ignore_by_filter do |exception_data|
176
+ # true if exception_data[:error_class] == "RuntimeError"
177
+ # end
178
+ #
179
+ # @param [Proc] block The new ignore filter
180
+ # @yieldparam [Hash] data The exception data given to +ProjectlockerErrata.notify+
181
+ # @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by projectlocker_errata.
182
+ def ignore_by_filter(&block)
183
+ self.ignore_by_filters << block
184
+ end
185
+
186
+ # Overrides the list of default ignored errors.
187
+ #
188
+ # @param [Array<Exception>] names A list of exceptions to ignore.
189
+ def ignore_only=(names)
190
+ @ignore = [names].flatten
191
+ end
192
+
193
+ # Overrides the list of default ignored user agents
194
+ #
195
+ # @param [Array<String>] A list of user agents to ignore
196
+ def ignore_user_agent_only=(names)
197
+ @ignore_user_agent = [names].flatten
198
+ end
199
+
200
+ # Allows config options to be read like a hash
201
+ #
202
+ # @param [Symbol] option Key for a given attribute
203
+ def [](option)
204
+ send(option)
205
+ end
206
+
207
+ # Returns a hash of all configurable options
208
+ def to_hash
209
+ OPTIONS.inject({}) do |hash, option|
210
+ hash[option.to_sym] = self.send(option)
211
+ hash
212
+ end
213
+ end
214
+
215
+ # Returns a hash of all configurable options merged with +hash+
216
+ #
217
+ # @param [Hash] hash A set of configuration options that will take precedence over the defaults
218
+ def merge(hash)
219
+ to_hash.merge(hash)
220
+ end
221
+
222
+ # Determines if the notifier will send notices.
223
+ # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
224
+ def public?
225
+ !development_environments.include?(environment_name)
226
+ end
227
+
228
+ def port
229
+ @port || default_port
230
+ end
231
+
232
+ # Determines whether protocol should be "http" or "https".
233
+ # @return [String] Returns +"http"+ if you've set secure to +false+ in
234
+ # configuration, and +"https"+ otherwise.
235
+ def protocol
236
+ if secure?
237
+ 'https'
238
+ else
239
+ 'http'
240
+ end
241
+ end
242
+
243
+ # Should ProjectlockerErrata send notifications asynchronously
244
+ # (boolean, nil or callable; default is nil).
245
+ # Can be used as callable-setter when block provided.
246
+ def async(&block)
247
+ if block_given?
248
+ @async = block
249
+ end
250
+ @async
251
+ end
252
+ alias_method :async?, :async
253
+
254
+ def async=(use_default_or_this)
255
+ @async = use_default_or_this == true ?
256
+ default_async_processor :
257
+ use_default_or_this
258
+ end
259
+
260
+ def js_api_key
261
+ @js_api_key || self.api_key
262
+ end
263
+
264
+ def js_notifier=(*args)
265
+ warn '[PROJECTLOCKER_ERRATA] config.js_notifier has been deprecated and has no effect. You should use <%= projectlocker_errata_javascript_notifier %> directly at the top of your layouts. Be sure to place it before all other javascript.'
266
+ end
267
+
268
+ def environment_filters
269
+ warn 'config.environment_filters has been deprecated and has no effect.'
270
+ []
271
+ end
272
+
273
+ def ca_bundle_path
274
+ if use_system_ssl_cert_chain? && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
275
+ OpenSSL::X509::DEFAULT_CERT_FILE
276
+ else
277
+ local_cert_path # ca-bundle.crt built from source, see resources/README.md
278
+ end
279
+ end
280
+
281
+ def local_cert_path
282
+ File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
283
+ end
284
+
285
+ private
286
+ # Determines what port should we use for sending notices.
287
+ # @return [Fixnum] Returns 443 if you've set secure to true in your
288
+ # configuration, and 80 otherwise.
289
+ def default_port
290
+ if secure?
291
+ 443
292
+ else
293
+ 80
294
+ end
295
+ end
296
+
297
+ # Async notice delivery defaults to girl friday
298
+ def default_async_processor
299
+ queue = GirlFriday::WorkQueue.new(nil, :size => 3) do |notice|
300
+ ProjectlockerErrata.sender.send_to_projectlocker_errata(notice)
301
+ end
302
+ lambda {|notice| queue << notice}
303
+ end
304
+ end
305
+ end