papermill-agent 0.0.2 → 0.1.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/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'rest-client', '~>1.6.1'
3
+ gemspec
4
4
 
5
5
  group :test do
6
6
  gem 'fakeweb', '>=1.3.0'
data/README.md CHANGED
@@ -3,16 +3,40 @@ WARNING: This is pre-alpha software. Use at your own risk until 1.0.
3
3
 
4
4
  The papermill agent parses responses from your web application.
5
5
 
6
- ## Usage Instructions
6
+ # Installation #
7
7
 
8
- Papermill works via a middleware called the Collector.
8
+ ## Step 1: Create config/papermill.yml ##
9
9
 
10
- #### In Rails
10
+ Papermill's configuration information is stored in a yml file located at
11
+ `[PROJECT_ROOT]/config/papermill.yml`. The only required configuration
12
+ option is your api token:
13
+
14
+ # config/papermill.yml
15
+ token: you-api-token
16
+
17
+ ## Step 2: include the middleware layer for request capturing and logging ##
18
+
19
+ Papermill works by capturing every request submitted to your application and
20
+ periodically sending them off to the PapermillApp servers. To enable this,
21
+ you must add the Papermill middleware class to your application's middleware
22
+ stack.
23
+
24
+ In rails, this is done in either `config/environment.rb` or `config/application.rb`,
25
+ depending on whether you're using rails 2 or rails 3. In a sinatra or rack app,
26
+ you simply add the middleware via rack's `use` method.
27
+
28
+ See the examples below.
29
+
30
+ ### rails 2.x ###
11
31
  # in config/environment.rb
12
32
  config.middleware.use 'Papermill::Collector'
13
33
 
14
- #### In Sinatra/rack
15
- # in config.ru
34
+ ### rails 3 ###
35
+ # in config/application.rb
36
+ config.middleware.use 'Papermill::Collector'
37
+
38
+ ### sinatra or a rack application ###
39
+ # in config.ru or in a Rack::Builder stack
16
40
  use Papermill::Collector
17
41
 
18
42
 
@@ -3,7 +3,7 @@ $:.unshift File.dirname(File.expand_path(__FILE__))
3
3
 
4
4
  module Papermill
5
5
  # papermill endpoint which will receive client requests
6
- API_ENDPOINT = 'http://api.papermillapp.com'
6
+ API_ENDPOINT = 'http://papermillapp.com/api'
7
7
 
8
8
  autoload :Agent, 'papermill-agent/agent'
9
9
  autoload :Collector, 'papermill-agent/rack/collector'
@@ -12,6 +12,7 @@ module Papermill
12
12
  UPDATE_INTERVAL = 10
13
13
 
14
14
  attr_reader :last_sent, :mutex, :config, :logger
15
+ attr_accessor :disabled
15
16
 
16
17
  # request timeout threshold for sending data to papermill
17
18
  @@request_timeout = 5
@@ -19,22 +20,28 @@ module Papermill
19
20
  @@request_timeout
20
21
  end
21
22
 
23
+ def disabled?
24
+ !!disabled
25
+ end
26
+
22
27
  def start
23
28
  @last_sent = Time.now
24
29
  @mutex = Mutex.new
25
- @config = Configurator.new
26
30
  @logger = Logger.new
31
+ @config = Configurator.new
27
32
  logger.info "\n\n=============== Starting papermill-agent at #{Time.now} in process #{Process.pid}"
28
33
  Thread.abort_on_exception = true
29
34
 
30
- @worker_thread = Thread.new do
31
- loop do
32
- if time_since_last_sent > UPDATE_INTERVAL
33
- send_data_to_papermill
34
- @last_sent = Time.now
35
+ unless disabled?
36
+ @worker_thread = Thread.new do
37
+ loop do
38
+ if time_since_last_sent > UPDATE_INTERVAL
39
+ send_data_to_papermill
40
+ @last_sent = Time.now
41
+ end
42
+ sleep_time = seconds_until_next_run
43
+ sleep sleep_time if sleep_time > 0
35
44
  end
