scout 5.3.5 → 5.4.4.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG +0 -12
  3. data/Gemfile +4 -0
  4. data/README +8 -0
  5. data/Rakefile +6 -108
  6. data/bin/scout +1 -0
  7. data/lib/scout.rb +5 -4
  8. data/lib/scout/command.rb +11 -12
  9. data/lib/scout/command/install.rb +1 -1
  10. data/lib/scout/command/run.rb +13 -1
  11. data/lib/scout/command/sign.rb +2 -8
  12. data/lib/scout/command/stream.rb +50 -0
  13. data/lib/scout/command/test.rb +1 -1
  14. data/lib/scout/daemon_spawn.rb +215 -0
  15. data/lib/scout/plugin.rb +20 -1
  16. data/lib/scout/server.rb +16 -111
  17. data/lib/scout/server_base.rb +100 -0
  18. data/lib/scout/streamer.rb +162 -0
  19. data/lib/scout/streamer_control.rb +43 -0
  20. data/lib/scout/version.rb +3 -0
  21. data/scout.gemspec +27 -0
  22. data/test/plugins/disk_usage.rb +86 -0
  23. data/test/scout_test.rb +598 -0
  24. data/vendor/pusher-gem/Gemfile +2 -0
  25. data/vendor/pusher-gem/LICENSE +20 -0
  26. data/vendor/pusher-gem/README.md +80 -0
  27. data/vendor/pusher-gem/Rakefile +11 -0
  28. data/vendor/pusher-gem/examples/async_message.rb +28 -0
  29. data/vendor/pusher-gem/lib/pusher.rb +107 -0
  30. data/vendor/pusher-gem/lib/pusher/channel.rb +154 -0
  31. data/vendor/pusher-gem/lib/pusher/request.rb +107 -0
  32. data/vendor/pusher-gem/pusher.gemspec +28 -0
  33. data/vendor/pusher-gem/spec/channel_spec.rb +274 -0
  34. data/vendor/pusher-gem/spec/pusher_spec.rb +87 -0
  35. data/vendor/pusher-gem/spec/spec_helper.rb +13 -0
  36. data/vendor/ruby-hmac/History.txt +15 -0
  37. data/vendor/ruby-hmac/Manifest.txt +11 -0
  38. data/vendor/ruby-hmac/README.md +41 -0
  39. data/vendor/ruby-hmac/Rakefile +23 -0
  40. data/vendor/ruby-hmac/lib/hmac-md5.rb +11 -0
  41. data/vendor/ruby-hmac/lib/hmac-rmd160.rb +11 -0
  42. data/vendor/ruby-hmac/lib/hmac-sha1.rb +11 -0
  43. data/vendor/ruby-hmac/lib/hmac-sha2.rb +25 -0
  44. data/vendor/ruby-hmac/lib/hmac.rb +118 -0
  45. data/vendor/ruby-hmac/lib/ruby_hmac.rb +2 -0
  46. data/vendor/ruby-hmac/ruby-hmac.gemspec +33 -0
  47. data/vendor/ruby-hmac/test/test_hmac.rb +89 -0
  48. data/vendor/signature/.document +5 -0
  49. data/vendor/signature/.gitignore +21 -0
  50. data/vendor/signature/Gemfile +3 -0
  51. data/vendor/signature/Gemfile.lock +29 -0
  52. data/vendor/signature/LICENSE +20 -0
  53. data/vendor/signature/README.md +55 -0
  54. data/vendor/signature/Rakefile +2 -0
  55. data/vendor/signature/VERSION +1 -0
  56. data/vendor/signature/lib/signature.rb +142 -0
  57. data/vendor/signature/lib/signature/version.rb +3 -0
  58. data/vendor/signature/signature.gemspec +22 -0
  59. data/vendor/signature/spec/signature_spec.rb +176 -0
  60. data/vendor/signature/spec/spec_helper.rb +10 -0
  61. data/vendor/util/lib/core_extensions.rb +60 -0
  62. metadata +120 -84
  63. data/AUTHORS +0 -4
  64. data/COPYING +0 -340
  65. data/INSTALL +0 -18
  66. data/TODO +0 -6
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 New Bamboo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Pusher gem
2
+ ==========
3
+
4
+ Getting started
5
+ ---------------
6
+
7
+ After registering at <http://pusher.com> configure your app with the security credentials
8
+
9
+ Pusher.app_id = 'your-pusher-app-id'
10
+ Pusher.key = 'your-pusher-key'
11
+ Pusher.secret = 'your-pusher-secret'
12
+
13
+ Trigger an event with {Pusher::Channel#trigger!}
14
+
15
+ Pusher['a_channel'].trigger!('an_event', {:some => 'data'})
16
+
17
+ Handle errors by rescuing `Pusher::Error` (all Pusher errors are descendants of this error)
18
+
19
+ begin
20
+ Pusher['a_channel'].trigger!('an_event', {:some => 'data'})
21
+ rescue Pusher::Error => e
22
+ # (Pusher::AuthenticationError, Pusher::HTTPError, or Pusher::Error)
23
+ end
24
+
25
+ Optionally a socket id may be provided. This will exclude the event from being triggered on this socket id (see <http://pusher.com/docs/publisher_api_guide/publisher_excluding_recipients> for more info).
26
+
27
+ Pusher['a_channel'].trigger!('an_event', {:some => 'data'}, socket_id)
28
+
29
+ If you don't need to handle failure cases, then you can simply use the {Pusher::Channel#trigger} method, which will rescue and log any errors for you
30
+
31
+ Pusher['a_channel'].trigger('an_event', {:some => 'data'})
32
+
33
+ Logging
34
+ -------
35
+
36
+ Errors are logged to `Pusher.logger`. It will by default use `Logger` from stdlib, however you can assign any logger:
37
+
38
+ Pusher.logger = Rails.logger
39
+
40
+ Asynchronous triggering
41
+ -----------------------
42
+
43
+ To avoid blocking in a typical web application, you may wish to use the {Pusher::Channel#trigger_async} method which makes asynchronous API requests. `trigger_async` returns a deferrable which you can optionally bind to with success and failure callbacks.
44
+
45
+ You need to be running eventmachine to make use of this functionality. This is already the case if, for example, you're deploying to Heroku or using the Thin web server. You will also need to add `em-http-request` to your Gemfile.
46
+
47
+ $ gem install em-http-request
48
+
49
+ deferrable = Pusher['a_channel'].trigger_async('an_event', {
50
+ :some => 'data'
51
+ }, socket_id)
52
+ deferrable.callback {
53
+ # Do something on success
54
+ }
55
+ deferrable.errback { |error|
56
+ # error is a instance of Pusher::Error
57
+ }
58
+
59
+ Private channels
60
+ ----------------
61
+
62
+ The Pusher Gem also deals with signing requests for authenticated private channels. A quick Rails controller example:
63
+
64
+ reponse = Pusher['private-my_channel'].authenticate(params[:socket_id])
65
+ render :json => response
66
+
67
+ Read more about private channels in [the docs](http://pusher.com/docs/client_api_guide/client_channels#subscribe-private-channels) and under {Pusher::Channel#authenticate}.
68
+
69
+ Developing
70
+ ----------
71
+
72
+ Use bundler in order to run specs with the correct dependencies.
73
+
74
+ bundle
75
+ bundle exec rspec spec/*_spec.rb
76
+
77
+ Copyright
78
+ ---------
79
+
80
+ Copyright (c) 2010 New Bamboo. See LICENSE for details.
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |s|
7
+ s.pattern = 'spec/**/*.rb'
8
+ end
9
+
10
+ task :default => :spec
11
+ task :test => :spec
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'pusher'
3
+ require 'eventmachine'
4
+ require 'em-http-request'
5
+
6
+ # To get these values:
7
+ # - Go to https://app.pusherapp.com/
8
+ # - Click on Choose App.
9
+ # - Click on one of your apps
10
+ # - Click API Access
11
+ Pusher.app_id = 'your_app_id'
12
+ Pusher.key = 'your_key'
13
+ Pusher.secret = 'your_secret'
14
+
15
+
16
+ EM.run {
17
+ deferrable = Pusher['test_channel'].trigger_async('my_event', 'hi')
18
+
19
+ deferrable.callback { # called on success
20
+ puts "Message sent successfully."
21
+ EM.stop
22
+ }
23
+ deferrable.errback { |error| # called on error
24
+ puts "Message could not be sent."
25
+ puts error
26
+ EM.stop
27
+ }
28
+ }
@@ -0,0 +1,107 @@
1
+ autoload 'Logger', 'logger'
2
+ require 'uri'
3
+
4
+ # Used for configuring API credentials and creating Channel objects
5
+ #
6
+ module Pusher
7
+ # All Pusher errors descend from this class so you can easily rescue Pusher
8
+ # errors
9
+ #
10
+ # @example
11
+ # begin
12
+ # Pusher['a_channel'].trigger!('an_event', {:some => 'data'})
13
+ # rescue Pusher::Error => e
14
+ # # Do something on error
15
+ # end
16
+ class Error < RuntimeError; end
17
+ class AuthenticationError < Error; end
18
+ class ConfigurationError < Error; end
19
+ class HTTPError < Error; attr_accessor :original_error; end
20
+
21
+ class << self
22
+ attr_accessor :scheme, :host, :port
23
+ attr_writer :logger
24
+ attr_accessor :app_id, :key, :secret
25
+
26
+ # @private
27
+ def logger
28
+ @logger ||= begin
29
+ log = Logger.new($stdout)
30
+ log.level = Logger::INFO
31
+ log
32
+ end
33
+ end
34
+
35
+ # @private
36
+ def authentication_token
37
+ Signature::Token.new(@key, @secret)
38
+ end
39
+
40
+ # @private Builds a connection url for Pusherapp
41
+ def url
42
+ URI::Generic.build({
43
+ :scheme => self.scheme,
44
+ :host => self.host,
45
+ :port => self.port,
46
+ :path => "/apps/#{self.app_id}"
47
+ })
48
+ end
49
+
50
+ # Configure Pusher connection by providing a url rather than specifying
51
+ # scheme, key, secret, and app_id separately.
52
+ #
53
+ # @example
54
+ # Pusher.url = http://KEY:SECRET@api.pusherapp.com/apps/APP_ID
55
+ #
56
+ def url=(url)
57
+ uri = URI.parse(url)
58
+ self.app_id = uri.path.split('/').last
59
+ self.key = uri.user
60
+ self.secret = uri.password
61
+ self.host = uri.host
62
+ self.port = uri.port
63
+ end
64
+
65
+ # Configure whether Pusher API calls should be made over SSL
66
+ # (default false)
67
+ #
68
+ # @example
69
+ # Pusher.encrypted = true
70
+ #
71
+ def encrypted=(boolean)
72
+ Pusher.scheme = boolean ? 'https' : 'http'
73
+ # Configure port if it hasn't already been configured
74
+ Pusher.port ||= boolean ? 443 : 80
75
+ end
76
+
77
+ private
78
+
79
+ def configured?
80
+ host && scheme && key && secret && app_id
81
+ end
82
+ end
83
+
84
+ # Defaults
85
+ self.scheme = 'http'
86
+ self.host = 'api.pusherapp.com'
87
+
88
+ if ENV['PUSHER_URL']
89
+ self.url = ENV['PUSHER_URL']
90
+ end
91
+
92
+ # Return a channel by name
93
+ #
94
+ # @example
95
+ # Pusher['my-channel']
96
+ # @return [Channel]
97
+ # @raise [ConfigurationError] unless key, secret and app_id have been
98
+ # configured
99
+ def self.[](channel_name)
100
+ raise ConfigurationError, 'Missing configuration: please check that Pusher.key, Pusher.secret and Pusher.app_id are configured.' unless configured?
101
+ @channels ||= {}
102
+ @channels[channel_name.to_s] ||= Channel.new(url, channel_name)
103
+ end
104
+ end
105
+
106
+ require 'pusher/channel'
107
+ require 'pusher/request'
@@ -0,0 +1,154 @@
1
+ require 'core_extensions'
2
+ require 'hmac-sha2'
3
+ require 'json'
4
+
5
+ module Pusher
6
+ # Trigger events on Channels
7
+ class Channel
8
+ attr_reader :name
9
+
10
+ def initialize(base_url, name)
11
+ @uri = base_url.dup
12
+ @uri.path = @uri.path + "/channels/#{name}/"
13
+ @name = name
14
+ end
15
+
16
+ # Trigger event asynchronously using EventMachine::HttpRequest
17
+ #
18
+ # @param (see #trigger!)
19
+ # @return [EM::DefaultDeferrable]
20
+ # Attach a callback to be notified of success (with no parameters).
21
+ # Attach an errback to be notified of failure (with an error parameter
22
+ # which includes the HTTP status code returned)
23
+ # @raise [LoadError] unless em-http-request gem is available
24
+ # @raise [Pusher::Error] unless the eventmachine reactor is running. You
25
+ # probably want to run your application inside a server such as thin
26
+ #
27
+ def trigger_async(event_name, data, socket_id = nil, &block)
28
+ request = construct_event_request(event_name, data, socket_id)
29
+ request.send_async
30
+ end
31
+
32
+ # Trigger event
33
+ #
34
+ # @example
35
+ # begin
36
+ # Pusher['my-channel'].trigger!('an_event', {:some => 'data'})
37
+ # rescue Pusher::Error => e
38
+ # # Do something on error
39
+ # end
40
+ #
41
+ # @param data [Object] Event data to be triggered in javascript.
42
+ # Objects other than strings will be converted to JSON
43
+ # @param socket_id Allows excluding a given socket_id from receiving the
44
+ # event - see http://pusher.com/docs/publisher_api_guide/publisher_excluding_recipients for more info
45
+ #
46
+ # @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
47
+ # @raise [Pusher::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
48
+ #
49
+ def trigger!(event_name, data, socket_id = nil)
50
+ request = construct_event_request(event_name, data, socket_id)
51
+ request.send_sync
52
+ end
53
+
54
+ # Trigger event, catching and logging any errors.
55
+ #
56
+ # @note CAUTION! No exceptions will be raised on failure
57
+ # @param (see #trigger!)
58
+ #
59
+ def trigger(event_name, data, socket_id = nil)
60
+ trigger!(event_name, data, socket_id)
61
+ rescue Pusher::Error => e
62
+ Pusher.logger.error("#{e.message} (#{e.class})")
63
+ Pusher.logger.debug(e.backtrace.join("\n"))
64
+ end
65
+
66
+ # Request channel stats
67
+ #
68
+ # @return [Hash] See Pusher api docs for reported stats
69
+ # @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
70
+ # @raise [Pusher::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
71
+ #
72
+ def stats
73
+ request = Pusher::Request.new(:get, @uri + 'stats', {})
74
+ return request.send_sync
75
+ end
76
+
77
+ # Compute authentication string required to subscribe to this channel.
78
+ #
79
+ # See http://pusher.com/docs/auth_signatures for more details.
80
+ #
81
+ # @param socket_id [String] Each Pusher socket connection receives a
82
+ # unique socket_id. This is sent from pusher.js to your server when
83
+ # channel authentication is required.
84
+ # @param custom_string [String] Allows signing additional data
85
+ # @return [String]
86
+ #
87
+ def authentication_string(socket_id, custom_string = nil)
88
+ raise "Invalid socket_id" if socket_id.nil? || socket_id.empty?
89
+ raise 'Custom argument must be a string' unless custom_string.nil? || custom_string.kind_of?(String)
90
+
91
+ string_to_sign = [socket_id, name, custom_string].compact.map{|e|e.to_s}.join(':')
92
+ Pusher.logger.debug "Signing #{string_to_sign}"
93
+ token = Pusher.authentication_token
94
+ signature = HMAC::SHA256.hexdigest(token.secret, string_to_sign)
95
+
96
+ return "#{token.key}:#{signature}"
97
+ end
98
+
99
+ # Deprecated - for backward compatibility
100
+ alias :socket_auth :authentication_string
101
+
102
+ # Generate an authentication endpoint response
103
+ #
104
+ # @example Private channels
105
+ # render :json => Pusher['private-my_channel'].authenticate(params[:socket_id])
106
+ #
107
+ # @example Presence channels
108
+ # render :json => Pusher['private-my_channel'].authenticate(params[:socket_id], {
109
+ # :user_id => current_user.id, # => required
110
+ # :user_info => { # => optional - for example
111
+ # :name => current_user.name,
112
+ # :email => current_user.email
113
+ # }
114
+ # })
115
+ #
116
+ # @param socket_id [String]
117
+ # @param custom_data [Hash] used for example by private channels
118
+ #
119
+ # @return [Hash]
120
+ #
121
+ # @private Custom data is sent to server as JSON-encoded string
122
+ #
123
+ def authenticate(socket_id, custom_data = nil)
124
+ custom_data = custom_data.to_json if custom_data
125
+ auth = socket_auth(socket_id, custom_data)
126
+ r = {:auth => auth}
127
+ r[:channel_data] = custom_data if custom_data
128
+ r
129
+ end
130
+
131
+ private
132
+
133
+ def construct_event_request(event_name, data, socket_id)
134
+ params = {
135
+ :name => event_name,
136
+ }
137
+ params[:socket_id] = socket_id if socket_id
138
+
139
+ body = case data
140
+ when String
141
+ data
142
+ else
143
+ begin
144
+ data.to_json
145
+ rescue Exception => e
146
+ Pusher.logger.error("Could not convert #{data.inspect} into JSON")
147
+ raise e
148
+ end
149
+ end
150
+
151
+ request = Pusher::Request.new(:post, @uri + 'events', params, body)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,107 @@
1
+ require 'signature'
2
+ require 'digest/md5'
3
+ require 'json'
4
+
5
+ module Pusher
6
+ class Request
7
+ def initialize(verb, uri, params, body = nil, token = nil)
8
+ @verb = verb
9
+ @uri = uri
10
+
11
+ if body
12
+ @body = body
13
+ params[:body_md5] = Digest::MD5.hexdigest(body)
14
+ end
15
+
16
+ request = Signature::Request.new(verb.to_s.upcase, uri.path, params)
17
+ auth_hash = request.sign(token || Pusher.authentication_token)
18
+ @params = params.merge(auth_hash)
19
+ end
20
+
21
+ def send_sync
22
+ require 'net/http' unless defined?(Net::HTTP)
23
+ require 'net/https' if (ssl? && !defined?(Net::HTTPS))
24
+
25
+ @http_sync ||= begin
26
+ http = Net::HTTP.new(@uri.host, @uri.port)
27
+ http.use_ssl = true if ssl?
28
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl?
29
+ http
30
+ end
31
+
32
+ begin
33
+ case @verb
34
+ when :post
35
+ response = @http_sync.post("#{@uri.path}?#{@params.to_params}",
36
+ @body, { 'Content-Type'=> 'application/json' })
37
+ when :get
38
+ response = @http_sync.get("#{@uri.path}?#{@params.to_params}",
39
+ { 'Content-Type'=> 'application/json' })
40
+ else
41
+ raise "Unknown verb"
42
+ end
43
+ rescue Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED,
44
+ Errno::ETIMEDOUT, Errno::EHOSTUNREACH, Errno::ECONNRESET,
45
+ Timeout::Error, EOFError,
46
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
47
+ Net::ProtocolError => e
48
+ error = Pusher::HTTPError.new("#{e.message} (#{e.class})")
49
+ error.original_error = e
50
+ raise error
51
+ end
52
+
53
+ return handle_response(response.code.to_i, response.body.chomp)
54
+ end
55
+
56
+ def send_async
57
+ unless defined?(EventMachine) && EventMachine.reactor_running?
58
+ raise Error, "In order to use trigger_async you must be running inside an eventmachine loop"
59
+ end
60
+ require 'em-http' unless defined?(EventMachine::HttpRequest)
61
+
62
+ deferrable = EM::DefaultDeferrable.new
63
+
64
+ http = EventMachine::HttpRequest.new(@uri).post({
65
+ :query => @params, :timeout => 5, :body => @body,
66
+ :head => {'Content-Type'=> 'application/json'}
67
+ })
68
+ http.callback {
69
+ begin
70
+ handle_response(http.response_header.status, http.response.chomp)
71
+ deferrable.succeed
72
+ rescue => e
73
+ deferrable.fail(e)
74
+ end
75
+ }
76
+ http.errback {
77
+ Pusher.logger.debug("Network error connecting to pusher: #{http.inspect}")
78
+ deferrable.fail(Error.new("Network error connecting to pusher"))
79
+ }
80
+
81
+ deferrable
82
+ end
83
+
84
+ private
85
+
86
+ def handle_response(status_code, body)
87
+ case status_code
88
+ when 200
89
+ return JSON.decode(body, :symbolize_keys => true)
90
+ when 202
91
+ return true
92
+ when 400
93
+ raise Error, "Bad request: #{body}"
94
+ when 401
95
+ raise AuthenticationError, body
96
+ when 404
97
+ raise Error, "Resource not found: app_id is probably invalid"
98
+ else
99
+ raise Error, "Unknown error (status code #{status_code}): #{body}"
100
+ end
101
+ end
102
+
103
+ def ssl?
104
+ @uri.scheme == 'https'
105
+ end
106
+ end
107
+ end