lilypad 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -10,46 +10,67 @@ Install
10
10
  sudo gem install lilypad --source http://gemcutter.org
11
11
  </pre>
12
12
 
13
+ Basic Usage
14
+ -----------
15
+
16
+ <pre>
17
+ require 'lilypad'
18
+ use Rack::Lilypad, 'hoptoad_api_key_goes_here'
19
+ </pre>
20
+
13
21
  Rails
14
22
  -----
15
23
 
16
24
  **config/environment.rb**
17
25
 
18
26
  <pre>
19
- require 'rack/lilypad'
27
+ require 'lilypad'
20
28
 
21
29
  Rails::Initializer.run do |config|
22
- config.middleware.insert_after(ActionController::Failsafe, Rack::Lilypad, 'hoptoad_api_key_goes_here')
30
+ config.middleware.insert_after(ActionController::Failsafe, Rack::Lilypad)
23
31
  end
24
32
 
25
- require 'rack/lilypad/rails'
33
+ Lilypad do
34
+ api_key 'hoptoad_api_key_goes_here'
35
+ rails
36
+ end
26
37
  </pre>
27
38
 
28
39
  Sinatra
29
40
  -------
30
41
 
31
42
  <pre>
32
- require 'rack/lilypad'
43
+ require 'lilypad'
33
44
 
34
45
  class MyApp < Sinatra::Application
35
- enable :raise_errors # Not needed when inheriting from Sinatra::Base
36
- use Rack::Lilypad, 'hoptoad_api_key_goes_here'
46
+ use Rack::Lilypad do
47
+ api_key 'hoptoad_api_key_goes_here'
48
+ sinatra
49
+ end
37
50
  end
38
51
  </pre>
39
52
 
40
- Filters
41
- -------
53
+ Error Redirection
54
+ -----------------
42
55
 
43
- Don't send certain environment variables to Hoptoad.
56
+ Conditionally redirect errors to different Hoptoad buckets.
44
57
 
45
58
  <pre>
46
- use Rack::Lilypad, 'hoptoad_api_key_goes_here' do |hoptoad|
47
- hoptoad.filters << %w(AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK)
59
+ Lilypad do
60
+ api_key do |env, exception|
61
+ if exception && exception.message =~ /No route matches/
62
+ 'hoptoad_api_key_goes_here'
63
+ elsif env && env['HTTP_USER_AGENT'] =~ /Googlebot/
64
+ 'hoptoad_api_key_goes_here'
65
+ else
66
+ 'hoptoad_api_key_goes_here'
67
+ end
68
+ end
48
69
  end
49
70
  </pre>
50
71
 
51
- Direct Access
52
- -------------
72
+ Notify
73
+ ------
53
74
 
54
75
  Send exceptions to Hoptoad from a rescue block.
55
76
 
@@ -57,18 +78,48 @@ Send exceptions to Hoptoad from a rescue block.
57
78
  begin
58
79
  raise 'Test'
59
80
  rescue Exception => e
60
- Rack::Lilypad.notify(e)
81
+ Lilypad.notify(e)
61
82
  end
62
83
  </pre>
63
84
 
64
- Log
65
- ---
85
+ Deploy
86
+ ------
87
+
88
+ Send deploy notifications to Hoptoad.
89
+
90
+ **deploy.rb**
91
+
92
+ <pre>
93
+ require 'capistrano/lilypad'
94
+ Lilypad { api_key 'hoptoad_api_key_goes_here' }
95
+ </pre>
96
+
97
+ Or you can do it manually:
98
+
99
+ <pre>
100
+ Lilypad.deploy(
101
+ :environment => 'production',
102
+ :repository => 'git@github.com:winton/lilypad.git',
103
+ :revision => '8acc488967085987f0a9f2c662383119f83e1bb8',
104
+ :username => 'winton'
105
+ )
106
+ </pre>
107
+
108
+ Options
109
+ -------
66
110
 
67
- See what you are sending and receiving from Hoptoad.
111
+ Below are the available options and their default values:
68
112
 
69
113
  <pre>
70
- use Rack::Lilypad, 'hoptoad_api_key_goes_here' do |hoptoad|
71
- hoptoad.log = '/var/www/log/hoptoad.log'
114
+ Lilypad do
115
+ api_key nil
116
+ environments %w(production staging)
117
+ deploy_url 'http://hoptoadapp.com:80/deploys.txt'
118
+ notify_url 'http://hoptoadapp.com:80/notifier_api/v2/notices'
119
+ filters [] # Array of environment variables to hide from Hoptoad
120
+ log nil # Path of Hoptoad log
121
+ rails # Requires the Rails adapter
122
+ sinatra # Requires the Sinatra adapter
72
123
  end
73
124
  </pre>
74
125
 
data/gemspec.rb CHANGED
@@ -14,5 +14,5 @@ GEM_SPEC = Gem::Specification.new do |s|
14
14
  s.name = GEM_NAME
