lilypad 0.2.4 → 0.3.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.
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