projectlocker_pulse 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/CHANGELOG +26 -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 +439 -0
  7. data/README_FOR_HEROKU_ADDON.md +89 -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_pulse_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/pulse/lib/insert_commands.rb +34 -0
  30. data/generators/pulse/lib/rake_commands.rb +24 -0
  31. data/generators/pulse/pulse_generator.rb +94 -0
  32. data/generators/pulse/templates/capistrano_hook.rb +6 -0
  33. data/generators/pulse/templates/initializer.rb +6 -0
  34. data/generators/pulse/templates/pulse_tasks.rake +25 -0
  35. data/install.rb +1 -0
  36. data/lib/projectlocker_pulse.rb +159 -0
  37. data/lib/pulse/backtrace.rb +108 -0
  38. data/lib/pulse/capistrano.rb +43 -0
  39. data/lib/pulse/configuration.rb +305 -0
  40. data/lib/pulse/notice.rb +390 -0
  41. data/lib/pulse/rack.rb +54 -0
  42. data/lib/pulse/rails/action_controller_catcher.rb +30 -0
  43. data/lib/pulse/rails/controller_methods.rb +85 -0
  44. data/lib/pulse/rails/error_lookup.rb +33 -0
  45. data/lib/pulse/rails/javascript_notifier.rb +47 -0
  46. data/lib/pulse/rails/middleware/exceptions_catcher.rb +33 -0
  47. data/lib/pulse/rails.rb +40 -0
  48. data/lib/pulse/rails3_tasks.rb +99 -0
  49. data/lib/pulse/railtie.rb +49 -0
  50. data/lib/pulse/rake_handler.rb +65 -0
  51. data/lib/pulse/sender.rb +128 -0
  52. data/lib/pulse/shared_tasks.rb +47 -0
  53. data/lib/pulse/tasks.rb +83 -0
  54. data/lib/pulse/user_informer.rb +27 -0
  55. data/lib/pulse/utils/blank.rb +53 -0
  56. data/lib/pulse/version.rb +3 -0
  57. data/lib/pulse_tasks.rb +64 -0
  58. data/lib/rails/generators/pulse/pulse_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/pulse.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/backtrace_test.rb +162 -0
  67. data/test/capistrano_test.rb +34 -0
  68. data/test/catcher_test.rb +333 -0
  69. data/test/configuration_test.rb +236 -0
  70. data/test/helper.rb +263 -0
  71. data/test/javascript_notifier_test.rb +51 -0
  72. data/test/logger_test.rb +79 -0
  73. data/test/notice_test.rb +490 -0
  74. data/test/notifier_test.rb +276 -0
  75. data/test/projectlocker_pulse_tasks_test.rb +170 -0
  76. data/test/pulse.xsd +88 -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 +432 -0