36
- sleep_time = seconds_until_next_run
37
- sleep sleep_time if sleep_time > 0
38
45
  end
39
46
  end
40
47
  end
@@ -7,19 +7,33 @@ module Papermill
7
7
 
8
8
  def initialize(config_source = 'config/papermill.yml')
9
9
  load_config(config_source)
10
+ after_init
10
11
  end
11
12
 
13
+ # Loads the papermill configuration.
14
+ #
15
+ # By default, the papermill configuration is read from a yaml file located
16
+ # at ./config/papermill.yml. Still, the configuration source can either be
17
+ # a yaml file of any name or a ruby hash.
18
+ #
19
+ # If the configuration file cannot be found, the agent will be disabled.
12
20
  def load_config(config_source)
13
21
  @config = case
14
22
  when config_source.class == String
15
- YAML.load_file(config_source)
23
+ begin
24
+ YAML.load_file(config_source)
25
+ rescue Errno::ENOENT => e
26
+ disable_agent
27
+ Agent.instance.logger.info "The papermill configuration file does not exist. The papermill agent is disabled."
28
+ return nil
29
+ end
16
30
  when config_source.class == Hash
17
31
  config_source
18
32
  end
19
33
  end
20
34
 
21
35
  def token
22
- config['token']
36
+ (config && config.has_key?('token')) ? config['token'] : nil
23
37
  end
24
38
 
25
39
  def endpoint
@@ -50,6 +64,16 @@ module Papermill
50
64
  end
51
65
 
52
66
  private
67
+ # If there is no token, disable the agent.
68
+ def after_init
69
+ if token.nil?
70
+ disable_agent
71
+ end
72
+ end
73
+
74
+ def disable_agent
75
+ Agent.instance.disabled = true
76
+ end
53
77
 
54
78
  # holds the internal config hash
55
79
  def config
@@ -3,7 +3,7 @@ require 'logger'
3
3
  module Papermill
4
4
  class Logger < ::Logger
5
5
  def initialize(io_object = 'log/papermill.log')
6
- FileUtils.mkdir('log') unless Dir.exists?('log')
6
+ FileUtils.mkdir('log') unless File.exists?('log')
7
7
  super(io_object)
8
8
  end
9
9
 
@@ -4,6 +4,7 @@ module Papermill
4
4
  # The Collector class is the middleware component of papermill.
5
5
  # To use it with a rails app, you must add the following to
6
6
  # your environment.rb file:
7
+ #
7
8
  # config.middleware.use Papermill::Collector
8
9
  class Collector
9
10
  def initialize(app)
@@ -0,0 +1,14 @@
1
+ require 'papermill-agent'
2
+ require 'rails'
3
+
4
+ module Papermill
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ require 'papermill-agent/tasks'
8
+ end
9
+
10
+ # initializer "..." do |app|
11
+ # app.config.middleware.use "Papermill::Collector"
12
+ # end
13
+ end
14
+ end
@@ -30,11 +30,15 @@ module Papermill
30
30
  end
31
31
 
32
32
  def extract_env_info
33
- result_hash = env.select { |key, value| ENV_CAPTURE_FIELDS.include?(key) }
33
+ result_hash = {}
34
+
35
+ # Hash#select returns an array in 1.8, so we have to do some manual
36
+ # iteration to extract the key/value pairs from the environment hash.
37
+ ENV_CAPTURE_FIELDS.each { |key| result_hash[key] = env[key] }
34
38
 
35
39
  # rename rack.* keys b/c mongo does not allow key names containing a .
36
- rack_keys = ENV_CAPTURE_FIELDS.select { |key| key =~ /^rack\./ }
37
- rack_keys.each do |key|
40
+ naughty_rack_keys = ENV_CAPTURE_FIELDS.select { |key| key =~ /^rack\./ }
41
+ naughty_rack_keys.each do |key|
38
42
  new_key = key.gsub(/^rack\./, '').upcase
39
43
  result_hash[new_key] = result_hash.delete(key)
40
44
  end
