fare 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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/README.md +470 -0
- data/Rakefile +28 -0
- data/bin/fare +48 -0
- data/fare.gemspec +35 -0
- data/features/multiqueue.feature +60 -0
- data/features/multistack.feature +65 -0
- data/features/step_definitions/aruba.rb +1 -0
- data/features/step_definitions/fare_steps.rb +40 -0
- data/features/subscriber.feature +95 -0
- data/features/support/env.rb +34 -0
- data/lib/fare.rb +96 -0
- data/lib/fare/configuration.rb +57 -0
- data/lib/fare/configuration_dsl.rb +134 -0
- data/lib/fare/configuration_when_locked.rb +82 -0
- data/lib/fare/event.rb +26 -0
- data/lib/fare/generate_lock_file.rb +131 -0
- data/lib/fare/load_configuration_file.rb +45 -0
- data/lib/fare/middleware/logging.rb +46 -0
- data/lib/fare/middleware/newrelic.rb +35 -0
- data/lib/fare/middleware/raven.rb +47 -0
- data/lib/fare/publisher.rb +65 -0
- data/lib/fare/queue_adapter.rb +30 -0
- data/lib/fare/rspec.rb +85 -0
- data/lib/fare/subscriber.rb +35 -0
- data/lib/fare/subscriber_cli.rb +270 -0
- data/lib/fare/subscriber_stack.rb +39 -0
- data/lib/fare/test_mode.rb +204 -0
- data/lib/fare/topic.rb +25 -0
- data/lib/fare/topic_adapter.rb +13 -0
- data/lib/fare/update_cli.rb +41 -0
- data/lib/fare/version.rb +3 -0
- data/spec/logger_spec.rb +45 -0
- data/spec/raven_spec.rb +52 -0
- data/spec/rspec_integration_spec.rb +45 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/stubbed_subscribing_spec.rb +66 -0
- metadata +264 -0
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'cucumber'
|
5
|
+
require 'cucumber/rake/task'
|
6
|
+
|
7
|
+
Cucumber::Rake::Task.new(:features)
|
8
|
+
|
9
|
+
rescue LoadError
|
10
|
+
task :features do
|
11
|
+
puts "Cucumber not installed."
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
19
|
+
t.pattern = "spec"
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
task :spec do
|
23
|
+
puts "RSpec not installed"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => [:spec, :features]
|
data/bin/fare
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path("../../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require "fare"
|
7
|
+
require "optparse"
|
8
|
+
|
9
|
+
command = ARGV.shift
|
10
|
+
|
11
|
+
def show_help
|
12
|
+
puts <<-HELP.gsub(/^ +/, '')
|
13
|
+
Usage: fare COMMAND [OPTIONS]
|
14
|
+
|
15
|
+
Available commands:
|
16
|
+
|
17
|
+
fare update Parses your configuration file, creates topics, queus, subscriptions and caches it.
|
18
|
+
fare subscriber Controls the subscribers.
|
19
|
+
|
20
|
+
You can run any command with the --help option.
|
21
|
+
|
22
|
+
Example: fare subscriber --help
|
23
|
+
HELP
|
24
|
+
end
|
25
|
+
|
26
|
+
case command
|
27
|
+
when "update"
|
28
|
+
require "fare/update_cli"
|
29
|
+
Fare::UpdateCLI.new(ARGV).call
|
30
|
+
when "subscriber"
|
31
|
+
require "fare/subscriber_cli"
|
32
|
+
Fare::SubscriberCLI.new(ARGV).call
|
33
|
+
when "-v", "--version"
|
34
|
+
puts "Fare version #{Fare::VERSION}"
|
35
|
+
exit 0
|
36
|
+
when "help", "--help", "-h"
|
37
|
+
show_help
|
38
|
+
exit 0
|
39
|
+
when "", nil
|
40
|
+
show_help
|
41
|
+
exit 1
|
42
|
+
else
|
43
|
+
puts "Unkown command: #{command}"
|
44
|
+
puts
|
45
|
+
show_help
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
data/fare.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fare/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fare"
|
8
|
+
spec.version = Fare::VERSION
|
9
|
+
spec.authors = ["iain"]
|
10
|
+
spec.email = ["iain@iain.nl"]
|
11
|
+
spec.description = %q{An event system built on Amazon SNS and SQS}
|
12
|
+
spec.summary = %q{An event system built on Amazon SNS and SQS}
|
13
|
+
spec.homepage = "https://github.com/yourkarma/fare"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "aws-sdk"
|
21
|
+
spec.add_dependency "daemonic", "~> 0.1.2"
|
22
|
+
spec.add_dependency "virtus"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "thin"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "fake_sqs", "~> 0.0.10"
|
29
|
+
spec.add_development_dependency "fake_sns", "~> 0.0.2"
|
30
|
+
|
31
|
+
spec.add_development_dependency "cucumber"
|
32
|
+
spec.add_development_dependency "aruba"
|
33
|
+
spec.add_development_dependency "json_expressions"
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: Multiqueue
|
2
|
+
|
3
|
+
Scenario: Different subscribers for different queues
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
publishes subject: "user", action: "signup", version: "0.1"
|
11
|
+
|
12
|
+
subscriber do
|
13
|
+
|
14
|
+
setup do
|
15
|
+
require File.expand_path("../../middleware", __FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
stack do
|
19
|
+
listen_to subject: "user", action: "login"
|
20
|
+
run do
|
21
|
+
use RegisterEvent, name: "default-queue"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
subscriber :other_queue do
|
28
|
+
|
29
|
+
setup do
|
30
|
+
require File.expand_path("../../middleware", __FILE__)
|
31
|
+
end
|
32
|
+
|
33
|
+
stack do
|
34
|
+
listen_to subject: "user", action: "login"
|
35
|
+
run do
|
36
|
+
use RegisterEvent, name: "other-queue"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
"""
|
42
|
+
|
43
|
+
And a file named "middleware.rb" with:
|
44
|
+
"""
|
45
|
+
class RegisterEvent
|
46
|
+
def initialize(app, options = {})
|
47
|
+
@app = app
|
48
|
+
@options = options
|
49
|
+
end
|
50
|
+
def call(env)
|
51
|
+
puts "Registered event by #{@options[:name]}"
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
"""
|
56
|
+
|
57
|
+
When I publish an event with the subject "user" and action "login"
|
58
|
+
And I run `fare subscriber start --name other_queue`
|
59
|
+
Then the output should contain "Registered event by other-queue"
|
60
|
+
But the output should not contain "Registered event by default-queue"
|
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Multistack
|
2
|
+
|
3
|
+
Scenario: Different stacks for different messages
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
publishes subject: "user", action: "signup", version: "0.1"
|
11
|
+
|
12
|
+
subscriber do
|
13
|
+
|
14
|
+
setup do
|
15
|
+
require File.expand_path("../../middleware", __FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
stack do
|
19
|
+
listen_to subject: "user", action: "login"
|
20
|
+
run do
|
21
|
+
use TestForLogin
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
stack do
|
26
|
+
listen_to subject: "user", action: "signup"
|
27
|
+
run do
|
28
|
+
use TestForSignup
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
"""
|
34
|
+
|
35
|
+
And a file named "middleware.rb" with:
|
36
|
+
"""
|
37
|
+
class TestForLogin
|
38
|
+
def initialize(app)
|
39
|
+
@app = app
|
40
|
+
end
|
41
|
+
def call(env)
|
42
|
+
puts "Received Login"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class TestForSignup
|
48
|
+
def initialize(app)
|
49
|
+
@app = app
|
50
|
+
end
|
51
|
+
def call(env)
|
52
|
+
puts "Received Signup"
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
"""
|
57
|
+
|
58
|
+
When I publish an event with the subject "user" and action "login"
|
59
|
+
And I run `fare subscriber start`
|
60
|
+
Then the output should contain "Received Login"
|
61
|
+
But the output should not contain "Received Signup"
|
62
|
+
|
63
|
+
When I publish an event with the subject "user" and action "signup"
|
64
|
+
And I run `fare subscriber start`
|
65
|
+
Then the output should contain "Received Signup"
|
@@ -0,0 +1 @@
|
|
1
|
+
require "aruba/cucumber"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Given(/^the Fare config:$/) do |string|
|
2
|
+
|
3
|
+
extra = %Q|require File.expand_path("../../helper.rb", __FILE__)|
|
4
|
+
|
5
|
+
write_file "config/fare.rb", "#{extra}\n#{string}"
|
6
|
+
|
7
|
+
write_file "helper.rb", <<-HELPER
|
8
|
+
|
9
|
+
AWS.config(
|
10
|
+
use_ssl: false,
|
11
|
+
sqs_endpoint: "localhost",
|
12
|
+
sqs_port: 4568,
|
13
|
+
sns_endpoint: "localhost",
|
14
|
+
sns_port: 9293,
|
15
|
+
access_key_id: "fake access key",
|
16
|
+
secret_access_key: "fake secret key",
|
17
|
+
)
|
18
|
+
|
19
|
+
HELPER
|
20
|
+
|
21
|
+
run_simple("fare update")
|
22
|
+
end
|
23
|
+
|
24
|
+
When(/^I give SNS time to propagate the messages$/) do
|
25
|
+
$fake_sns.drain
|
26
|
+
end
|
27
|
+
|
28
|
+
When(/^I publish an event with the subject "(.*?)" and action "(.*?)"$/) do |subject, action|
|
29
|
+
write_file "publisher.rb", <<-PUBLISHER
|
30
|
+
require "bundler/setup"
|
31
|
+
Bundler.require(:default, :test)
|
32
|
+
Fare.publish(subject: #{subject.inspect}, action: #{action.inspect}, payload: "payload")
|
33
|
+
PUBLISHER
|
34
|
+
|
35
|
+
run_simple "ruby publisher.rb"
|
36
|
+
|
37
|
+
$fake_sns.drain
|
38
|
+
$fake_sns.reset
|
39
|
+
run_simple("fare update --force")
|
40
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
Feature: Subscriber
|
2
|
+
|
3
|
+
Scenario: Running in the foreground
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
|
11
|
+
subscriber do
|
12
|
+
|
13
|
+
stack do
|
14
|
+
listen_to subject: "user", action: "login"
|
15
|
+
|
16
|
+
run do
|
17
|
+
use Class.new {
|
18
|
+
|
19
|
+
def initialize(app, options = {})
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
event = env.fetch(:event)
|
25
|
+
puts "Event received: #{event.subject} - #{event.action}"
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
"""
|
35
|
+
|
36
|
+
When I publish an event with the subject "user" and action "login"
|
37
|
+
|
38
|
+
And I run `fare subscriber start`
|
39
|
+
|
40
|
+
Then the output should contain "Event received: user - login"
|
41
|
+
|
42
|
+
Scenario: Multiple Middleware
|
43
|
+
|
44
|
+
Given the Fare config:
|
45
|
+
"""
|
46
|
+
app_name "my_app"
|
47
|
+
|
48
|
+
publishes subject: "user", action: "login", version: "0.1"
|
49
|
+
|
50
|
+
subscriber do
|
51
|
+
|
52
|
+
setup do
|
53
|
+
require File.expand_path("../../middleware", __FILE__)
|
54
|
+
end
|
55
|
+
|
56
|
+
stack do
|
57
|
+
listen_to subject: "user", action: "login"
|
58
|
+
|
59
|
+
run do
|
60
|
+
use TestMiddlewareOne
|
61
|
+
use TestMiddlewareTwo
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
"""
|
67
|
+
|
68
|
+
And a file named "middleware.rb" with:
|
69
|
+
"""
|
70
|
+
class TestMiddlewareOne
|
71
|
+
def initialize(app)
|
72
|
+
@app = app
|
73
|
+
end
|
74
|
+
def call(env)
|
75
|
+
env[:one] = "Extra Data"
|
76
|
+
@app.call(env)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class TestMiddlewareTwo
|
81
|
+
def initialize(app)
|
82
|
+
@app = app
|
83
|
+
end
|
84
|
+
def call(env)
|
85
|
+
puts "Received data: #{env[:one]}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end
|
89
|
+
"""
|
90
|
+
|
91
|
+
When I publish an event with the subject "user" and action "login"
|
92
|
+
|
93
|
+
And I run `fare subscriber start`
|
94
|
+
|
95
|
+
Then the output should contain "Received data: Extra Data"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
ENV["RACK_ENV"] = "test"
|
2
|
+
require "bundler/setup"
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
|
5
|
+
require "fare"
|
6
|
+
|
7
|
+
require "fake_sns/test_integration"
|
8
|
+
require "fake_sqs/test_integration"
|
9
|
+
|
10
|
+
AWS.config(
|
11
|
+
use_ssl: false,
|
12
|
+
sqs_endpoint: "localhost",
|
13
|
+
sqs_port: 4568,
|
14
|
+
sns_endpoint: "localhost",
|
15
|
+
sns_port: 9293,
|
16
|
+
access_key_id: "fake access key",
|
17
|
+
secret_access_key: "fake secret key",
|
18
|
+
)
|
19
|
+
|
20
|
+
$fake_sns = FakeSNS::TestIntegration.new(database: ":memory:")
|
21
|
+
$fake_sqs = FakeSQS::TestIntegration.new(database: ":memory:")
|
22
|
+
|
23
|
+
$fake_sns.start
|
24
|
+
$fake_sqs.start
|
25
|
+
|
26
|
+
at_exit {
|
27
|
+
$fake_sns.stop
|
28
|
+
$fake_sqs.stop
|
29
|
+
}
|
30
|
+
|
31
|
+
Before do
|
32
|
+
$fake_sns.reset
|
33
|
+
$fake_sqs.reset
|
34
|
+
end
|
data/lib/fare.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
require "verbose_hash_fetch"
|
3
|
+
require "base64"
|
4
|
+
require "json"
|
5
|
+
require "yaml"
|
6
|
+
require "digest/md5"
|
7
|
+
require "virtus"
|
8
|
+
require "forwardable"
|
9
|
+
|
10
|
+
require "fare/version"
|
11
|
+
|
12
|
+
require "fare/load_configuration_file"
|
13
|
+
require "fare/configuration_dsl"
|
14
|
+
require "fare/configuration"
|
15
|
+
require "fare/subscriber_stack"
|
16
|
+
require "fare/generate_lock_file"
|
17
|
+
require "fare/topic"
|
18
|
+
|
19
|
+
require "fare/queue_adapter"
|
20
|
+
require "fare/topic_adapter"
|
21
|
+
|
22
|
+
require "fare/configuration_when_locked"
|
23
|
+
require "fare/publisher"
|
24
|
+
|
25
|
+
require "fare/subscriber"
|
26
|
+
require "fare/event"
|
27
|
+
|
28
|
+
require "fare/middleware/logging"
|
29
|
+
require "fare/middleware/raven"
|
30
|
+
|
31
|
+
module Fare
|
32
|
+
ConfigurationNotFoundError = Class.new(RuntimeError)
|
33
|
+
LockFileNotFoundError = Class.new(RuntimeError)
|
34
|
+
ChecksumNotMatchingError = Class.new(RuntimeError)
|
35
|
+
NoEnvironmentFoundError = Class.new(RuntimeError)
|
36
|
+
|
37
|
+
class << self
|
38
|
+
attr_accessor :queue_adapter, :topic_adapter
|
39
|
+
end
|
40
|
+
self.queue_adapter = QueueAdapter
|
41
|
+
self.topic_adapter = TopicAdapter
|
42
|
+
|
43
|
+
def self.test_mode!
|
44
|
+
require "fare/test_mode"
|
45
|
+
@message_list = TestMode::MessageList.new
|
46
|
+
self.queue_adapter = @message_list.queue_adapter
|
47
|
+
self.topic_adapter = @message_list.topic_adapter
|
48
|
+
update(force: true)
|
49
|
+
@configuration = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.stubbed_messages
|
53
|
+
if @message_list
|
54
|
+
@message_list
|
55
|
+
else
|
56
|
+
fail "Not in test mode. Enable it with: Fare.test_mode!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.given_event(*args)
|
61
|
+
stubbed_messages.given_event(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.run(*args)
|
65
|
+
stubbed_messages.run(*args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.default_config_file
|
69
|
+
File.expand_path("config/fare.rb", Dir.pwd)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.default_environment
|
73
|
+
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"]
|
74
|
+
if env.nil? && defined?(::Rails)
|
75
|
+
env = Rails.env
|
76
|
+
end
|
77
|
+
env
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.update(options = {})
|
81
|
+
GenerateLockFile.new(options).call
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.publish(options)
|
85
|
+
Publisher.new(configuration, options).call
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.configuration
|
89
|
+
@configuration ||= config({})
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.config(options)
|
93
|
+
@configuration = ConfigurationWhenLocked.new(options)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|