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 +70 -19
- data/gemspec.rb +1 -1
- data/lib/capistrano/lilypad.rb +21 -0
- data/lib/lilypad.rb +52 -0
- data/lib/lilypad/adapters/rails.rb +41 -0
- data/lib/lilypad/adapters/sinatra.rb +16 -0
- data/lib/lilypad/config.rb +57 -0
- data/lib/lilypad/config/request.rb +23 -0
- data/lib/lilypad/hoptoad/deploy.rb +48 -0
- data/lib/lilypad/hoptoad/notify.rb +87 -0
- data/lib/lilypad/hoptoad/xml.rb +61 -0
- data/lib/lilypad/log.rb +51 -0
- data/lib/rack/lilypad.rb +9 -143
- data/spec/fixtures/rails/config/environment.rb +2 -2
- data/spec/fixtures/rails/log/production.log +15987 -620
- data/spec/fixtures/sinatra.rb +5 -1
- data/spec/lilypad/adapters/rails_spec.rb +67 -0
- data/spec/lilypad/adapters/sinatra_spec.rb +49 -0
- data/spec/lilypad/config/request_spec.rb +27 -0
- data/spec/lilypad/config_spec.rb +59 -0
- data/spec/lilypad/hoptoad/deploy_spec.rb +85 -0
- data/spec/lilypad/hoptoad/notify_spec.rb +138 -0
- data/spec/lilypad_spec.rb +76 -0
- data/spec/spec_helper.rb +12 -2
- metadata +19 -6
- data/lib/rack/lilypad/rails.rb +0 -27
- data/spec/fixtures/rails/log/development.log +0 -4871
- data/spec/fixtures/rails/log/staging.log +0 -196
- data/spec/rack/lilypad_spec.rb +0 -128
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 '
|
27
|
+
require 'lilypad'
|
20
28
|
|
21
29
|
Rails::Initializer.run do |config|
|
22
|
-
config.middleware.insert_after(ActionController::Failsafe, Rack::Lilypad
|
30
|
+
config.middleware.insert_after(ActionController::Failsafe, Rack::Lilypad)
|
23
31
|
end
|
24
32
|
|
25
|
-
|
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 '
|
43
|
+
require 'lilypad'
|
33
44
|
|
34
45
|
class MyApp < Sinatra::Application
|
35
|
-
|
36
|
-
|
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
|
-
|
41
|
-
|
53
|
+
Error Redirection
|
54
|
+
-----------------
|
42
55
|
|
43
|
-
|
56
|
+
Conditionally redirect errors to different Hoptoad buckets.
|
44
57
|
|
45
58
|
<pre>
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
|
81
|
+
Lilypad.notify(e)
|
61
82
|
end
|
62
83
|
</pre>
|
63
84
|
|
64
|
-
|
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
|
-
|
111
|
+
Below are the available options and their default values:
|
68
112
|
|
69
113
|
<pre>
|
70
|
-
|
71
|
-
|
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
@@ -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
|