@@ -0,0 +1,21 @@
1
+ require 'papermill-agent'
2
+
3
+ namespace :papermill do
4
+ desc "Generate a papermill configuration file"
5
+ task :create_config do
6
+ require 'yaml'
7
+
8
+ # don't overwrite existing configuration files
9
+ if File.exists?('./config/papermil.yml')
10
+ fail("A papermill configuration file already exists. Please remove it and try again.")
11
+ end
12
+
13
+ token = ENV['TOKEN'] || 'replace-with-your-token'
14
+
15
+ File.open('./config/papermill.yml', 'w') do |file|
16
+ config = {'token' => token}
17
+ YAML.dump(config, file)
18
+ end
19
+ STDOUT.puts "Your papermill configuration file has been created at config/papermill.yml."
20
+ end
21
+ end
data/spec/agent_spec.rb CHANGED
@@ -147,7 +147,7 @@ module Papermill
147
147
 
148
148
  describe 'the api endpoint' do
149
149
  subject { Papermill::API_ENDPOINT }
150
- it { should == 'http://api.papermillapp.com' }
150
+ it { should == 'http://papermillapp.com/api' }
151
151
  end
152
152
 
153
153
  describe 'request timeout interval' do
@@ -188,7 +188,7 @@ module Papermill
188
188
 
189
189
  describe 'serializing the payload' do
190
190
  it 'jsonifies the payload data' do
191
- agent.jsonify_payload.should == '[{"headers":{"Content-Type":"text/html"},"status":200}]'
191
+ agent.jsonify_payload.should == Storage.store.flatten.to_json
192
192
  end
193
193
  end
194
194
 
@@ -201,7 +201,7 @@ module Papermill
201
201
  end
202
202
 
203
203
  it 'sends a request to the papermill api endpoint' do
204
- RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload => '[{"headers":{"Content-Type":"text/html"},"status":200}]')
204
+ RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload => Storage.store.flatten.to_json)
205
205
  agent.send_data_to_papermill
206
206
  end
207
207
 
@@ -234,5 +234,30 @@ module Papermill
234
234
  end
235
235
  end
236
236
 
237
+ describe 'disabling the papermill agent' do
238
+ context 'when disabled is set to true' do
239
+ before { agent.disabled = true }
240
+ after { agent.disabled = false }
241
+
242
+ it 'is disabled' do
243
+ agent.should be_disabled
244
+ end
245
+ end
246
+
247
+ context 'wen disabled is set to false' do
248
+ before { agent.disabled = false }
249
+
250
+ it 'is disabled' do
251
+ agent.should_not be_disabled
252
+ end
253
+ end
254
+
255
+ context 'by default' do
256
+ it 'is not disabled' do
257
+ agent.should_not be_disabled
258
+ end
259
+ end
260
+ end
261
+
237
262
  end
238
263
 
@@ -8,6 +8,35 @@ module Papermill
8
8
  YAML.should_receive(:load_file).with('filename')
9
9
  Configurator.new('filename')
10
10
  end
11
+
12
+ context 'the config file does not exist' do
13
+ after { agent.disabled = false }
14
+
15
+ it 'disables the agent' do
16
+ lambda {
17
+ Configurator.new('does not exist')
18
+ }.should change(agent, :disabled?).to(true)
19
+ end
20
+
21
+ it 'does not raise a file load error' do
22
+ lambda {
23
+ Configurator.new('does not exist')
24
+ }.should_not raise_error(Errno::ENOENT)
25
+ end
26
+
27
+ it 'sets config to nil' do
28
+ config = Configurator.new('does not exist')
29
+ config.send(:config).should be_nil
30
+ end
31
+ end
32
+
33
+ context 'when no token key is found' do
34
+ it 'disables the agent' do
35
+ lambda {
36
+ Configurator.new({})
37
+ }.should change(agent, :disabled?).to(true)
38
+ end
39
+ end
11
40
  end
12
41
 
13
42
  context 'when a hash is passed in' do
@@ -22,14 +51,19 @@ module Papermill
22
51
  end
23
52
 
24
53
  describe 'given a configurator instance' do
25
- let(:configurator) do
26
- YAML.stub!(:load_file).and_return({'token' => 'token'})
27
- Configurator.new
28
- end
29
-
30
54
  describe 'the token' do