15
15
  s.platform = Gem::Platform::RUBY
16
16
  s.require_path = "lib"
17
- s.version = "0.2.4"
17
+ s.version = "0.3.0"
18
18
  end
@@ -0,0 +1,21 @@
1
+ require File.expand_path("#{File.dirname __FILE__}/../lilypad")
2
+
3
+ Capistrano::Configuration.instance(:must_exist).load do
4
+
5
+ after "deploy", "hoptoad:notify"
6
+ after "deploy:long", "hoptoad:notify"
7
+ after "deploy:migrations", "hoptoad:notify"
8
+
9
+ namespace :hoptoad do
10
+ desc "Notify Hoptoad of the deployment"
11
+ task :notify, :except => { :no_release => true } do
12
+ ENV['RACK_ENV'] = fetch(:rails_env, "production")
13
+ Lilypad.deploy(
14
+ :environment => ENV['RACK_ENV'],
15
+ :repository => repository,
16
+ :revision => current_revision,
17
+ :username => ENV['USER'] || ENV['USERNAME']
18
+ )
19
+ end
20
+ end
21
+ end
data/lib/lilypad.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'builder'
2
+ require 'net/http'
3
+ require 'rack'
4
+
5
+ unless defined?(::Lilypad)
6
+ lib = File.dirname(__FILE__)
7
+ require "#{lib}/lilypad/config"
8
+ require "#{lib}/lilypad/config/request"
9
+ require "#{lib}/lilypad/log"
10
+ require "#{lib}/lilypad/hoptoad/deploy"
11
+ require "#{lib}/lilypad/hoptoad/notify"
12
+ require "#{lib}/lilypad/hoptoad/xml"
13
+ require "#{lib}/rack/lilypad"
14
+ end
15
+
16
+ class Lilypad
17
+ class <<self
18
+
19
+ def active?
20
+ Config.api_key
21
+ end
22
+
23
+ def config(api_key=nil, &block)
24
+ if api_key
25
+ Config.api_key api_key
26
+ end
27
+ if block_given?
28
+ Config.class_eval &block
29
+ end
30
+ end
31
+
32
+ def deploy(options)
33
+ if active? && production?
34
+ Hoptoad::Deploy.new options
35
+ end
36
+ end
37
+
38
+ def notify(exception, env=nil)
39
+ if active? && production?
40
+ Hoptoad::Notify.new env, exception
41
+ end
42
+ end
43
+
44
+ def production?
45
+ Config.environments.include? ENV['RACK_ENV']
46
+ end
47
+ end
48
+ end
49
+
50
+ def Lilypad(api_key=nil, &block)
51
+ Lilypad.config api_key, &block
52
+ end
@@ -0,0 +1,41 @@
1
+ class Lilypad
2
+ module Rails
3
+
4
+ def self.included(base)
5
+ ENV['RACK_ENV'] = ENV['RAILS_ENV']
6
+ if Lilypad.production? && !base.included_modules.include?(InstanceMethods)
7
+ base.send :extend, ClassMethods
8
+ base.send :include, InstanceMethods
9
+ base.send :alias_method_chain, :rescue_action, :lilypad
10
+ base.class_eval do
11
+ class <<self
12
+ alias_method_chain :call_with_exception, :lilypad
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ def call_with_exception_with_lilypad(env, exception)
21
+ raise exception
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+
27
+ private
28
+
29
+ def rescue_action_with_lilypad(exception)
30
+ rescue_action_without_lilypad exception
31
+ Config::Request.action params[:action]
32
+ Config::Request.component params[:controller]
33
+ raise exception
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ if defined?(ActionController::Base)
40
+ ActionController::Base.send(:include, Lilypad::Rails)
41
+ end
@@ -0,0 +1,16 @@
1
+ class Lilypad
2
+ module Sinatra
3
+
4
+ def self.included(base)
5
+ base.set(:raise_errors, true) if Lilypad.production?
6
+ end
7
+ end
8
+ end
9
+
10
+ if defined?(Sinatra::Base)
11
+ Sinatra::Base.send(:include, Lilypad::Sinatra)
12
+ end
13
+
14
+ if defined?(Sinatra::Application)
15
+ Sinatra::Application.send(:include, Lilypad::Sinatra)
16
+ end
@@ -0,0 +1,57 @@
1
+ class Lilypad
2
+ class Config
3
+ class <<self
4
+
5
+ def api_key(api_key=nil, &block)
6
+ @api_key = api_key unless api_key.nil?
7
+ @api_key_block = block if block_given?
8
+ @api_key || @api_key_block
9
+ end
10
+
11
+ def deploy_url(url=nil)
12
+ @deploy_url = url unless url.nil?
13
+ @deploy_url || "http://hoptoadapp.com:80/deploys.txt"
14
+ end
15
+
16
+ def environments(environments=nil)
17
+ @environments = environments unless environments.nil?
18
+ @environments || %w(production staging)
19
+ end
20
+
21
+ def filters(filters=nil)
22
+ @filters = filters unless filters.nil?
23
+ @filters || []
24
+ end
25
+
26
+ def log(log=nil)
27
+ @log = log unless log.nil?
28
+ @log
29
+ end
30
+
31
+ def notify_url(url=nil)
32
+ @notify_url = url unless url.nil?
33
+ @notify_url || "http://hoptoadapp.com:80/notifier_api/v2/notices"
34
+ end
35
+
36
+ def rails
37
+ require "#{File.dirname(__FILE__)}/adapters/rails"
38
+ end
39
+
40
+ def sinatra
41
+ require "#{File.dirname(__FILE__)}/adapters/sinatra"
42
+ end
43
+
44
+ end
45
+
46
+ module Methods
47
+
48
+ def api_key(env=nil, exception=nil)
49
+ if Config.api_key.respond_to?(:call)
50
+ Config.api_key.call env, exception
51
+ else
52
+ Config.api_key
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ class Lilypad
2
+ class Config
3
+ class Request
4
+ class <<self
5
+
6
+ def action(action=nil)
7
+ @action = action unless action.nil?
8
+ @action
9
+ end
10
+
11
+ def component(component=nil)
12
+ @component = component unless component.nil?
13
+ @component
14
+ end
15
+
16
+ def reset!
17
+ @action = nil
18
+ @component = nil
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ class Lilypad
2
+ class Hoptoad
3
+ class Deploy
4
+
5
+ include Config::Methods
6
+ include Log::Methods
7
+
8
+ def initialize(options)
9
+ @options = options
10
+
11
+ begin
12
+ post
13
+ rescue Exception => e
14
+ end
15
+
16
+ log :deploy, @response
17
+ success?
18
+ end
19
+
20
+ class <<self
21
+ def last_request
22
+ defined?(@@last_request) ? @@last_request : nil
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def params
29
+ @@last_request = {
30
+ 'api_key' => api_key,
31
+ 'deploy[local_username]' => @options[:username],
32
+ 'deploy[rails_env]' => @options[:environment],
33
+ 'deploy[scm_revision]' => @options[:revision],
34
+ 'deploy[scm_repository]' => @options[:repository]
35
+ }
36
+ end
37
+
38
+ def post
39
+ url = URI.parse Config.deploy_url
40
+ @response = Net::HTTP.post_form url, params
41
+ end
42
+
43
+ def success?
44
+ @response.class.superclass == Net::HTTPSuccess
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,87 @@
1
+ class Lilypad
2
+ class Hoptoad
3
+ class Notify
4
+
5
+ include Log::Methods
6
+
7
+ def initialize(env, exception)
8
+ @exception = exception
9
+ @env = env
10
+
11
+ http_start do |http|
12
+ begin
13
+ xml = XML.build *parse
14
+ http.post @uri.path, xml, headers
15
+ rescue Exception => e
16
+ end
17
+ end
18
+
19
+ if env && success?
20
+ env['hoptoad.notified'] = true
21
+ end
22
+
23
+ Config::Request.reset!
24
+ log :notify, @response
25
+ success?
26
+ end
27
+
28
+ private
29
+
30
+ class Backtrace < Struct.new(:file, :number, :method)
31
+ end
32
+
33
+ def backtrace
34
+ regex = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}
35
+ @exception.backtrace.map do |line|
36
+ _, file, number, method = line.match(regex).to_a
37
+ Backtrace.new file, number, method
38
+ end
39
+ end
40
+
41
+ def filter(hash)
42
+ return hash if Config.filters.empty?
43
+ hash.inject({}) do |acc, (key, val)|
44
+ match = Config.filters.any? { |f| key.to_s =~ Regexp.new(f) }
45
+ acc[key] = match ? "[FILTERED]" : val
46
+ acc
47
+ end
48
+ end
49
+
50
+ def headers
51
+ {
52
+ 'Content-type' => 'text/xml',
53
+ 'Accept' => 'text/xml, application/xml'
54
+ }
55
+ end
56
+
57
+ def http_start(&block)
58
+ @uri = URI.parse Config.notify_url
59
+ Net::HTTP.start @uri.host, @uri.port do |http|
60
+ http.read_timeout = 5 # seconds
61
+ http.open_timeout = 2 # seconds
62
+ @response = yield http
63
+ end
64
+ end
65
+
66
+ def parse
67
+ env = filter ENV.to_hash.merge(@env || {})
68
+
69
+ if @env
70
+ request = Rack::Request.new @env
71
+ params = request.params rescue Hash.new
72
+ params = filter params
73
+ request_path = request.script_name + request.path_info
74
+ else
75
+ params = {}
76
+ request_path = 'Internal'
77
+ end
78
+
79
+ [ backtrace, env, @exception, params, request_path ]
80
+ end
81
+
82
+ def success?
83
+ @response.class.superclass == Net::HTTPSuccess
84
+ end
85
+ end
86
+ end
87
+ end