@@ -0,0 +1,40 @@
1
+ require 'projectlocker_pulse'
2
+ require 'pulse/rails/controller_methods'
3
+ require 'pulse/rails/action_controller_catcher'
4
+ require 'pulse/rails/error_lookup'
5
+ require 'pulse/rails/javascript_notifier'
6
+
7
+ module Pulse
8
+ module Rails
9
+ def self.initialize
10
+ if defined?(ActionController::Base)
11
+ ActionController::Base.send(:include, Pulse::Rails::ActionControllerCatcher)
12
+ ActionController::Base.send(:include, Pulse::Rails::ErrorLookup)
13
+ ActionController::Base.send(:include, Pulse::Rails::ControllerMethods)
14
+ ActionController::Base.send(:include, Pulse::Rails::JavascriptNotifier)
15
+ end
16
+
17
+ rails_logger = if defined?(::Rails.logger)
18
+ ::Rails.logger
19
+ elsif defined?(RAILS_DEFAULT_LOGGER)
20
+ RAILS_DEFAULT_LOGGER
21
+ end
22
+
23
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
24
+ ::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
25
+ Pulse::Rack
26
+ ::Rails.configuration.middleware.insert_after 'Rack::Lock',
27
+ Pulse::UserInformer
28
+ end
29
+
30
+ Pulse.configure(true) do |config|
31
+ config.logger = rails_logger
32
+ config.environment_name = defined?(::Rails.env) && ::Rails.env || defined?(RAILS_ENV) && RAILS_ENV
33
+ config.project_root = defined?(::Rails.root) && ::Rails.root || defined?(RAILS_ROOT) && RAILS_ROOT
34
+ config.framework = defined?(::Rails.version) && "Rails: #{::Rails.version}" || defined?(::Rails::VERSION::STRING) && "Rails: #{::Rails::VERSION::STRING}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Pulse::Rails.initialize
@@ -0,0 +1,99 @@
1
+ require 'projectlocker_pulse'
2
+ require File.join(File.dirname(__FILE__), 'shared_tasks')
3
+
4
+ puts "\n\nLOADED RAILS3 TASKS\n\n"
5
+ namespace :pulse do
6
+ desc "Verify your gem installation by sending a test exception to the pulse service"
7
+ task :test => [:environment] do
8
+ Rails.logger = defined?(ActiveSupport::TaggedLogging) ?
9
+ ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) :
10
+ Logger.new(STDOUT)
11
+
12
+ def wait_for_threads
13
+ # if using multiple threads, we have to wait for
14
+ # them to finish
15
+ if GirlFriday.status.empty?
16
+ Thread.list.each do |thread|
17
+ thread.join unless thread == Thread.current
18
+ end
19
+ else
20
+ GirlFriday.shutdown!
21
+ end
22
+ end
23
+
24
+ Rails.logger.level = Logger::DEBUG
25
+ Pulse.configure(true) do |config|
26
+ config.logger = Rails.logger
27
+ end
28
+
29
+ require './app/controllers/application_controller'
30
+
31
+ class PulseTestingException < RuntimeError; end
32
+
33
+ unless Pulse.configuration.api_key
34
+ puts "Pulse needs an API key configured! Check the README to see how to add it."
35
+ exit
36
+ end
37
+
38
+ Pulse.configuration.development_environments = []
39
+
40
+ puts "Configuration:"
41
+ Pulse.configuration.to_hash.each do |key, value|
42
+ puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
43
+ end
44
+
45
+ unless defined?(ApplicationController)
46
+ puts "No ApplicationController found"
47
+ exit
48
+ end
49
+
50
+ puts 'Setting up the Controller.'
51
+ class ApplicationController
52
+ # This is to bypass any filters that may prevent access to the action.
53
+ prepend_before_filter :test_pulse
54
+ def test_pulse
55
+ puts "Raising '#{exception_class.name}' to simulate application failure."
56
+ raise exception_class.new, 'Testing Pulse via "rake pulse:test". If you can see this, it works.'
57
+ end
58
+
59
+ # def rescue_action(exception)
60
+ # rescue_action_in_public exception
61
+ # end
62
+
63
+ # Ensure we actually have an action to go to.
64
+ def verify; end
65
+
66
+ # def consider_all_requests_local
67
+ # false
68
+ # end
69
+
70
+ # def local_request?
71
+ # false
72
+ # end
73
+
74
+ def exception_class
75
+ exception_name = ENV['EXCEPTION'] || "PulseTestingException"
76
+ Object.const_get(exception_name)
77
+ rescue
78
+ Object.const_set(exception_name, Class.new(Exception))
79
+ end
80
+
81
+ def logger
82
+ nil
83
+ end
84
+ end
85
+ class PulseVerificationController < ApplicationController; end
86
+
87
+ Rails.application.routes_reloader.execute_if_updated
88
+ Rails.application.routes.draw do
89
+ match 'verify' => 'application#verify', :as => 'verify'
90
+ end
91
+
92
+ puts 'Processing request.'
93
+ env = Rack::MockRequest.env_for("/verify")
94
+
95
+ Rails.application.call(env)
96
+
97
+ wait_for_threads
98
+ end
99
+ end
@@ -0,0 +1,49 @@
1
+ require 'projectlocker_pulse'
2
+ require 'rails'
3
+
4
+ module Pulse
5
+ class Railtie < ::Rails::Railtie
6
+ rake_tasks do
7
+ require 'pulse/rake_handler'
8
+ require 'pulse/rails3_tasks'
9
+ #load "#{File.dirname(__FILE__)}/rails3_tasks.rb"
10
+ end
11
+
12
+ initializer "pulse.use_rack_middleware" do |app|
13
+ app.config.middleware.insert 0, "Pulse::UserInformer"
14
+ app.config.middleware.insert_after "Pulse::UserInformer","Pulse::Rack"
15
+ end
16
+
17
+ config.after_initialize do
18
+ Pulse.configure(true) do |config|
19
+ config.logger ||= config.async? ? ::Logger.new(STDERR) : ::Rails.logger
20
+ config.environment_name ||= ::Rails.env
21
+ config.project_root ||= ::Rails.root
22
+ config.framework = "Rails: #{::Rails::VERSION::STRING}"
23
+ end
24
+
25
+ ActiveSupport.on_load(:action_controller) do
26
+ # Lazily load action_controller methods
27
+ #
28
+ require 'pulse/rails/javascript_notifier'
29
+ require 'pulse/rails/controller_methods'
30
+
31
+ include Pulse::Rails::JavascriptNotifier
32
+ include Pulse::Rails::ControllerMethods
33
+ end
34
+
35
+ if defined?(::ActionDispatch::DebugExceptions)
36
+ # We should catch the exceptions in ActionDispatch::DebugExceptions in Rails 3.2.x.
37
+ #
38
+ require 'pulse/rails/middleware/exceptions_catcher'
39
+ ::ActionDispatch::DebugExceptions.send(:include,Pulse::Rails::Middleware::ExceptionsCatcher)
40
+ elsif defined?(::ActionDispatch::ShowExceptions)
41
+ # ActionDispatch::DebugExceptions is not defined in Rails 3.0.x and 3.1.x so
42
+ # catch the exceptions in ShowExceptions.
43
+ #
44
+ require 'pulse/rails/middleware/exceptions_catcher'
45
+ ::ActionDispatch::ShowExceptions.send(:include,Pulse::Rails::Middleware::ExceptionsCatcher)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,65 @@
1
+ # Patch Rake::Application to handle errors with Pulse
2
+ module Pulse::RakeHandler
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ include Rake087Methods unless defined?(Rake::VERSION) && Rake::VERSION >= '0.9.0'
6
+ alias_method :display_error_message_without_pulse, :display_error_message
7
+ alias_method :display_error_message, :display_error_message_with_pulse
8
+ end
9
+ end
10
+
11
+ def display_error_message_with_pulse(ex)
12
+ if Pulse.sender && Pulse.configuration &&
13
+ (Pulse.configuration.rescue_rake_exceptions ||
14
+ (Pulse.configuration.rescue_rake_exceptions===nil && !self.tty_output?))
15
+
16
+ Pulse.notify_or_ignore(ex, :component => reconstruct_command_line, :cgi_data => ENV)
17
+ end
18
+
19
+ display_error_message_without_pulse(ex)
20
+ end
21
+
22
+ def reconstruct_command_line
23
+ "rake #{ARGV.join( ' ' )}"
24
+ end
25
+
26
+ # This module brings Rake 0.8.7 error handling to 0.9.0 standards
27
+ module Rake087Methods
28
+ # Method taken from Rake 0.9.0 source
29
+ #
30
+ # Provide standard exception handling for the given block.
31
+ def standard_exception_handling
32
+ begin
33
+ yield
34
+ rescue SystemExit => ex
35
+ # Exit silently with current status
36
+ raise
37
+ rescue OptionParser::InvalidOption => ex
38
+ $stderr.puts ex.message
39
+ exit(false)
40
+ rescue Exception => ex
41
+ # Exit with error message
42
+ display_error_message(ex)
43
+ exit(false)
44
+ end
45
+ end
46
+
47
+ # Method extracted from Rake 0.8.7 source
48
+ def display_error_message(ex)
49
+ $stderr.puts "#{name} aborted!"
50
+ $stderr.puts ex.message
51
+ if options.trace
52
+ $stderr.puts ex.backtrace.join("\n")
53
+ else
54
+ $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
55
+ $stderr.puts "(See full trace by running task with --trace)"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ Rake.application.instance_eval do
62
+ class << self
63
+ include Pulse::RakeHandler
64
+ end
65
+ end
@@ -0,0 +1,128 @@
1
+ module Pulse
2
+ # Sends out the notice to Pulse
3
+ class Sender
4
+
5
+ NOTICES_URI = '/notifier_api/v2/notices/'.freeze
6
+ HTTP_ERRORS = [Timeout::Error,
7
+ Errno::EINVAL,
8
+ Errno::ECONNRESET,
9
+ EOFError,
10
+ Net::HTTPBadResponse,
11
+ Net::HTTPHeaderSyntaxError,
12
+ Net::ProtocolError,
13
+ Errno::ECONNREFUSED].freeze
14
+
15
+ def initialize(options = {})
16
+ [ :proxy_host,
17
+ :proxy_port,
18
+ :proxy_user,
19
+ :proxy_pass,
20
+ :protocol,
21
+ :host,
22
+ :port,
23
+ :secure,
24
+ :use_system_ssl_cert_chain,
25
+ :http_open_timeout,
26
+ :http_read_timeout
27
+ ].each do |option|
28
+ instance_variable_set("@#{option}", options[option])
29
+ end
30
+ end
31
+
32
+ # Sends the notice data off to Pulse for processing.
33
+ #
34
+ # @param [Notice or String] notice The notice to be sent off
35
+ def send_to_pulse(notice)
36
+ data = notice.respond_to?(:to_xml) ? notice.to_xml : notice
37
+ http = setup_http_connection
38
+
39
+ response = begin
40
+ http.post(url.path, data, HEADERS)
41
+ rescue *HTTP_ERRORS => e
42
+ log :level => :error,
43
+ :message => "Unable to contact the Pulse server. HTTP Error=#{e}"
44
+ nil
45
+ end
46
+
47
+ case response
48
+ when Net::HTTPSuccess then
49
+ log :level => :info,
50
+ :message => "Success: #{response.class}",
51
+ :response => response
52
+ else
53
+ log :level => :error,
54
+ :message => "Failure: #{response.class}",
55
+ :response => response,
56
+ :notice => notice
57
+ end
58
+
59
+ if response && response.respond_to?(:body)
60
+ error_id = response.body.match(%r{<id[^>]*>(.*?)</id>})
61
+ error_id[1] if error_id
62
+ end
63
+ rescue => e
64
+ log :level => :error,
65
+ :message => "[Pulse::Sender#send_to_pulse] Cannot send notification. Error: #{e.class}" +
66
+ " - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
67
+
68
+ nil
69
+ end
70
+
71
+ attr_reader :proxy_host,
72
+ :proxy_port,
73
+ :proxy_user,
74
+ :proxy_pass,
75
+ :protocol,
76
+ :host,
77
+ :port,
78
+ :secure,
79
+ :use_system_ssl_cert_chain,
80
+ :http_open_timeout,
81
+ :http_read_timeout
82
+
83
+ alias_method :secure?, :secure
84
+ alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
85
+
86
+ private
87
+
88
+ def url
89
+ URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
90
+ end
91
+
92
+ def log(opts = {})
93
+ opts[:logger].send opts[:level], LOG_PREFIX + opts[:message] if opts[:logger]
94
+ Pulse.report_environment_info
95
+ Pulse.report_response_body(opts[:response].body) if opts[:response] && opts[:response].respond_to?(:body)
96
+ Pulse.report_notice(opts[:notice]) if opts[:notice]
97
+ end
98
+
99
+ def logger
100
+ Pulse.logger
101
+ end
102
+
103
+ def setup_http_connection
104
+ http =
105
+ Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
106
+ new(url.host, url.port)
107
+
108
+ http.read_timeout = http_read_timeout
109
+ http.open_timeout = http_open_timeout
110
+
111
+ if secure?
112
+ http.use_ssl = true
113
+
114
+ http.ca_file = Pulse.configuration.ca_bundle_path
115
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
116
+ else
117
+ http.use_ssl = false
118
+ end
119
+
120
+ http
121
+ rescue => e
122
+ log :level => :error,
123
+ :message => "[Pulse::Sender#setup_http_connection] Failure initializing the HTTP connection.\n" +
124
+ "Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
125
+ raise e
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,47 @@
1
+ namespace :pulse do
2
+ desc "Notify Pulse of a new deploy."
3
+ task :deploy do
4
+ require 'pulse_tasks'
5
+
6
+ if defined?(Rails.root)
7
+ initializer_file = Rails.root.join('config', 'initializers','pulse.rb')
8
+
9
+ if initializer_file.exist?
10
+ load initializer_file
11
+ else
12
+ Rake::Task[:environment].invoke
13
+ end
14
+ end
15
+
16
+ PulseTasks.deploy(:rails_env => ENV['TO'],
17
+ :scm_revision => ENV['REVISION'],
18
+ :scm_repository => ENV['REPO'],
19
+ :local_username => ENV['USER'],
20
+ :api_key => ENV['API_KEY'],
21
+ :dry_run => ENV['DRY_RUN'])
22
+ end
23
+
24
+ task :log_stdout do
25
+ require 'logger'
26
+ RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
27
+ end
28
+
29
+ namespace :heroku do
30
+ desc "Install Heroku deploy notifications addon"
31
+ task :add_deploy_notification => [:environment] do
32
+
33
+ def heroku_var(var)
34
+ `heroku config | grep -E "#{var.upcase}" | awk '{ print $3; }'`.strip
35
+ end
36
+
37
+ heroku_rails_env = heroku_var("rails_env")
38
+ heroku_api_key = heroku_var("pulse_api_key").split.find {|x| x unless x.blank?} ||
39
+ Pulse.configuration.api_key
40
+
41
+ command = %Q(heroku addons:add deployhooks:http --url="http://errors.projectlocker.com/deploys.txt?deploy[rails_env]=#{heroku_rails_env}&api_key=#{heroku_api_key}")
42
+
43
+ puts "\nRunning:\n#{command}\n"
44
+ puts `#{command}`
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ require 'projectlocker_pulse'
2
+ require File.join(File.dirname(__FILE__), 'shared_tasks')
3
+
4
+ namespace :pulse do
5
+ desc "Verify your gem installation by sending a test exception to the pulse service"
6
+ task :test => ['pulse:log_stdout', :environment] do
7
+ RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
8
+
9
+ require 'action_controller/test_process'
10
+
11
+ Dir["app/controllers/application*.rb"].each { |file| require(File.expand_path(file)) }
12
+
13
+ class PulseTestingException < RuntimeError; end
14
+
15
+ unless Pulse.configuration.api_key
16
+ puts "Pulse needs an API key configured! Check the README to see how to add it."
17
+ exit
18
+ end
19
+
20
+ Pulse.configuration.development_environments = []
21
+
22
+ catcher = Pulse::Rails::ActionControllerCatcher
23
+ in_controller = ApplicationController.included_modules.include?(catcher)
24
+ in_base = ActionController::Base.included_modules.include?(catcher)
25
+ if !in_controller || !in_base
26
+ puts "Rails initialization did not occur"
27
+ exit
28
+ end
29
+
30
+ puts "Configuration:"
31
+ Pulse.configuration.to_hash.each do |key, value|
32
+ puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
33
+ end
34
+
35
+ unless defined?(ApplicationController)
36
+ puts "No ApplicationController found"
37
+ exit
38
+ end
39
+
40
+ puts 'Setting up the Controller.'
41
+ class ApplicationController
42
+ # This is to bypass any filters that may prevent access to the action.
43
+ prepend_before_filter :test_pulse
44
+ def test_pulse
45
+ puts "Raising '#{exception_class.name}' to simulate application failure."
46
+ raise exception_class.new, 'Testing Pulse via "rake pulse:test". If you can see this, it works.'
47
+ end
48
+
49
+ def rescue_action(exception)
50
+ rescue_action_in_public exception
51
+ end
52
+
53
+ # Ensure we actually have an action to go to.
54
+ def verify; end
55
+
56
+ def consider_all_requests_local
57
+ false
58
+ end
59
+
60
+ def local_request?
61
+ false
62
+ end
63
+
64
+ def exception_class
65
+ exception_name = ENV['EXCEPTION'] || "PulseTestingException"
66
+ exception_name.split("::").inject(Object){|klass, name| klass.const_get(name)}
67
+ rescue
68
+ Object.const_set(exception_name.gsub(/:+/, "_"), Class.new(Exception))
69
+ end
70
+
71
+ def logger
72
+ nil
73
+ end
74
+ end
75
+ class PulseVerificationController < ApplicationController; end
76
+
77
+ puts 'Processing request.'
78
+ request = ActionController::TestRequest.new("REQUEST_URI" => "/pulse_verification_controller")
79
+ response = ActionController::TestResponse.new
80
+ PulseVerificationController.new.process(request, response)
81
+ end
82
+ end
83
+
@@ -0,0 +1,27 @@
1
+ module Pulse
2
+ class UserInformer
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def replacement(with)
8
+ Pulse.configuration.user_information.gsub(/\{\{\s*error_id\s*\}\}/, with.to_s)
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, body = @app.call(env)
13
+ if env['pulse.error_id'] && Pulse.configuration.user_information
14
+ new_body = []
15
+ replace = replacement(env['pulse.error_id'])
16
+ body.each do |chunk|
17
+ new_body << chunk.gsub("<!-- PULSE ERROR -->", replace)
18
+ end
19
+ body.close if body.respond_to?(:close)
20
+ headers['Content-Length'] = new_body.sum(&:bytesize).to_s
21
+ body = new_body
22
+ end
23
+ [status, headers, body]
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,53 @@
1
+ # stolen from ActiveSupport
2
+
3
+ class Object
4
+ def blank?
5
+ respond_to?(:empty?) ? empty? : !self
6
+ end
7
+
8
+ def present?
9
+ !blank?
10
+ end
11
+
12
+ def presence
13
+ self if present?
14
+ end
15
+ end
16
+
17
+ class NilClass
18
+ def blank?
19
+ true
20
+ end
21
+ end
22
+
23
+ class FalseClass
24
+ def blank?
25
+ true
26
+ end
27
+ end
28
+
29
+ class TrueClass
30
+ def blank?
31
+ false
32
+ end
33
+ end
34
+
35
+ class Array
36
+ alias_method :blank?, :empty?
37
+ end
38
+
39
+ class Hash
40
+ alias_method :blank?, :empty?
41
+ end
42
+
43
+ class String
44
+ def blank?
45
+ self !~ /[^[:space:]]/
46
+ end
47
+ end
48
+
49
+ class Numeric
50
+ def blank?
51
+ false
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Pulse
2
+ VERSION = "0.2.1".freeze
3
+ end
@@ -0,0 +1,64 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ # Capistrano tasks for notifying Pulse of deploys
5
+ module PulseTasks
6
+
7
+ # Alerts Pulse of a deploy.
8
+ #
9
+ # @param [Hash] opts Data about the deploy that is set to Pulse
10
+ #
11
+ # @option opts [String] :api_key Api key of you Pulse application
12
+ # @option opts [String] :rails_env Environment of the deploy (production, staging)
13
+ # @option opts [String] :scm_revision The given revision/sha that is being deployed
14
+ # @option opts [String] :scm_repository Address of your repository to help with code lookups
15
+ # @option opts [String] :local_username Who is deploying
16
+ def self.deploy(opts = {})
17
+ api_key = opts.delete(:api_key) || Pulse.configuration.api_key
18
+ if api_key.blank?
19
+ puts "I don't seem to be configured with an API key. Please check your configuration."
20
+ return false
21
+ end
22
+
23
+ if opts[:rails_env].blank?
24
+ puts "I don't know to which Rails environment you are deploying (use the TO=production option)."
25
+ return false
26
+ end
27
+
28
+ dry_run = opts.delete(:dry_run)
29
+ params = {'api_key' => api_key}
30
+ opts.each {|k,v| params["deploy[#{k}]"] = v }
31
+
32
+ host = Pulse.configuration.host || 'errors.projectlocker.com'
33
+ port = Pulse.configuration.port
34
+
35
+ proxy = Net::HTTP.Proxy(Pulse.configuration.proxy_host,
36
+ Pulse.configuration.proxy_port,
37
+ Pulse.configuration.proxy_user,
38
+ Pulse.configuration.proxy_pass)
39
+ http = proxy.new(host, port)
40
+
41
+
42
+
43
+ # Handle Security
44
+ if Pulse.configuration.secure?
45
+ http.use_ssl = true
46
+ http.ca_file = Pulse.configuration.ca_bundle_path
47
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
48
+ end
49
+
50
+ post = Net::HTTP::Post.new("/deploys.txt")
51
+ post.set_form_data(params)
52
+
53
+ if dry_run
54
+ puts http.inspect, params.inspect
55
+ return true
56
+ else
57
+ response = http.request(post)
58
+
59
+ puts response.body
60
+ return Net::HTTPSuccess === response
61
+ end
62
+ end
63
+ end
64
+