31
- it 'should return the token from the config' do
32
- configurator.token.should == 'token'
55
+ subject { config.token }
56
+
57
+ context 'when the token exists' do
58
+ let(:config) { Configurator.new({'token' => 'token'}) }
59
+
60
+ it { should == 'token' }
61
+ end
62
+
63
+ context 'when the token does not exist' do
64
+ let(:config) { Configurator.new({}) }
65
+
66
+ it { should be_nil }
33
67
  end
34
68
  end
35
69
 
@@ -159,8 +193,9 @@ module Papermill
159
193
  end
160
194
 
161
195
  describe 'retrieving a setting for the current environment' do
196
+ let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
197
+
162
198
  context 'for a given environment' do
163
- let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
164
199
  before { config.environment = 'production' }
165
200
 
166
201
  context 'for a setting that has a value' do
@@ -172,9 +207,31 @@ module Papermill
172
207
  context 'for a setting that does not have a value' do
173
208
  it 'returns nil' do
174
209
  config.setting('does not exist').should == nil
210
+ config.setting('does not exist').should === nil
211
+ config.setting('does not exist').should be_instance_of NilClass
212
+ end
213
+
214
+ it 'does not return false' do
215
+ config.setting('does not exist').should_not === false
216
+ config.setting('does not exist').should_not == false
217
+ config.setting('does not exist').should_not === FalseClass
218
+ config.setting('does not exist').should_not be_instance_of FalseClass
175
219
  end
176
220
  end
177
221
  end
178
222
 
223
+ context 'when trying to access a property with no environment' do
224
+ before { config.environment = 'not defined' }
225
+ subject { config.setting('does not exist') }
226
+
227
+ it 'should not return false' do
228
+ subject.should_not == false
229
+ end
230
+
231
+ it 'should return nil' do
232
+ subject.should == nil
233
+ end
234
+ end
235
+
179
236
  end
180
237
  end
@@ -14,14 +14,12 @@ module Papermill
14
14
  end
15
15
 
16
16
  context 'in a rails app' do
17
- before do
18
- # self.class.module_eval { remove_const(:Sinatra) if defined?(Sinatra) }
17
+ class Rails
19
18
  end
20
19
 
21
20
  it 'uses the rails adapter' do
22
- class Rails
23
- end
24
- ResponseParser.parse_klass.should == ResponseAdapters::Rails
21
+ ResponseAdapters::Rails.should_receive(:new).and_return(mock('object', :parse => nil))
22
+ ResponseParser.parse(200, {}, [], {})
25
23
  end
26
24
  end
27
25
  end
data/spec/spec_helper.rb CHANGED
@@ -28,4 +28,8 @@ RSpec.configure do |config|
28
28
  # clear the storage before each example
29
29
  Papermill::Storage.clear
30
30
  end
31
+
32
+ config.after(:each) do
33
+ agent.disabled = false
34
+ end
31
35
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 2
9
- version: 0.0.2
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alex Sharp
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-02 00:00:00 -06:00
17
+ date: 2011-01-07 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -32,6 +32,19 @@ dependencies:
32
32
  version: 1.6.1
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json_pure
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
35
48
  description: The client agent for papermillapp.com
36
49
  email:
37
50
  - ajsharp@gmail.com
@@ -47,11 +60,13 @@ files:
47
60
  - lib/papermill-agent/configurator.rb
48
61
  - lib/papermill-agent/logger.rb
49
62
  - lib/papermill-agent/rack/collector.rb
63
+ - lib/papermill-agent/railtie.rb
50
64
  - lib/papermill-agent/response_adapters/base.rb
51
65
  - lib/papermill-agent/response_adapters/rails.rb
52
66
  - lib/papermill-agent/response_adapters/sinatra.rb
53
67
  - lib/papermill-agent/response_parser.rb
54
68
  - lib/papermill-agent/storage.rb
69
+ - lib/papermill-agent/tasks.rb
55
70
  - lib/papermill-agent.rb
56
71
  - LICENSE
57
72
  - README.md