spanx 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.
Files changed (59) hide show
  1. data/.gitignore +20 -0
  2. data/.pairs +13 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE +22 -0
  8. data/README.md +175 -0
  9. data/Rakefile +2 -0
  10. data/bin/spanx +7 -0
  11. data/conf/spanx-config.yml.example +44 -0
  12. data/conf/spanx-whitelist.txt.example +2 -0
  13. data/lib/spanx.rb +38 -0
  14. data/lib/spanx/actor/analyzer.rb +94 -0
  15. data/lib/spanx/actor/collector.rb +64 -0
  16. data/lib/spanx/actor/log_reader.rb +46 -0
  17. data/lib/spanx/actor/writer.rb +68 -0
  18. data/lib/spanx/cli.rb +47 -0
  19. data/lib/spanx/cli/analyze.rb +50 -0
  20. data/lib/spanx/cli/disable.rb +36 -0
  21. data/lib/spanx/cli/enable.rb +36 -0
  22. data/lib/spanx/cli/flush.rb +36 -0
  23. data/lib/spanx/cli/watch.rb +91 -0
  24. data/lib/spanx/config.rb +45 -0
  25. data/lib/spanx/helper.rb +8 -0
  26. data/lib/spanx/helper/exit.rb +11 -0
  27. data/lib/spanx/helper/subclassing.rb +31 -0
  28. data/lib/spanx/helper/timing.rb +9 -0
  29. data/lib/spanx/ip_checker.rb +5 -0
  30. data/lib/spanx/logger.rb +47 -0
  31. data/lib/spanx/notifier/audit_log.rb +18 -0
  32. data/lib/spanx/notifier/base.rb +22 -0
  33. data/lib/spanx/notifier/campfire.rb +47 -0
  34. data/lib/spanx/notifier/email.rb +61 -0
  35. data/lib/spanx/runner.rb +74 -0
  36. data/lib/spanx/usage.rb +9 -0
  37. data/lib/spanx/version.rb +3 -0
  38. data/lib/spanx/whitelist.rb +31 -0
  39. data/spanx.gemspec +32 -0
  40. data/spec/fixtures/access.log.1 +104 -0
  41. data/spec/fixtures/access.log.bots +7 -0
  42. data/spec/fixtures/config.yml +10 -0
  43. data/spec/fixtures/config_with_checks.yml +18 -0
  44. data/spec/fixtures/whitelist.txt +4 -0
  45. data/spec/spanx/actor/analyzer_spec.rb +114 -0
  46. data/spec/spanx/actor/collector_spec.rb +4 -0
  47. data/spec/spanx/actor/log_reader_spec.rb +68 -0
  48. data/spec/spanx/actor/writer_spec.rb +63 -0
  49. data/spec/spanx/config_spec.rb +62 -0
  50. data/spec/spanx/helper/timing_spec.rb +22 -0
  51. data/spec/spanx/notifier/base_spec.rb +16 -0
  52. data/spec/spanx/notifier/campfire_spec.rb +5 -0
  53. data/spec/spanx/notifier/email_spec.rb +121 -0
  54. data/spec/spanx/runner_spec.rb +102 -0
  55. data/spec/spanx/whitelist_spec.rb +66 -0
  56. data/spec/spec_helper.rb +25 -0
  57. data/spec/support/fakeredis.rb +1 -0
  58. data/spec/support/mail.rb +10 -0
  59. metadata +302 -0
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Helper::Timing do
4
+ class TestClass
5
+ include Spanx::Helper::Timing
6
+ end
7
+
8
+ let(:tester) { TestClass.new }
9
+
10
+ describe "#period_marker" do
11
+ let(:time) { DateTime.parse('2001-02-02T21:03:26+00:00').to_time }
12
+
13
+ before { time.to_i.should == 981147806 }
14
+
15
+ it "returns unix time floored to the nearest resolution block" do
16
+ Timecop.freeze time do
17
+ tester.period_marker(10).should == 981147800
18
+ tester.period_marker(300).should == 981147600
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Notifier::Base do
4
+
5
+ describe "message content" do
6
+ let(:time) { Time.now }
7
+ let(:ip_check) { Spanx::IPChecker.new("1.2.3.4") }
8
+ let(:period_check) { Pause::PeriodCheck.new(60, 100, 80)}
9
+ let(:blocked_action) { Pause::BlockedAction.new(ip_check, period_check, 500, time.to_i)}
10
+
11
+ it "should set the correct message content" do
12
+ Spanx::Notifier::Base.new.send(:generate_block_ip_message, blocked_action).should ==
13
+ "1.2.3.4 blocked @ #{time} for 1mins, for 500 requests over 1mins, with 100 allowed."
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Notifier::Campfire do
4
+
5
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'mail'
3
+
4
+ describe Spanx::Notifier::Email, "#initialize" do
5
+ describe "#initialize" do
6
+ let(:config) { {
7
+ email: {
8
+ gateway: "a.b.com",
9
+ from: "me@me.com",
10
+ password: "p4ssw0rd",
11
+ domain: "my.domain.com"
12
+ }
13
+ } }
14
+
15
+ context "enabled" do
16
+ before { Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(true) }
17
+
18
+ it "should configure SMTP gateway" do
19
+ Spanx::Notifier::Email.new(config)
20
+ delivery_method = Mail::Configuration.instance.delivery_method
21
+ delivery_method.should be_an_instance_of(Mail::SMTP)
22
+ delivery_method.settings[:address].should == "a.b.com"
23
+ delivery_method.settings[:user_name].should == "me@me.com"
24
+ delivery_method.settings[:password].should == "p4ssw0rd"
25
+ delivery_method.settings[:domain].should == "my.domain.com"
26
+ end
27
+ end
28
+
29
+ context "when disabled" do
30
+ before {
31
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(false)
32
+ }
33
+
34
+ it "should not configure email gateway" do
35
+ Mail.should_not_receive(:defaults)
36
+ Spanx::Notifier::Email.new(config)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ describe Spanx::Notifier::Email, "#publish" do
43
+ include Mail::Matchers
44
+
45
+ subject { Spanx::Notifier::Email.new(config) }
46
+
47
+ let(:time_blocked) { Time.now }
48
+ let(:period) { mock() }
49
+ let(:action) { Spanx::IPChecker.new("1.2.3.4") }
50
+ let(:blocked_ip) { Pause::BlockedAction.new(action, period, 50, time_blocked) }
51
+
52
+ before { Spanx::Notifier::Email.any_instance.stub(:configure_email_gateway) }
53
+
54
+ context "when disabled" do
55
+ let(:config) { {} }
56
+
57
+ before {
58
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(false)
59
+ subject.publish(blocked_ip)
60
+ }
61
+
62
+ it { should_not have_sent_email }
63
+ end
64
+
65
+ context "when enabled" do
66
+ let(:email_content) { "blocked email message" }
67
+ let(:email_subject) { nil }
68
+ let(:config) { {
69
+ email: {
70
+ to: "you@you.com",
71
+ from: "me@me.com",
72
+ subject: email_subject
73
+ }
74
+ } }
75
+
76
+ before {
77
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(true)
78
+ Spanx::Notifier::Email.any_instance.stub(:generate_block_ip_message).and_return(email_content)
79
+ subject.publish(blocked_ip)
80
+ subject.thread.join
81
+ }
82
+
83
+ context "with email subject" do
84
+ let(:email_subject) { "OMG It's an email!" }
85
+
86
+ it {
87
+ should have_sent_email.
88
+ to("you@you.com").
89
+ from("me@me.com").
90
+ with_body(email_content).
91
+ with_subject("OMG It's an email! 1.2.3.4")
92
+ }
93
+ end
94
+
95
+ context "without a subject line" do
96
+ it {
97
+ should have_sent_email.
98
+ to("you@you.com").
99
+ from("me@me.com").
100
+ with_body(email_content).
101
+ with_subject("IP Blocked: 1.2.3.4")
102
+ }
103
+ end
104
+ end
105
+ end
106
+
107
+ describe Spanx::Notifier::Email, "#enabled?" do
108
+ subject { Spanx::Notifier::Email.new(config) }
109
+
110
+ context "with no email configuration" do
111
+ let(:config) { {} }
112
+
113
+ it { should_not be_enabled }
114
+ end
115
+
116
+ context "with enabled email configuration" do
117
+ let(:config) { {email: {enabled: true}} }
118
+
119
+ it { should be_enabled }
120
+ end
121
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Runner do
4
+ let(:config) { {:access_log => "logfile", :whitelist_file => "whitelist", :log_reader => {:tail_interval => 1}} }
5
+ let(:runner) { Spanx::Runner.new(config) }
6
+ let(:faker) { double() }
7
+
8
+ describe "#new" do
9
+ it "should create a new thread queue" do
10
+ runner.queue.should be_a(Queue)
11
+ end
12
+
13
+ context "actor initialization" do
14
+ let(:collector) { mock("collector") }
15
+ let(:writer) { mock("writer") }
16
+ let(:log_reader) { mock("log_reader") }
17
+ let(:analyzer) { mock("analyzer") }
18
+
19
+ before do
20
+ Spanx::Runner.any_instance.stub(:collector).and_return(collector)
21
+ Spanx::Runner.any_instance.stub(:writer).and_return(writer)
22
+ Spanx::Runner.any_instance.stub(:log_reader).and_return(log_reader)
23
+ Spanx::Runner.any_instance.stub(:analyzer).and_return(analyzer)
24
+ end
25
+
26
+ it "should match string args" do
27
+ Spanx::Runner.new("collector", config).actors.should == [collector]
28
+ Spanx::Runner.new("writer", config).actors.should == [writer]
29
+ Spanx::Runner.new("log_reader", config).actors.should == [log_reader]
30
+ Spanx::Runner.new("analyzer", config).actors.should == [analyzer]
31
+ Spanx::Runner.new("collector", "analyzer", config).actors.should == [collector, analyzer]
32
+ end
33
+
34
+ it "raises if an invalid actor is passed" do
35
+ lambda {
36
+ Spanx::Runner.new("methods", config)
37
+ }.should raise_error("Invalid actor")
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#run" do
43
+ let(:actor1) { mock("actor") }
44
+ let(:actor2) { mock("actor") }
45
+
46
+ before do
47
+ actor1.should_receive(:run).and_return(actor1)
48
+ actor2.should_receive(:run).and_return(actor2)
49
+ actor2.should_receive(:join).and_return(true)
50
+ end
51
+
52
+ it "runs all actors and joins the last one" do
53
+ runner.actors = [actor1, actor2]
54
+ runner.run
55
+ end
56
+ end
57
+
58
+ describe "#collector" do
59
+ before { Spanx::Actor::Collector.should_receive(:new).with(config, runner.queue).and_return(faker) }
60
+
61
+ it "should create a collector" do
62
+ runner.collector.should === faker
63
+ end
64
+ end
65
+
66
+ describe "#whitelist" do
67
+ before { Spanx::Whitelist.should_receive(:new).with("whitelist").and_return(faker) }
68
+
69
+ it "should create a collector" do
70
+ runner.whitelist.should === faker
71
+ end
72
+ end
73
+
74
+ describe "#log_reader" do
75
+ let(:whitelist) { double() }
76
+
77
+ before do
78
+ runner.should_receive(:whitelist).and_return(whitelist)
79
+ Spanx::Actor::LogReader.should_receive(:new).with("logfile", runner.queue, 1, whitelist).and_return(faker)
80
+ end
81
+
82
+ it "should create a log reader" do
83
+ runner.log_reader.should === faker
84
+ end
85
+ end
86
+
87
+ describe "#analyzer" do
88
+ before { Spanx::Actor::Analyzer.should_receive(:new).with(config).and_return(faker) }
89
+
90
+ it "should create an analyzer" do
91
+ runner.analyzer.should === faker
92
+ end
93
+ end
94
+
95
+ describe "#writer" do
96
+ before { Spanx::Actor::Writer.should_receive(:new).with(config).and_return(faker) }
97
+
98
+ it "should create an analyzer" do
99
+ runner.writer.should === faker
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Whitelist do
4
+ let(:whitelist) { Spanx::Whitelist.new(file) }
5
+ let(:file) { "spec/fixtures/whitelist.txt" }
6
+
7
+
8
+ describe "#new" do
9
+ context "with filename" do
10
+ it "loads whitelist patterns into memory" do
11
+ whitelist.patterns[0].should eql(/^127\.0\.0\.1/)
12
+ whitelist.patterns[1].should eql(/^10\.1\.\d{1,3}\.\d{1,3}/)
13
+ whitelist.patterns.each{ |p| p.is_a?(Regexp).should be_true }
14
+ end
15
+ end
16
+
17
+ context "without filename" do
18
+ let(:file) { nil }
19
+
20
+ it "keeps an empty whitelist table" do
21
+ whitelist.patterns.should == []
22
+ end
23
+ end
24
+
25
+ context "with non-existent file" do
26
+ let(:file) { "non-existent-whitelist" }
27
+
28
+ it "writes an error to stderr and exits" do
29
+ $stderr.should_receive(:puts).with("Error: Unable to find whitelist file at #{file}")
30
+ $stderr.should_receive(:puts).with(Spanx::USAGE)
31
+
32
+ lambda {
33
+ whitelist.patterns
34
+ }.should raise_error(SystemExit)
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ describe "#match?" do
41
+ context "IP address matching" do
42
+ it 'is true if IP address is in match list' do
43
+ whitelist.match?("127.0.0.1").should be_true
44
+ end
45
+
46
+ it 'is false if IP address does not match patterns' do
47
+ whitelist.match?("sadfasdf").should be_false
48
+ end
49
+ end
50
+
51
+ context "User agent matches pattern" do
52
+ let(:googlebot) { %Q{66.249.73.24 - - [18/Oct/2012:03:25:33 -0700] GET /p/2213071/39535615 HTTP/1.1 "200" 3943 "-" "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "2.87""Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "-"upstream_addr 127.0.0.1:8100upstream_response_time 0.082 request_time 0.082} }
53
+ it "whitelists googlebot" do
54
+ whitelist.match?(googlebot).should be_true
55
+ end
56
+ end
57
+
58
+ context "users/me matches" do
59
+ let(:log) { %Q{66.249.73.24 - - [18/Oct/2012:03:25:33 -0700] GET /users/me HTTP/1.1 "200" 3943 "-" "-" "Mozilla/5.0 } }
60
+ it "excludes users/me" do
61
+ whitelist.match?(log).should be_true
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
+ require 'rubygems'
10
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
11
+ require 'spanx'
12
+
13
+ Dir['spec/support/**/*.rb'].each { |filename| require_relative "../#{filename}" }
14
+
15
+ RSpec.configure do |config|
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+ end
@@ -0,0 +1 @@
1
+ require 'fakeredis/rspec'
@@ -0,0 +1,10 @@
1
+ require 'mail'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:each) do
5
+ Mail::TestMailer.deliveries.clear
6
+ Mail.defaults do
7
+ delivery_method :test
8
+ end
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,302 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spanx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Konstantin Gredeskoul
9
+ - Eric Saxby
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-11-14 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: pause
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 0.0.3
31
+ - !ruby/object:Gem::Dependency
32
+ name: file-tail
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: mixlib-cli
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: daemons
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: tinder
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: mail
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: 2.4.4
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 2.4.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: fakeredis
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: timecop
145
+ requirement: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: guard-rspec
161
+ requirement: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ - !ruby/object:Gem::Dependency
176
+ name: rb-fsevent
177
+ requirement: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ type: :development
184
+ prerelease: false
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ description: Real time IP parsing and rate detection gem for access_log files
192
+ email:
193
+ - kigster@gmail.com
194
+ - sax@livinginthepast.org
195
+ executables:
196
+ - spanx
197
+ extensions: []
198
+ extra_rdoc_files: []
199
+ files:
200
+ - .gitignore
201
+ - .pairs
202
+ - .rspec
203
+ - .rvmrc
204
+ - Gemfile
205
+ - Guardfile
206
+ - LICENSE
207
+ - README.md
208
+ - Rakefile
209
+ - bin/spanx
210
+ - conf/spanx-config.yml.example
211
+ - conf/spanx-whitelist.txt.example
212
+ - lib/spanx.rb
213
+ - lib/spanx/actor/analyzer.rb
214
+ - lib/spanx/actor/collector.rb
215
+ - lib/spanx/actor/log_reader.rb
216
+ - lib/spanx/actor/writer.rb
217
+ - lib/spanx/cli.rb
218
+ - lib/spanx/cli/analyze.rb
219
+ - lib/spanx/cli/disable.rb
220
+ - lib/spanx/cli/enable.rb
221
+ - lib/spanx/cli/flush.rb
222
+ - lib/spanx/cli/watch.rb
223
+ - lib/spanx/config.rb
224
+ - lib/spanx/helper.rb
225
+ - lib/spanx/helper/exit.rb
226
+ - lib/spanx/helper/subclassing.rb
227
+ - lib/spanx/helper/timing.rb
228
+ - lib/spanx/ip_checker.rb
229
+ - lib/spanx/logger.rb
230
+ - lib/spanx/notifier/audit_log.rb
231
+ - lib/spanx/notifier/base.rb
232
+ - lib/spanx/notifier/campfire.rb
233
+ - lib/spanx/notifier/email.rb
234
+ - lib/spanx/runner.rb
235
+ - lib/spanx/usage.rb
236
+ - lib/spanx/version.rb
237
+ - lib/spanx/whitelist.rb
238
+ - spanx.gemspec
239
+ - spec/fixtures/access.log.1
240
+ - spec/fixtures/access.log.bots
241
+ - spec/fixtures/config.yml
242
+ - spec/fixtures/config_with_checks.yml
243
+ - spec/fixtures/whitelist.txt
244
+ - spec/spanx/actor/analyzer_spec.rb
245
+ - spec/spanx/actor/collector_spec.rb
246
+ - spec/spanx/actor/log_reader_spec.rb
247
+ - spec/spanx/actor/writer_spec.rb
248
+ - spec/spanx/config_spec.rb
249
+ - spec/spanx/helper/timing_spec.rb
250
+ - spec/spanx/notifier/base_spec.rb
251
+ - spec/spanx/notifier/campfire_spec.rb
252
+ - spec/spanx/notifier/email_spec.rb
253
+ - spec/spanx/runner_spec.rb
254
+ - spec/spanx/whitelist_spec.rb
255
+ - spec/spec_helper.rb
256
+ - spec/support/fakeredis.rb
257
+ - spec/support/mail.rb
258
+ homepage: https://github.com/wanelo/spanx
259
+ licenses: []
260
+ post_install_message:
261
+ rdoc_options: []
262
+ require_paths:
263
+ - lib
264
+ required_ruby_version: !ruby/object:Gem::Requirement
265
+ none: false
266
+ requirements:
267
+ - - ! '>='
268
+ - !ruby/object:Gem::Version
269
+ version: '0'
270
+ required_rubygems_version: !ruby/object:Gem::Requirement
271
+ none: false
272
+ requirements:
273
+ - - ! '>='
274
+ - !ruby/object:Gem::Version
275
+ version: '0'
276
+ requirements: []
277
+ rubyforge_project:
278
+ rubygems_version: 1.8.24
279
+ signing_key:
280
+ specification_version: 3
281
+ summary: Real time IP parsing and rate detection gem for access_log files
282
+ test_files:
283
+ - spec/fixtures/access.log.1
284
+ - spec/fixtures/access.log.bots
285
+ - spec/fixtures/config.yml
286
+ - spec/fixtures/config_with_checks.yml
287
+ - spec/fixtures/whitelist.txt
288
+ - spec/spanx/actor/analyzer_spec.rb
289
+ - spec/spanx/actor/collector_spec.rb
290
+ - spec/spanx/actor/log_reader_spec.rb
291
+ - spec/spanx/actor/writer_spec.rb
292
+ - spec/spanx/config_spec.rb
293
+ - spec/spanx/helper/timing_spec.rb
294
+ - spec/spanx/notifier/base_spec.rb
295
+ - spec/spanx/notifier/campfire_spec.rb
296
+ - spec/spanx/notifier/email_spec.rb
297
+ - spec/spanx/runner_spec.rb
298
+ - spec/spanx/whitelist_spec.rb
299
+ - spec/spec_helper.rb
300
+ - spec/support/fakeredis.rb
301
+ - spec/support/mail.rb
302
+ has_rdoc: