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,17 @@
1
+ When /I run rake with (.+)/ do |command|
2
+ @rake_command = "rake #{command.gsub(' ','_')}"
3
+ @rake_result = `cd features/support/rake && GEM_HOME=#{BUILT_GEM_ROOT} #{@rake_command} 2>&1`
4
+ end
5
+
6
+ Then /Pulse should (|not) ?catch the exception/ do |condition|
7
+ if condition=='not'
8
+ @rake_result.should_not =~ /^pulse/
9
+ else
10
+ @rake_result.should =~ /^pulse/
11
+ end
12
+ end
13
+
14
+ Then /Pulse should send the rake command line as the component name/ do
15
+ component = @rake_result.match(/^pulse (.*)$/)[1]
16
+ component.should == @rake_command
17
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support'
2
+ require 'nokogiri'
3
+ require 'rspec'
4
+
5
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
6
+ TEMP_DIR = File.join(PROJECT_ROOT, 'tmp').freeze
7
+ LOCAL_RAILS_ROOT = File.join(TEMP_DIR, 'rails_root').freeze
8
+ BUILT_GEM_ROOT = File.join(TEMP_DIR, 'built_gems').freeze
9
+ LOCAL_GEM_ROOT = File.join(TEMP_DIR, 'local_gems').freeze
10
+ RACK_FILE = File.join(TEMP_DIR, 'rack_app.rb').freeze
11
+
12
+ Before do
13
+ FileUtils.mkdir_p(TEMP_DIR)
14
+ FileUtils.rm_rf(BUILT_GEM_ROOT)
15
+ FileUtils.rm_rf(LOCAL_RAILS_ROOT)
16
+ FileUtils.rm_f(RACK_FILE)
17
+ FileUtils.mkdir_p(BUILT_GEM_ROOT)
18
+ end
@@ -0,0 +1,35 @@
1
+ RSpec::Matchers.define :have_content do |xpath, content|
2
+ match do |document|
3
+ @elements = document.search(xpath)
4
+
5
+ if @elements.empty?
6
+ false
7
+ else
8
+ element_with_content = document.at("#{xpath}[contains(.,'#{content}')]")
9
+
10
+ if element_with_content.nil?
11
+ @found = @elements.collect { |element| element.content }
12
+
13
+ false
14
+ else
15
+ true
16
+ end
17
+ end
18
+ end
19
+
20
+ failure_message_for_should do |document|
21
+ if @elements.empty?
22
+ "In XML:\n#{document}\nNo element at #{xpath}"
23
+ else
24
+ "In XML:\n#{document}\nGot content #{@found.inspect} at #{xpath} instead of #{content.inspect}"
25
+ end
26
+ end
27
+
28
+ failure_message_for_should_not do |document|
29
+ unless @elements.empty?
30
+ "In XML:\n#{document}\nExpcted no content #{content.inspect} at #{xpath}"
31
+ end
32
+ end
33
+ end
34
+
35
+ World(RSpec::Matchers)
@@ -0,0 +1,16 @@
1
+ require 'sham_rack'
2
+
3
+ Pulse.configuration.logger = Logger.new STDOUT if defined?(Pulse)
4
+
5
+ ShamRack.at("errors.projectlocker.com") do |env|
6
+ response = <<-end_xml
7
+ <notice>
8
+ <id>b6817316-9c45-ed26-45eb-780dbb86aadb</id>
9
+ <url>http://errors.projectlocker.com/locate/b6817316-9c45-ed26-45eb-780dbb86aadb</url>
10
+ </notice>
11
+
12
+ Request:
13
+ #{env["rack.input"].read}
14
+ end_xml
15
+ ["200 OK", { "Content-type" => "text/xml" }, [response]]
16
+ end
@@ -0,0 +1,201 @@
1
+ module RailsHelpers
2
+ def rails_root_exists?
3
+ File.exists?(environment_path)
4
+ end
5
+
6
+ def application_controller_filename
7
+ controller_filename = File.join(rails_root, 'app', 'controllers', "application_controller.rb")
8
+ end
9
+
10
+ def rails3?
11
+ rails_version =~ /^3/
12
+ end
13
+
14
+ def rails_root
15
+ LOCAL_RAILS_ROOT
16
+ end
17
+
18
+ def rails_uses_rack?
19
+ rails3? || rails_version =~ /^2\.3/
20
+ end
21
+
22
+ def rails_version
23
+ @rails_version ||= begin
24
+ if ENV["RAILS_VERSION"]
25
+ ENV["RAILS_VERSION"]
26
+ elsif bundler_manages_gems?
27
+ rails_version = open(gemfile_path).read.match(/gem.*rails["'].*["'](.+)["']/)[1]
28
+ else
29
+ environment_file = File.join(rails_root, 'config', 'environment.rb')
30
+ rails_version = `grep RAILS_GEM_VERSION #{environment_file}`.match(/[\d.]+/)[0]
31
+ end
32
+ end
33
+ end
34
+
35
+ def bundler_manages_gems?
36
+ File.exists?(gemfile_path)
37
+ end
38
+
39
+ def gemfile_path
40
+ gemfile = File.join(rails_root, 'Gemfile')
41
+ end
42
+
43
+ def rails_manages_gems?
44
+ rails_version =~ /^2\.[123]/
45
+ end
46
+
47
+ def rails_supports_initializers?
48
+ rails3? || rails_version =~ /^2\./
49
+ end
50
+
51
+ def rails_finds_generators_in_gems?
52
+ rails3? || rails_version =~ /^2\./
53
+ end
54
+
55
+ def version_string
56
+ ENV['RAILS_VERSION'] || `tail -n 1 SUPPORTED_RAILS_VERSIONS` # use latest version if ENV["RAILS_VERSION"] is undefined
57
+ end
58
+
59
+ def environment_path
60
+ File.join(rails_root, 'config', 'environment.rb')
61
+ end
62
+
63
+ def rakefile_path
64
+ File.join(rails_root, 'Rakefile')
65
+ end
66
+
67
+ def bundle_gem(gem_name, version = nil)
68
+ File.open(gemfile_path, 'a') do |file|
69
+ gem = "gem '#{gem_name}'"
70
+ gem += ", '#{version}'" if version
71
+ file.puts(gem)
72
+ end
73
+ end
74
+
75
+ def config_gem(gem_name, version = nil)
76
+ run = "Rails::Initializer.run do |config|"
77
+ insert = " config.gem '#{gem_name}'"
78
+ insert += ", :version => '#{version}'" if version
79
+ content = File.read(environment_path)
80
+ content = "require 'thread'\n#{content}"
81
+ if content.sub!(run, "#{run}\n#{insert}")
82
+ File.open(environment_path, 'wb') { |file| file.write(content) }
83
+ else
84
+ raise "Couldn't find #{run.inspect} in #{environment_path}"
85
+ end
86
+ end
87
+
88
+ def config_gem_dependencies
89
+ insert = <<-END
90
+ if Gem::VERSION >= "1.3.6"
91
+ module Rails
92
+ class GemDependency
93
+ def requirement
94
+ r = super
95
+ (r == Gem::Requirement.default) ? nil : r
96
+ end
97
+ end
98
+ end
99
+ end
100
+ END
101
+ run = "Rails::Initializer.run do |config|"
102
+ content = File.read(environment_path)
103
+ if content.sub!(run, "#{insert}\n#{run}")
104
+ File.open(environment_path, 'wb') { |file| file.write(content) }
105
+ else
106
+ raise "Couldn't find #{run.inspect} in #{environment_path}"
107
+ end
108
+ end
109
+
110
+ def require_thread
111
+ content = File.read(rakefile_path)
112
+ content = "require 'thread'\n#{content}"
113
+ File.open(rakefile_path, 'wb') { |file| file.write(content) }
114
+ end
115
+
116
+ def perform_request(uri, environment = 'production')
117
+ if rails3?
118
+ request_script = <<-SCRIPT
119
+ require File.expand_path('../config/environment', __FILE__)
120
+
121
+
122
+ env = Rack::MockRequest.env_for(#{uri.inspect})
123
+ response = RailsRoot::Application.call(env)
124
+
125
+
126
+ response = response.last if response.last.is_a?(ActionDispatch::Response)
127
+
128
+ if response.is_a?(Array)
129
+ puts response.join
130
+ else
131
+ puts response.body
132
+ end
133
+ SCRIPT
134
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
135
+ @terminal.cd(rails_root)
136
+ @terminal.run("ruby -rthread ./script/rails runner -e #{environment} request.rb")
137
+ elsif rails_uses_rack?
138
+ request_script = <<-SCRIPT
139
+ require File.expand_path('../config/environment', __FILE__)
140
+
141
+ env = Rack::MockRequest.env_for(#{uri.inspect})
142
+ app = Rack::Lint.new(ActionController::Dispatcher.new)
143
+
144
+ status, headers, body = app.call(env)
145
+
146
+ response = ""
147
+ if body.respond_to?(:to_str)
148
+ response << body
149
+ else
150
+ body.each { |part| response << part }
151
+ end
152
+
153
+ puts response
154
+ SCRIPT
155
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
156
+ @terminal.cd(rails_root)
157
+ @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
158
+ else
159
+ uri = URI.parse(uri)
160
+ request_script = <<-SCRIPT
161
+ require 'cgi'
162
+ class CGIWrapper < CGI
163
+ def initialize(*args)
164
+ @env_table = {}
165
+ @stdinput = $stdin
166
+ super(*args)
167
+ end
168
+ attr_reader :env_table
169
+ end
170
+ $stdin = StringIO.new("")
171
+ cgi = CGIWrapper.new
172
+ cgi.env_table.update({
173
+ 'HTTPS' => 'off',
174
+ 'REQUEST_METHOD' => "GET",
175
+ 'HTTP_HOST' => #{[uri.host, uri.port].join(':').inspect},
176
+ 'SERVER_PORT' => #{uri.port.inspect},
177
+ 'REQUEST_URI' => #{uri.request_uri.inspect},
178
+ 'PATH_INFO' => #{uri.path.inspect},
179
+ 'QUERY_STRING' => #{uri.query.inspect}
180
+ })
181
+ require 'dispatcher' unless defined?(ActionController::Dispatcher)
182
+ Dispatcher.dispatch(cgi)
183
+ SCRIPT
184
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
185
+ @terminal.cd(rails_root)
186
+ @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
187
+ end
188
+ end
189
+
190
+ def monkeypatch_old_version
191
+ monkeypatchin= <<-MONKEYPATCHIN
192
+
193
+ MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1]
194
+
195
+ MONKEYPATCHIN
196
+
197
+ File.open(File.join(rails_root,"config","initializers", 'monkeypatchin.rb'), 'w') { |file| file.write(monkeypatchin) }
198
+ end
199
+ end
200
+
201
+ World(RailsHelpers)
@@ -0,0 +1,68 @@
1
+ # A test harness for RakeHandler
2
+ #
3
+ require 'rake'
4
+ require 'rubygems'
5
+ require 'projectlocker_pulse'
6
+ require 'pulse/rake_handler'
7
+
8
+ Pulse.configure do |c|
9
+ end
10
+
11
+ # Should catch exception
12
+ task :pulse do
13
+ Pulse.configuration.rescue_rake_exceptions = true
14
+ stub_tty_output(true)
15
+ raise_exception
16
+ end
17
+
18
+ # Should not catch exception
19
+ task :pulse_disabled do
20
+ Pulse.configuration.rescue_rake_exceptions = false
21
+ stub_tty_output(true)
22
+ raise_exception
23
+ end
24
+
25
+ # Should not catch exception as tty_output is true
26
+ task :pulse_autodetect_from_terminal do
27
+ Pulse.configuration.rescue_rake_exceptions = nil
28
+ stub_tty_output(true)
29
+ raise_exception
30
+ end
31
+
32
+ # Should catch exception as tty_output is false
33
+ task :pulse_autodetect_not_from_terminal do
34
+ Pulse.configuration.rescue_rake_exceptions = nil
35
+ stub_tty_output(false)
36
+ raise_exception
37
+ end
38
+
39
+ task :pulse_not_yet_configured do
40
+ Pulse.configuration.rescue_rake_exceptions = true
41
+ stub_tty_output(true)
42
+ stub_empty_sender
43
+ raise_exception
44
+ end
45
+
46
+ module Pulse
47
+ def self.notify_or_ignore(*args)
48
+ # TODO if you need to check more params, you'll have to use json.dump or something
49
+ $stderr.puts "pulse #{args[1][:component]}"
50
+ end
51
+ end
52
+
53
+ def stub_empty_sender
54
+ Pulse.sender = nil
55
+ end
56
+
57
+ def stub_tty_output(value)
58
+ Rake.application.instance_eval do
59
+ @tty_output_stub = value
60
+ def tty_output?
61
+ @tty_output_stub
62
+ end
63
+ end
64
+ end
65
+
66
+ def raise_exception
67
+ raise 'TEST'
68
+ end
@@ -0,0 +1,107 @@
1
+ require 'fileutils'
2
+
3
+ Before do
4
+ @terminal = Terminal.new
5
+ end
6
+
7
+ After do |story|
8
+ if story.failed?
9
+ # puts @terminal.output
10
+ end
11
+ end
12
+
13
+ class Terminal
14
+ attr_reader :output, :status
15
+ attr_accessor :environment_variables, :invoke_heroku_rake_tasks_locally
16
+
17
+ def initialize
18
+ @cwd = FileUtils.pwd
19
+ @output = ""
20
+ @status = 0
21
+ @logger = Logger.new(File.join(TEMP_DIR, 'terminal.log'))
22
+
23
+ @invoke_heroku_rake_tasks_locally = false
24
+
25
+ @environment_variables = {
26
+ "GEM_HOME" => LOCAL_GEM_ROOT,
27
+ "GEM_PATH" => "#{LOCAL_GEM_ROOT}:#{BUILT_GEM_ROOT}",
28
+ "PATH" => "#{gem_bin_path}:#{ENV['PATH']}"
29
+ }
30
+ end
31
+
32
+ def cd(directory)
33
+ @cwd = directory
34
+ end
35
+
36
+ def run(command)
37
+ command = optionally_invoke_heroku_rake_tasks_locally(command)
38
+
39
+ output << "#{command}\n"
40
+ FileUtils.cd(@cwd) do
41
+ # The ; forces ruby to shell out so the env settings work right
42
+ cmdline = "#{environment_settings} #{command} 2>&1 ; "
43
+ logger.debug(cmdline)
44
+ result = `#{cmdline}`
45
+ logger.debug(result)
46
+ output << result
47
+ end
48
+ @status = $?
49
+ end
50
+
51
+ def optionally_invoke_heroku_rake_tasks_locally(command)
52
+ if invoke_heroku_rake_tasks_locally
53
+ command.sub(/^heroku /, '')
54
+ else
55
+ command
56
+ end
57
+ end
58
+
59
+ def echo(string)
60
+ logger.debug(string)
61
+ end
62
+
63
+ def flush!
64
+ @output = ""
65
+ end
66
+
67
+ def build_and_install_gem(gemspec)
68
+ pkg_dir = File.join(TEMP_DIR, 'pkg')
69
+ FileUtils.mkdir_p(pkg_dir)
70
+ output = `gem build #{gemspec} 2>&1`
71
+ gem_file = Dir.glob("*.gem").first
72
+ unless gem_file
73
+ raise "Gem didn't build:\n#{output}"
74
+ end
75
+ target = File.join(pkg_dir, gem_file)
76
+ FileUtils.mv(gem_file, target)
77
+ install_gem_to(LOCAL_GEM_ROOT, target)
78
+ end
79
+
80
+ def install_gem(gem)
81
+ install_gem_to(LOCAL_GEM_ROOT, gem)
82
+ end
83
+
84
+ def uninstall_gem(gem)
85
+ `gem uninstall -i #{LOCAL_GEM_ROOT} #{gem}`
86
+ end
87
+
88
+ def prepend_path(path)
89
+ @environment_variables['PATH'] = path + ":" + @environment_variables['PATH']
90
+ end
91
+
92
+ private
93
+
94
+ def install_gem_to(root, gem)
95
+ `gem install -i #{root} --no-ri --no-rdoc #{gem}`
96
+ end
97
+
98
+ def environment_settings
99
+ @environment_variables.map { |key, value| "#{key}=#{value}" }.join(' ')
100
+ end
101
+
102
+ def gem_bin_path
103
+ File.join(LOCAL_GEM_ROOT, "bin")
104
+ end
105
+
106
+ attr_reader :logger
107
+ end
@@ -0,0 +1,63 @@
1
+ Feature: Inform the user of the Pulse notice that was just created
2
+
3
+ Background:
4
+ Given I have built and installed the "projectlocker-pulse" gem
5
+
6
+ Scenario: Rescue an exception in a controller
7
+ When I generate a new Rails application
8
+ And I configure the Pulse shim
9
+ And I configure my application to require the "projectlocker-pulse" gem
10
+ And I run the pulse generator with "-k myapikey"
11
+ And I define a response for "TestController#index":
12
+ """
13
+ raise RuntimeError, "some message"
14
+ """
15
+ And the response page for a "500" error is
16
+ """
17
+ <!-- PROJECTLOCKER_ERRATA ERROR -->
18
+ """
19
+ And I route "/test/index" to "test#index"
20
+ And I perform a request to "http://example.com:123/test/index?param=value"
21
+ Then I should see "Pulse Error b6817316-9c45-ed26-45eb-780dbb86aadb"
22
+
23
+ Scenario: Rescue an exception in a controller with a custom error string
24
+ When I generate a new Rails application
25
+ And I configure the Pulse shim
26
+ And I configure my application to require the "projectlocker-pulse" gem
27
+ And I configure the notifier to use the following configuration lines:
28
+ """
29
+ config.user_information = 'Error #{{ error_id }}'
30
+ """
31
+ And I run the pulse generator with "-k myapikey"
32
+ And I define a response for "TestController#index":
33
+ """
34
+ raise RuntimeError, "some message"
35
+ """
36
+ And the response page for a "500" error is
37
+ """
38
+ <!-- PROJECTLOCKER_ERRATA ERROR -->
39
+ """
40
+ And I route "/test/index" to "test#index"
41
+ And I perform a request to "http://example.com:123/test/index?param=value"
42
+ Then I should see "Error #b6817316-9c45-ed26-45eb-780dbb86aadb"
43
+
44
+ Scenario: Don't inform them user
45
+ When I generate a new Rails application
46
+ And I configure the Pulse shim
47
+ And I configure my application to require the "projectlocker-pulse" gem
48
+ And I configure the notifier to use the following configuration lines:
49
+ """
50
+ config.user_information = false
51
+ """
52
+ And I run the pulse generator with "-k myapikey"
53
+ And I define a response for "TestController#index":
54
+ """
55
+ raise RuntimeError, "some message"
56
+ """
57
+ And the response page for a "500" error is
58
+ """
59
+ <!-- PROJECTLOCKER_ERRATA ERROR -->
60
+ """
61
+ And I route "/test/index" to "test#index"
62
+ And I perform a request to "http://example.com:123/test/index?param=value"
63
+ Then I should not see "Pulse Error b6817316-9c45-ed26-45eb-780dbb86aadb"
@@ -0,0 +1,34 @@
1
+ # Mostly pinched from http://github.com/ryanb/nifty-generators/tree/master
2
+
3
+ Rails::Generator::Commands::Base.class_eval do
4
+ def file_contains?(relative_destination, line)
5
+ File.read(destination_path(relative_destination)).include?(line)
6
+ end
7
+ end
8
+
9
+ Rails::Generator::Commands::Create.class_eval do
10
+ def append_to(file, line)
11
+ logger.insert "#{line} appended to #{file}"
12
+ unless options[:pretend] || file_contains?(file, line)
13
+ File.open(file, "a") do |file|
14
+ file.puts
15
+ file.puts line
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Rails::Generator::Commands::Destroy.class_eval do
22
+ def append_to(file, line)
23
+ logger.remove "#{line} removed from #{file}"
24
+ unless options[:pretend]
25
+ gsub_file file, "\n#{line}", ''
26
+ end
27
+ end
28
+ end
29
+
30
+ Rails::Generator::Commands::List.class_eval do
31
+ def append_to(file, line)
32
+ logger.insert "#{line} appended to #{file}"
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ Rails::Generator::Commands::Create.class_eval do
2
+ def rake(cmd, opts = {})
3
+ logger.rake "rake #{cmd}"
4
+ unless system("rake #{cmd}")
5
+ logger.rake "#{cmd} failed. Rolling back"
6
+ command(:destroy).invoke!
7
+ end
8
+ end
9
+ end
10
+
11
+ Rails::Generator::Commands::Destroy.class_eval do
12
+ def rake(cmd, opts = {})
13
+ unless opts[:generate_only]
14
+ logger.rake "rake #{cmd}"
15
+ system "rake #{cmd}"
16
+ end
17
+ end
18
+ end
19
+
20
+ Rails::Generator::Commands::List.class_eval do
21
+ def rake(cmd, opts = {})
22
+ logger.rake "rake #{cmd}"
23
+ end
24
+ end
@@ -0,0 +1,94 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/lib/insert_commands.rb")
2
+ require File.expand_path(File.dirname(__FILE__) + "/lib/rake_commands.rb")
3
+
4
+ class PulseGenerator < Rails::Generator::Base
5
+ def add_options!(opt)
6
+ opt.on('-k', '--api-key=key', String, "Your Pulse API key") { |v| options[:api_key] = v}
7
+ opt.on('-h', '--heroku', "Use the Heroku addon to provide your Pulse API key") { |v| options[:heroku] = v}
8
+ opt.on('-a', '--app=myapp', String, "Your Heroku app name (only required if deploying to >1 Heroku app)") { |v| options[:app] = v}
9
+ end
10
+
11
+ def manifest
12
+ if !api_key_configured? && !options[:api_key] && !options[:heroku]
13
+ puts "Must pass --api-key or --heroku or create config/initializers/pulse.rb"
14
+ exit
15
+ end
16
+ if plugin_is_present?
17
+ puts "You must first remove the pulse plugin. Please run: script/plugin remove pulse"
18
+ exit
19
+ end
20
+ record do |m|
21
+ m.directory 'lib/tasks'
22
+ m.file 'pulse_tasks.rake', 'lib/tasks/pulse_tasks.rake'
23
+ if ['config/deploy.rb', 'Capfile'].all? { |file| File.exists?(file) }
24
+ m.append_to 'config/deploy.rb', capistrano_hook
25
+ end
26
+ if api_key_expression
27
+ if use_initializer?
28
+ m.template 'initializer.rb', 'config/initializers/pulse.rb',
29
+ :assigns => {:api_key => api_key_expression}
30
+ else
31
+ m.template 'initializer.rb', 'config/pulse.rb',
32
+ :assigns => {:api_key => api_key_expression}
33
+ m.append_to 'config/environment.rb', "require 'config/pulse'"
34
+ end
35
+ end
36
+ determine_api_key if heroku?
37
+ m.rake "pulse:test --trace", :generate_only => true
38
+ end
39
+ end
40
+
41
+ def api_key_expression
42
+ s = if options[:api_key]
43
+ "'#{options[:api_key]}'"
44
+ elsif options[:heroku]
45
+ "ENV['PULSE_API_KEY']"
46
+ end
47
+ end
48
+
49
+ def determine_api_key
50
+ puts "Attempting to determine your API Key from Heroku..."
51
+ ENV['PULSE_API_KEY'] = heroku_api_key
52
+ if ENV['PULSE_API_KEY'].blank?
53
+ puts "... Failed."
54
+ puts "WARNING: We were unable to detect the Pulse API Key from your Heroku environment."
55
+ puts "Your Heroku application environment may not be configured correctly."
56
+ exit 1
57
+ else
58
+ puts "... Done."
59
+ puts "Heroku's Pulse API Key is '#{ENV['PULSE_API_KEY']}'"
60
+ end
61
+ end
62
+
63
+ def heroku_var(var,app_name = nil)
64
+ app = app_name ? "--app #{app_name}" : ''
65
+ `heroku config #{app} | grep -E "#{var.upcase}" | awk '{ print $3; }'`.strip
66
+ end
67
+
68
+ def heroku_api_key
69
+ heroku_var("pulse_api_key",options[:app]).split.find {|x| x unless x.blank?}
70
+ end
71
+
72
+ def heroku?
73
+ options[:heroku] ||
74
+ system("grep PULSE_API_KEY config/initializers/pulse.rb") ||
75
+ system("grep PULSE_API_KEY config/environment.rb")
76
+ end
77
+
78
+ def use_initializer?
79
+ Rails::VERSION::MAJOR > 1
80
+ end
81
+
82
+ def api_key_configured?
83
+ File.exists?('config/initializers/pulse.rb') ||
84
+ system("grep Pulse config/environment.rb")
85
+ end
86
+
87
+ def capistrano_hook
88
+ IO.read(source_path('capistrano_hook.rb'))
89
+ end
90
+
91
+ def plugin_is_present?
92
+ File.exists?('vendor/plugins/pulse')
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+
2
+ Dir[File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'projectlocker-pulse-*')].each do |vendored_notifier|
3
+ $: << File.join(vendored_notifier, 'lib')
4
+ end
5
+
6
+ require 'pulse/capistrano'