brown 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Rakefile +19 -0
- data/bin/brown +30 -0
- data/brown.gemspec +29 -0
- data/lib/brown.rb +5 -0
- data/lib/brown/acl_loader.rb +57 -0
- data/lib/brown/acl_lookup.rb +52 -0
- data/lib/brown/agent.rb +32 -0
- data/lib/brown/amqp_errors.rb +148 -0
- data/lib/brown/logger.rb +51 -0
- data/lib/brown/message.rb +73 -0
- data/lib/brown/module_methods.rb +120 -0
- data/lib/brown/queue_definition.rb +32 -0
- data/lib/brown/queue_factory.rb +33 -0
- data/lib/brown/receiver.rb +143 -0
- data/lib/brown/sender.rb +92 -0
- data/lib/brown/util.rb +58 -0
- data/lib/smith.rb +4 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 484e391eba640c3d0556ff4e9ad183427615015b
|
4
|
+
data.tar.gz: 8942004eb6a02b16be0918798311d2796b284539
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d46574e93ee7268e3bfb0d5cfe5be84b8820012682c58e4fd592ea4a85953069da27c4fac289ec7fe362608fb9ee040c4cfd9dd6b8a5536e03046beef69c60ef
|
7
|
+
data.tar.gz: 290cda8b17a65fc9b76fce0439d1e76e02b5f112f82fcc766bc66efb8c9440e39f6c4940fa37b37d8fef5811a7865588cf990cbc487f5495cb5c4c169f8bebd1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
exec(*(["bundle", "exec", $PROGRAM_NAME] + ARGV)) if ENV['BUNDLE_GEMFILE'].nil?
|
2
|
+
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
begin
|
8
|
+
Bundler.setup(:default, :development)
|
9
|
+
rescue Bundler::BundlerError => e
|
10
|
+
$stderr.puts e.message
|
11
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
12
|
+
exit e.status_code
|
13
|
+
end
|
14
|
+
|
15
|
+
Bundler::GemHelper.install_tasks
|
16
|
+
|
17
|
+
task :release do
|
18
|
+
sh "git release"
|
19
|
+
end
|
data/bin/brown
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Run an agent. Any agent.
|
2
|
+
|
3
|
+
require 'envied'
|
4
|
+
|
5
|
+
envied_config = (ENVied.config || ENVied::Configuration.new).tap do |cfg|
|
6
|
+
cfg.enable_defaults!
|
7
|
+
cfg.variable :BROWN_ACL_PATH
|
8
|
+
cfg.variable :RABBITMQ_SERVER
|
9
|
+
cfg.variable :BROWN_LOG_LEVEL, :string, :default => "info"
|
10
|
+
end
|
11
|
+
|
12
|
+
ENVied.require(:default, :config => envied_config)
|
13
|
+
|
14
|
+
# Trick smith-using agents into loving us -- since we have a `smith.rb` and
|
15
|
+
# we should be the only thing in $LOAD_PATH at the moment, our version will
|
16
|
+
# be loaded and the real smith will never darken our door.
|
17
|
+
require 'smith'
|
18
|
+
|
19
|
+
load *ARGV
|
20
|
+
|
21
|
+
agent_classes = ObjectSpace.each_object(Class).select do |k|
|
22
|
+
k != Brown::Agent and k.ancestors.include?(Brown::Agent)
|
23
|
+
end
|
24
|
+
|
25
|
+
Brown::ACLLoader.load_all(ENVied.BROWN_ACL_PATH.split(":"))
|
26
|
+
|
27
|
+
Brown.start(:server_url => ENVied.RABBITMQ_SERVER,
|
28
|
+
:log_level => ENVied.BROWN_LOG_LEVEL) do
|
29
|
+
agent_classes.each { |k| k.new.run }
|
30
|
+
end
|
data/brown.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "git-version-bump"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "brown"
|
5
|
+
s.version = GVB.version
|
6
|
+
s.date = GVB.date
|
7
|
+
|
8
|
+
s.summary = "Run individual smith agents directly from the command line"
|
9
|
+
|
10
|
+
s.licenses = ["GPL-3"]
|
11
|
+
|
12
|
+
s.authors = ["Richard Heycock", "Matt Palmer"]
|
13
|
+
|
14
|
+
s.files = `git ls-files -z`.split("\0")
|
15
|
+
s.executables = %w{brown}
|
16
|
+
|
17
|
+
s.has_rdoc = false
|
18
|
+
|
19
|
+
s.add_runtime_dependency "amqp", "~> 1.4"
|
20
|
+
s.add_runtime_dependency "envied", "~> 0.8"
|
21
|
+
s.add_runtime_dependency "eventmachine-le", "~> 1.0"
|
22
|
+
s.add_runtime_dependency "extlib", "~> 0.9"
|
23
|
+
s.add_runtime_dependency "murmurhash3", "~> 0.1"
|
24
|
+
s.add_runtime_dependency "protobuf", "~> 3.0"
|
25
|
+
|
26
|
+
s.add_development_dependency "bundler"
|
27
|
+
s.add_development_dependency "git-version-bump", "~> 0.10"
|
28
|
+
s.add_development_dependency "rake", "~> 10.4", ">= 10.4.2"
|
29
|
+
end
|
data/lib/brown.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'protobuf'
|
5
|
+
|
6
|
+
require "brown/logger"
|
7
|
+
|
8
|
+
class Brown::ACLLoader
|
9
|
+
include Brown::Logger
|
10
|
+
|
11
|
+
def self.load_all(*dirs)
|
12
|
+
dirs.flatten!
|
13
|
+
pfiles = dirs.each_with_object([]) do |dir, list|
|
14
|
+
list << Dir["#{dir}/*.proto"]
|
15
|
+
end.flatten
|
16
|
+
|
17
|
+
load_proto_files(pfiles, dirs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_proto_files(pfiles, dirs)
|
21
|
+
orig_load_path = $LOAD_PATH
|
22
|
+
|
23
|
+
Dir.mktmpdir do |tmpdir|
|
24
|
+
dirs = pfiles.map { |f| File.dirname(f) }.uniq + [tmpdir]
|
25
|
+
dirs.each { |d| $LOAD_PATH.unshift(d) }
|
26
|
+
|
27
|
+
compiles = []
|
28
|
+
|
29
|
+
pfiles.each do |f|
|
30
|
+
pbrbfile = f.gsub(/\.proto$/, ".pb.rb")
|
31
|
+
unless File.exists?(pbrbfile) and File.stat(pfile).mtime <= File.stat(pbrbfile).mtime
|
32
|
+
compiles << f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
includes = dirs.map { |d| "-I '#{d}'" }.join(" ")
|
37
|
+
|
38
|
+
unless compiles.empty?
|
39
|
+
cmd = "protoc --ruby_out='#{tmpdir}' #{includes} #{compiles.map { |f| "'#{f}'" }.join(' ')} 2>&1"
|
40
|
+
output = nil
|
41
|
+
|
42
|
+
IO.popen(cmd) { |fd| output = fd.read }
|
43
|
+
|
44
|
+
if $?.exitstatus != 0
|
45
|
+
logger.fatal { "protoc failed: #{output}" }
|
46
|
+
raise RuntimeError, output
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
pfiles.each do |f|
|
51
|
+
require "#{File.basename(f, '.proto')}.pb"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
$LOAD_PATH.replace(orig_load_path)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'extlib'
|
4
|
+
require 'murmurhash3'
|
5
|
+
|
6
|
+
module Brown::ACLLookup
|
7
|
+
def get_by_hash(type)
|
8
|
+
hashes[type]
|
9
|
+
end
|
10
|
+
module_function :get_by_hash
|
11
|
+
|
12
|
+
def get_by_type(type)
|
13
|
+
to_murmur32(type)
|
14
|
+
end
|
15
|
+
module_function :get_by_type
|
16
|
+
|
17
|
+
# Look the key up in the cache. This defaults to the key being the hash.
|
18
|
+
# If :by_type => true is passed in as the second argument then it will
|
19
|
+
# perform the lookup in the type hash.
|
20
|
+
#
|
21
|
+
def include?(key, opts={})
|
22
|
+
if opts[:by_type]
|
23
|
+
!get_by_type(key).nil?
|
24
|
+
else
|
25
|
+
!get_by_hash(key).nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module_function :include?
|
29
|
+
|
30
|
+
def clear!
|
31
|
+
@hashes = nil
|
32
|
+
end
|
33
|
+
module_function :clear!
|
34
|
+
|
35
|
+
def hashes
|
36
|
+
@hashes ||= begin
|
37
|
+
map = ObjectSpace.each_object(Class).map do |k|
|
38
|
+
[[to_murmur32(k), k], [k.to_s.split(/::/).last.snake_case, k]]
|
39
|
+
end.flatten(1)
|
40
|
+
Hash[map]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
module_function :hashes
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Convert the name to a base 36 murmur hash
|
48
|
+
def to_murmur32(type)
|
49
|
+
MurmurHash3::V32.murmur3_32_str_hash(type.to_s).to_s(36)
|
50
|
+
end
|
51
|
+
module_function :to_murmur32
|
52
|
+
end
|
data/lib/brown/agent.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require "brown/logger"
|
4
|
+
|
5
|
+
class Brown::Agent
|
6
|
+
include Brown::Logger
|
7
|
+
|
8
|
+
# Override this method to implement your own agent.
|
9
|
+
def run
|
10
|
+
raise ArgumentError, "You must override this method"
|
11
|
+
end
|
12
|
+
|
13
|
+
def receiver(queue_name, opts={}, &blk)
|
14
|
+
queues.receiver(queue_name, opts, &blk)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sender(queue_name, opts={}, &blk)
|
18
|
+
queues.sender(queue_name, opts, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# I care not for your opts... this is just here for Smith compatibility
|
23
|
+
def options(opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def queues
|
30
|
+
@queues ||= Brown::QueueFactory.new
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Brown::AmqpErrors
|
4
|
+
def error_message(code, text, &blk)
|
5
|
+
details = error_lookup(code)
|
6
|
+
message = "#{details[:error_class]} exception: #{code} " +
|
7
|
+
"(#{details[:name]}). #{details[:description]}"
|
8
|
+
|
9
|
+
case code
|
10
|
+
when 404
|
11
|
+
diagnosis = "looks like the queue has been deleted."
|
12
|
+
when 406
|
13
|
+
case text
|
14
|
+
when /.*(unknown delivery tag [0-9]+).*/
|
15
|
+
diagnosis = "#{$1} - you've probably already acknowledged the message."
|
16
|
+
end
|
17
|
+
else
|
18
|
+
end
|
19
|
+
blk.call(message, diagnosis)
|
20
|
+
end
|
21
|
+
|
22
|
+
def error_lookup(code)
|
23
|
+
errors[code]
|
24
|
+
end
|
25
|
+
|
26
|
+
def errors
|
27
|
+
@errors ||= {
|
28
|
+
311 => {
|
29
|
+
:name => "content-too-large",
|
30
|
+
:error_class => "Channel",
|
31
|
+
:description => "The client attempted to transfer content larger " +
|
32
|
+
"than the server could accept at the present time. " +
|
33
|
+
"The client may retry at a later time."
|
34
|
+
},
|
35
|
+
313 => {
|
36
|
+
:name => "no-consumers",
|
37
|
+
:error_class => "Channel",
|
38
|
+
:description => "When the exchange cannot deliver to a consumer " +
|
39
|
+
"when the immediate flag is set. As a result of " +
|
40
|
+
"pending data on the queue or the absence of any " +
|
41
|
+
"consumers of the queue."
|
42
|
+
},
|
43
|
+
320 => {
|
44
|
+
:name => "connection-forced",
|
45
|
+
:error_class => "Connection",
|
46
|
+
:description => "An operator intervened to close the Connection " +
|
47
|
+
"for some reason. The client may retry at some " +
|
48
|
+
"later date."
|
49
|
+
},
|
50
|
+
402 => {
|
51
|
+
:name => "invalid-path",
|
52
|
+
:error_class => "Connection",
|
53
|
+
:description => "The client tried to work with an unknown virtual host."
|
54
|
+
},
|
55
|
+
403 => {
|
56
|
+
:name => "access-refused",
|
57
|
+
:error_class => "Channel",
|
58
|
+
:description => "The client attempted to work with a server " +
|
59
|
+
"entity to which it has no access due to security " +
|
60
|
+
"settings."
|
61
|
+
},
|
62
|
+
404 => {
|
63
|
+
:name => "not-found",
|
64
|
+
:error_class => "Channel",
|
65
|
+
:description => "The client attempted to work with a server " +
|
66
|
+
"entity that does not exist."
|
67
|
+
},
|
68
|
+
405 => {
|
69
|
+
:name => "resource-locked",
|
70
|
+
:error_class => "Channel",
|
71
|
+
:description => "The client attempted to work with a server " +
|
72
|
+
"entity to which it has no access because " +
|
73
|
+
"another client is working with it."
|
74
|
+
},
|
75
|
+
406 => {
|
76
|
+
:name => "precondition-failed",
|
77
|
+
:error_class => "Channel",
|
78
|
+
:description => "The client requested a method that was not " +
|
79
|
+
"allowed because some precondition failed."
|
80
|
+
},
|
81
|
+
501 => {
|
82
|
+
:name => "frame-error",
|
83
|
+
:error_class => "Connection",
|
84
|
+
:description => "The sender sent a malformed frame that the " +
|
85
|
+
"recipient could not decode. This strongly " +
|
86
|
+
"implies a programming error in the sending peer."
|
87
|
+
},
|
88
|
+
502 => {
|
89
|
+
:name => "syntax-error",
|
90
|
+
:error_class => "Connection",
|
91
|
+
:description => "The sender sent a frame that contained illegal " +
|
92
|
+
"values for one or more fields. This strongly " +
|
93
|
+
"implies a programming error in the sending peer."
|
94
|
+
},
|
95
|
+
503 => {
|
96
|
+
:name => "command-invalid",
|
97
|
+
:error_class => "Connection",
|
98
|
+
:description => "The client sent an invalid sequence of frames, " +
|
99
|
+
"attempting to perform an operation that was " +
|
100
|
+
"considered invalid by the server. This usually " +
|
101
|
+
"implies a programming error in the client."
|
102
|
+
},
|
103
|
+
504 => {
|
104
|
+
:name => "channel-error",
|
105
|
+
:error_class => "Connection",
|
106
|
+
:description => "The client attempted to work with a Channel that " +
|
107
|
+
"had not been correctly opened. This most likely " +
|
108
|
+
"indicates a fault in the client layer."
|
109
|
+
},
|
110
|
+
505 => {
|
111
|
+
:name => "unexpected-frame",
|
112
|
+
:error_class => "Connection",
|
113
|
+
:description => "The peer sent a frame that was not expected, " +
|
114
|
+
"usually in the context of a content header and " +
|
115
|
+
"body. This strongly indicates a fault in the " +
|
116
|
+
"peer's content processing."
|
117
|
+
},
|
118
|
+
506 => {
|
119
|
+
:name => "resource-error",
|
120
|
+
:error_class => "Connection",
|
121
|
+
:description => "The server could not complete the method because " +
|
122
|
+
"it lacked sufficient resources. This may be due " +
|
123
|
+
"to the client creating too many of some type of entity."
|
124
|
+
},
|
125
|
+
530 => {
|
126
|
+
:name => "not-allowed",
|
127
|
+
:error_class => "Connection",
|
128
|
+
:description => "The client tried to work with some entity in a " +
|
129
|
+
"manner that is prohibited by the server, due to " +
|
130
|
+
"security settings or by some other criteria."
|
131
|
+
},
|
132
|
+
540 => {
|
133
|
+
:name => "not-implemented",
|
134
|
+
:error_class => "Connection",
|
135
|
+
:description => "The client tried to use functionality that is " +
|
136
|
+
"not implemented in the server."
|
137
|
+
},
|
138
|
+
541 => {
|
139
|
+
:name => "internal-error",
|
140
|
+
:error_class => "Connection",
|
141
|
+
:description => "The server could not complete the method " +
|
142
|
+
"because of an internal error. The server may " +
|
143
|
+
"require intervention by an operator in order " +
|
144
|
+
"to resume normal operations."
|
145
|
+
}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
data/lib/brown/logger.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class Logger
|
6
|
+
VERBOSE = 0.5
|
7
|
+
TRACE = -1
|
8
|
+
|
9
|
+
# Lack of prior planning, peeps!
|
10
|
+
remove_const(:SEV_LABEL)
|
11
|
+
SEV_LABEL = {
|
12
|
+
TRACE => "TRACE",
|
13
|
+
DEBUG => "DEBUG",
|
14
|
+
VERBOSE => "VERB",
|
15
|
+
INFO => "INFO",
|
16
|
+
WARN => "WARN",
|
17
|
+
ERROR => "ERROR",
|
18
|
+
FATAL => "FATAL"
|
19
|
+
}
|
20
|
+
|
21
|
+
def verbose(progname = nil, &block)
|
22
|
+
add(VERBOSE, nil, progname, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def trace(progname = nil, &block)
|
26
|
+
add(TRACE, nil, progname, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Brown::Logger
|
31
|
+
def logger
|
32
|
+
@logger ||= begin
|
33
|
+
Logger.new($stderr).tap do |l|
|
34
|
+
l.formatter = proc { |s,dt,n,msg| "#{$$} [#{s[0]}] #{msg}\n" }
|
35
|
+
l.level = Logger.const_get(Brown.log_level.upcase.to_sym)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_level(level=nil)
|
41
|
+
if level
|
42
|
+
logger.level = Logger.const_get(level.upcase.to_sym)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def backtrace(ex)
|
47
|
+
if ex.respond_to?(:backtrace) and ex.backtrace
|
48
|
+
self.debug { ex.backtrace.map { |l| " #{l}" }.join("\n") }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'brown/logger'
|
4
|
+
|
5
|
+
class Brown::Message
|
6
|
+
include Brown::Logger
|
7
|
+
|
8
|
+
attr_reader :payload, :metadata
|
9
|
+
|
10
|
+
def initialize(payload, metadata, requeue_queue, requeue_options, opts = {}, &blk)
|
11
|
+
@metadata = metadata
|
12
|
+
|
13
|
+
@requeue_queue = requeue_queue
|
14
|
+
@requeue_options = requeue_options
|
15
|
+
|
16
|
+
@requeue_options[:strategy] ||= :linear
|
17
|
+
|
18
|
+
@requeue_options[:on_requeue] ||= ->(count, total_count, cumulative_delay) {
|
19
|
+
logger.info { "Requeuing (#{@requeue_options[:strategy]}) message on queue: #{@requeue_queue.name}, count: #{count} of #{total_count}." }
|
20
|
+
}
|
21
|
+
|
22
|
+
@requeue_options[:on_requeue_limit] ||= ->(message, count, total_count, cumulative_delay) {
|
23
|
+
logger.info { "Not attempting any more requeues, requeue limit reached: #{total_count} for queue: #{@requeue_queue.name}, cummulative delay: #{cumulative_delay}s." }
|
24
|
+
}
|
25
|
+
|
26
|
+
klass = Brown::ACLLookup.get_by_hash(metadata.type)
|
27
|
+
raise RuntimeError, "Unknown ACL: #{metadata.type}" if klass.nil?
|
28
|
+
|
29
|
+
@payload = klass.new.parse_from_string(payload)
|
30
|
+
|
31
|
+
blk.call(@payload, self)
|
32
|
+
ack if opts[:auto_ack]
|
33
|
+
end
|
34
|
+
|
35
|
+
def ack(multiple = false)
|
36
|
+
@metadata.ack(multiple)
|
37
|
+
end
|
38
|
+
|
39
|
+
def nak(opts = {})
|
40
|
+
@metadata.reject(opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :reject, :nak
|
44
|
+
|
45
|
+
def requeue
|
46
|
+
if current_requeue_number < @requeue_options[:count]
|
47
|
+
cumulative_delay = case @requeue_options[:strategy].to_sym
|
48
|
+
when :linear
|
49
|
+
@requeue_options[:delay] * (current_requeue_number + 1)
|
50
|
+
when :exponential
|
51
|
+
@requeue_options[:delay] * (2 ** current_requeue_number)
|
52
|
+
when :exponential_no_initial_delay
|
53
|
+
@requeue_options[:delay] * (2 ** current_requeue_number - 1)
|
54
|
+
else
|
55
|
+
raise RuntimeError, "Unknown requeue strategy #{@requeue_options[:strategy].to_sym.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
EM.add_timer(cumulative_delay) do
|
59
|
+
new_headers = (@metadata.headers || {}).merge('requeue' => current_requeue_number + 1)
|
60
|
+
@requeue_queue.publish(@payload, @metadata.to_hash.merge(:headers => new_headers))
|
61
|
+
end
|
62
|
+
|
63
|
+
@requeue_options[:on_requeue].call(current_requeue_number + 1, @requeue_options[:count], cumulative_delay)
|
64
|
+
else
|
65
|
+
@requeue_options[:on_requeue_limit].call(@payload, current_requeue_number + 1, @requeue_options[:count], @requeue_options[:delay] * current_requeue_number)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def current_requeue_number
|
71
|
+
(@metadata.headers['requeue'] rescue nil) || 0
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# Remove any pre-existing activation of eventmachine, so that `eventmachine-le`
|
2
|
+
# takes priority
|
3
|
+
$:.delete_if { |d| d =~ /\/eventmachine-\d/ }
|
4
|
+
|
5
|
+
require 'eventmachine-le'
|
6
|
+
require 'amqp'
|
7
|
+
require 'uri'
|
8
|
+
|
9
|
+
require 'brown/logger'
|
10
|
+
|
11
|
+
module Brown::ModuleMethods
|
12
|
+
include Brown::Logger
|
13
|
+
|
14
|
+
attr_reader :connection, :log_level
|
15
|
+
|
16
|
+
def compile_acls
|
17
|
+
@compiler = ACLCompiler.new
|
18
|
+
@compiler.compile
|
19
|
+
end
|
20
|
+
|
21
|
+
def running?
|
22
|
+
EM.reactor_running?
|
23
|
+
end
|
24
|
+
|
25
|
+
def start(opts={})
|
26
|
+
@log_level = opts[:log_level] || "info"
|
27
|
+
|
28
|
+
connection_settings = {
|
29
|
+
:on_tcp_connection_failure => method(:tcp_connection_failure_handler),
|
30
|
+
:on_possible_authentication_failure => method(:authentication_failure_handler)
|
31
|
+
}
|
32
|
+
|
33
|
+
AMQP.start(opts[:server_url], connection_settings) do |connection|
|
34
|
+
EM.threadpool_size = 1
|
35
|
+
@connection = connection
|
36
|
+
|
37
|
+
connection.on_connection do
|
38
|
+
logger.info { "Connected to: AMQP Broker: #{broker_identifier(connection)}" }
|
39
|
+
end
|
40
|
+
|
41
|
+
connection.on_tcp_connection_loss do |connection, settings|
|
42
|
+
logger.info { "Reconnecting to AMQP Broker: #{broker_identifier(connection)} in 5s" }
|
43
|
+
connection.reconnect(false, 5)
|
44
|
+
end
|
45
|
+
|
46
|
+
connection.after_recovery do |connection|
|
47
|
+
logger.info { "Connection with AMQP Broker restored: #{broker_identifier(connection)}" }
|
48
|
+
end
|
49
|
+
|
50
|
+
connection.on_error do |connection, connection_close|
|
51
|
+
# If the broker is gracefully shutdown we get a 320. Log a nice message.
|
52
|
+
if connection_close.reply_code == 320
|
53
|
+
logger.info { "AMQP Broker shutdown: #{broker_identifier(connection)}" }
|
54
|
+
else
|
55
|
+
logger.warn { connection_close.reply_text }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# This will be the last thing run by the reactor.
|
60
|
+
shutdown_hook { logger.debug { "Reactor Stopped" } }
|
61
|
+
|
62
|
+
yield if block_given?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def shutdown_hook(&block)
|
67
|
+
EM.add_shutdown_hook(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop(immediately=false, &blk)
|
71
|
+
shutdown_hook(&blk) if blk
|
72
|
+
|
73
|
+
if running?
|
74
|
+
if immediately
|
75
|
+
EM.next_tick do
|
76
|
+
@connection.close { EM.stop_event_loop }
|
77
|
+
end
|
78
|
+
else
|
79
|
+
EM.add_timer(1) do
|
80
|
+
@connection.close { EM.stop_event_loop }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
logger.fatal { "Eventmachine is not running, exiting with prejudice" }
|
85
|
+
exit!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def tcp_connection_failure_handler(settings)
|
92
|
+
# Only display the following settings.
|
93
|
+
s = settings.select { |k,v| ([:user, :pass, :vhost, :host, :port, :ssl].include?(k)) }
|
94
|
+
|
95
|
+
logger.fatal { "Cannot connect to the AMQP server." }
|
96
|
+
logger.fatal { "Is the server running and are the connection details correct?" }
|
97
|
+
logger.info { "Details:" }
|
98
|
+
s.each do |k,v|
|
99
|
+
logger.info { " Setting: %-7s%s" % [k, v] }
|
100
|
+
end
|
101
|
+
EM.stop
|
102
|
+
end
|
103
|
+
|
104
|
+
def authentication_failure_handler(settings)
|
105
|
+
# Only display the following settings.
|
106
|
+
s = settings.select { |k,v| [:user, :pass, :vhost, :host].include?(k) }
|
107
|
+
|
108
|
+
logger.fatal { "Authentication failure." }
|
109
|
+
logger.info { "Details:" }
|
110
|
+
s.each do |k,v|
|
111
|
+
logger.info { " Setting: %-7s%s" % [k, v] }
|
112
|
+
end
|
113
|
+
EM.stop
|
114
|
+
end
|
115
|
+
|
116
|
+
def broker_identifier(connection)
|
117
|
+
broker = connection.broker.properties
|
118
|
+
"#{connection.broker_endpoint}, (#{broker['product']}/v#{broker['version']})"
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
class Brown::QueueDefinition
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(name, options)
|
7
|
+
@normalised_queue = "smith.#{name}"
|
8
|
+
@denormalised_queue = "#{name}"
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def denormalise
|
13
|
+
@denormalised_queue
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@normalised_queue
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalise
|
21
|
+
@normalised_queue
|
22
|
+
end
|
23
|
+
|
24
|
+
# to_a is defined to make the splat operator work.
|
25
|
+
def to_a
|
26
|
+
return @normalised_queue, @options
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"<#{self.class}: #{@denormalised_queue}, #{@options.inspect}>"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
class Brown::QueueFactory
|
4
|
+
def initialize
|
5
|
+
@cache = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
# Convenience method that returns a Sender object.
|
9
|
+
def sender(queue_name, opts={}, &blk)
|
10
|
+
k = "sender:#{queue_name}"
|
11
|
+
@cache[k] ||= Brown::Sender.new(queue_name, opts)
|
12
|
+
blk.call(@cache[k])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Convenience method that returns a Receiver object.
|
16
|
+
def receiver(queue_name, opts={}, &blk)
|
17
|
+
k = "receiver:#{queue_name}"
|
18
|
+
@cache[k] ||= Brown::Receiver.new(queue_name, opts)
|
19
|
+
blk.call(@cache[k])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Passes each queue to the supplied block.
|
23
|
+
def each_queue
|
24
|
+
@cache.values.each do |queue|
|
25
|
+
yield queue if block_given?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns all queues as a hash, with the queue name being the key.
|
30
|
+
def queues
|
31
|
+
@cache
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'brown/logger'
|
4
|
+
require 'brown/util'
|
5
|
+
|
6
|
+
class Brown::Receiver
|
7
|
+
include Brown::Logger
|
8
|
+
include Brown::Util
|
9
|
+
|
10
|
+
def initialize(queue_def, opts={}, &blk)
|
11
|
+
@queue_def = queue_def.is_a?(Brown::QueueDefinition) ? queue_def : Brown::QueueDefinition.new(queue_def, opts)
|
12
|
+
|
13
|
+
@acl_type_cache = Brown::ACLLookup
|
14
|
+
|
15
|
+
@options = opts
|
16
|
+
|
17
|
+
@requeue_options = {}
|
18
|
+
@requeue_queue = Brown::Sender.new(@queue_def, opts)
|
19
|
+
|
20
|
+
@payload_type = Array(option_or_default(@queue_def.options, :type, []))
|
21
|
+
|
22
|
+
prefetch = option_or_default(@queue_def.options, :prefetch, 1)
|
23
|
+
|
24
|
+
@channel_completion = EM::Completion.new
|
25
|
+
@queue_completion = EM::Completion.new
|
26
|
+
|
27
|
+
open_channel(:prefetch => prefetch) do |channel|
|
28
|
+
logger.debug { "channel open for receiver on #{@queue_def.denormalise}" }
|
29
|
+
channel.on_error do |ch, close|
|
30
|
+
logger.fatal { "Channel error: #{close.inspect}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
channel.queue(@queue_def.normalise) do |queue|
|
34
|
+
logger.debug { "Registered queue #{@queue_def.denormalise} on channel" }
|
35
|
+
@queue_completion.succeed(queue)
|
36
|
+
end
|
37
|
+
|
38
|
+
@channel_completion.succeed(channel)
|
39
|
+
end
|
40
|
+
|
41
|
+
blk.call(self) if blk
|
42
|
+
end
|
43
|
+
|
44
|
+
def ack(multiple=false)
|
45
|
+
@channel_completion.completion {|channel| channel.ack(multiple) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Subscribes to a queue and passes the headers and payload into the
|
49
|
+
# block. +subscribe+ will automatically acknowledge the message unless
|
50
|
+
# the options sets :ack to false.
|
51
|
+
def subscribe(opts = {}, &blk)
|
52
|
+
@queue_completion.completion do |queue|
|
53
|
+
logger.debug { "Subscribing to: [queue]:#{@queue_def.denormalise} [options]:#{@queue_def.options}" }
|
54
|
+
queue.subscribe(opts.merge(:ack => true)) do |metadata,payload|
|
55
|
+
logger.debug { "Received a message on #{@queue_def.denormalise}: #{metadata.to_hash.inspect}" }
|
56
|
+
if payload
|
57
|
+
on_message(metadata, payload, &blk)
|
58
|
+
else
|
59
|
+
logger.debug { "Received null message on: #{@queue_def.denormalise} [options]:#{@queue_def.options}" }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def unsubscribe(&blk)
|
66
|
+
@queue_completion.completion do |queue|
|
67
|
+
queue.unsubscribe(&blk)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def requeue_parameters(opts)
|
72
|
+
@requeue_options.merge!(opts)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_requeue(&blk)
|
76
|
+
@requeue_options[:on_requeue] = blk
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_requeue_limit(&blk)
|
80
|
+
@requeue_options[:on_requeue_limit] = blk
|
81
|
+
end
|
82
|
+
|
83
|
+
# pops a message off the queue and passes the headers and payload
|
84
|
+
# into the block. +pop+ will automatically acknowledge the message
|
85
|
+
# unless the options sets :ack to false.
|
86
|
+
def pop(&blk)
|
87
|
+
@queue_completion.completion do |queue|
|
88
|
+
queue.pop({}) do |metadata, payload|
|
89
|
+
if payload
|
90
|
+
on_message(metadata, payload, &blk)
|
91
|
+
else
|
92
|
+
blk.call(nil,nil)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Define a channel error handler.
|
99
|
+
def on_error(chain=false, &blk)
|
100
|
+
# TODO Check that this chains callbacks
|
101
|
+
@channel_completion.completion do |channel|
|
102
|
+
channel.on_error(&blk)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def queue_name
|
107
|
+
@queue_def.denormalise
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete(&blk)
|
111
|
+
@queue_completion.completion do |queue|
|
112
|
+
@channel_completion.completion do |channel|
|
113
|
+
queue.unbind(exchange) do
|
114
|
+
queue.delete do
|
115
|
+
exchange.delete do
|
116
|
+
channel.close(&blk)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def status(&blk)
|
125
|
+
@queue_completion.completion do |queue|
|
126
|
+
queue.status do |num_messages, num_consumers|
|
127
|
+
blk.call(num_messages, num_consumers)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def on_message(metadata, payload, &blk)
|
135
|
+
if @payload_type.empty? || @payload_type.include?(@acl_type_cache.get_by_hash(metadata.type))
|
136
|
+
Brown::Message.new(payload, metadata, @requeue_queue, @requeue_options, @options, &blk)
|
137
|
+
else
|
138
|
+
allowable_acls = @payload_type.join(", ")
|
139
|
+
received_acl = @acl_type_cache.get_by_hash(metadata.type)
|
140
|
+
raise ACL::IncorrectTypeError, "Received ACL: #{received_acl} on queue: #{@queue_def.denormalise}. This queue can only accept the following ACLs: #{allowable_acls}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/brown/sender.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'brown/logger'
|
4
|
+
require 'brown/util'
|
5
|
+
|
6
|
+
class Brown::Sender
|
7
|
+
include Brown::Logger
|
8
|
+
include Brown::Util
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(queue_def, opts={})
|
13
|
+
@queue_def = queue_def.is_a?(Brown::QueueDefinition) ? queue_def : Brown::QueueDefinition.new(queue_def, opts)
|
14
|
+
@name = @queue_def.denormalise
|
15
|
+
|
16
|
+
@reply_container = {}
|
17
|
+
|
18
|
+
@message_count = 0
|
19
|
+
|
20
|
+
@channel_completion = EM::Completion.new
|
21
|
+
|
22
|
+
open_channel do |channel|
|
23
|
+
logger.debug { "Opening a channel for sending" }
|
24
|
+
@channel_completion.succeed(channel)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def publish(payload, opts={}, &blk)
|
29
|
+
logger.debug { "Publishing to: [queue]: #{@queue_def.denormalise}. [options]: #{opts}" }
|
30
|
+
logger.debug { "ACL content: [queue]: #{@queue_def.denormalise}, [metadata type]: #{payload.class}, [message]: #{payload.inspect}" }
|
31
|
+
|
32
|
+
increment_counter
|
33
|
+
|
34
|
+
type = Brown::ACLLookup.get_by_type(payload.class)
|
35
|
+
|
36
|
+
@channel_completion.completion do |channel|
|
37
|
+
logger.debug { "Publishing #{payload.inspect} to queue #{@queue_def.denormalise}" }
|
38
|
+
AMQP::Exchange.default(channel).publish(payload.to_s, opts.merge(:type => type, :routing_key => @queue_def.normalise), &blk)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(&blk)
|
43
|
+
queue.delete do
|
44
|
+
@channel_completion.completion do |channel|
|
45
|
+
channel.close(&blk)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
=begin
|
51
|
+
def status(&blk)
|
52
|
+
@queue_completion.completion do |queue|
|
53
|
+
queue.status do |num_messages, num_consumers|
|
54
|
+
blk.call(num_messages, num_consumers) if blk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def message_count(&blk)
|
60
|
+
status do |messages|
|
61
|
+
blk.call(messages) if blk
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def consumer_count(&blk)
|
66
|
+
status do |_, consumers|
|
67
|
+
blk.call(consumers) if blk
|
68
|
+
end
|
69
|
+
end
|
70
|
+
=end
|
71
|
+
|
72
|
+
def counter
|
73
|
+
@message_count
|
74
|
+
end
|
75
|
+
|
76
|
+
# Define a channel error handler.
|
77
|
+
def on_error(chain=false, &blk)
|
78
|
+
@channel_completion.completion do |channel|
|
79
|
+
channel.on_error(&blk)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def queue_name
|
84
|
+
@queue_def.denormalise
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def increment_counter(value=1)
|
90
|
+
@message_count += value
|
91
|
+
end
|
92
|
+
end
|
data/lib/brown/util.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require "brown/amqp_errors"
|
4
|
+
require "brown/logger"
|
5
|
+
|
6
|
+
module Brown::Util
|
7
|
+
include Brown::AmqpErrors
|
8
|
+
include Brown::Logger
|
9
|
+
|
10
|
+
def number_of_messages
|
11
|
+
status do |num_messages, _|
|
12
|
+
yield num_messages
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def number_of_consumers
|
17
|
+
status do |_, num_consumers|
|
18
|
+
yield num_consumers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def open_channel(opts={}, &blk)
|
25
|
+
AMQP::Channel.new(Brown.connection) do |channel,_|
|
26
|
+
logger.debug { "Opened channel: #{"%#x" % channel.object_id}" }
|
27
|
+
|
28
|
+
channel.auto_recovery = true
|
29
|
+
logger.debug { "Channel auto recovery enabled" }
|
30
|
+
|
31
|
+
# Set up QoS. If you do not do this then any subscribes will get
|
32
|
+
# overwhelmed if there are too many messages.
|
33
|
+
prefetch = opts[:prefetch] || 1
|
34
|
+
|
35
|
+
channel.prefetch(prefetch)
|
36
|
+
logger.debug { "AMQP prefetch set to: #{prefetch}" }
|
37
|
+
|
38
|
+
blk.call(channel)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def random(prefix = '', suffix = '')
|
43
|
+
"#{prefix}#{SecureRandom.hex(8)}#{suffix}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def option_or_default(options, key, default, &blk)
|
47
|
+
if options.is_a?(Hash)
|
48
|
+
if options.key?(key)
|
49
|
+
v = options.delete(key)
|
50
|
+
(blk) ? blk.call(v) : v
|
51
|
+
else
|
52
|
+
default
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise ArguementError, "Options must be a Hash."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/smith.rb
ADDED
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brown
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Heycock
|
8
|
+
- Matt Palmer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-02-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: amqp
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.4'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.4'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: envied
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0.8'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0.8'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: eventmachine-le
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: extlib
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.9'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.9'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: murmurhash3
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.1'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.1'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: protobuf
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '3.0'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: bundler
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: git-version-bump
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0.10'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0.10'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rake
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - "~>"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '10.4'
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 10.4.2
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '10.4'
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 10.4.2
|
146
|
+
description:
|
147
|
+
email:
|
148
|
+
executables:
|
149
|
+
- brown
|
150
|
+
extensions: []
|
151
|
+
extra_rdoc_files: []
|
152
|
+
files:
|
153
|
+
- ".gitignore"
|
154
|
+
- Gemfile
|
155
|
+
- Rakefile
|
156
|
+
- bin/brown
|
157
|
+
- brown.gemspec
|
158
|
+
- lib/brown.rb
|
159
|
+
- lib/brown/acl_loader.rb
|
160
|
+
- lib/brown/acl_lookup.rb
|
161
|
+
- lib/brown/agent.rb
|
162
|
+
- lib/brown/amqp_errors.rb
|
163
|
+
- lib/brown/logger.rb
|
164
|
+
- lib/brown/message.rb
|
165
|
+
- lib/brown/module_methods.rb
|
166
|
+
- lib/brown/queue_definition.rb
|
167
|
+
- lib/brown/queue_factory.rb
|
168
|
+
- lib/brown/receiver.rb
|
169
|
+
- lib/brown/sender.rb
|
170
|
+
- lib/brown/util.rb
|
171
|
+
- lib/smith.rb
|
172
|
+
homepage:
|
173
|
+
licenses:
|
174
|
+
- GPL-3
|
175
|
+
metadata: {}
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubyforge_project:
|
192
|
+
rubygems_version: 2.2.2
|
193
|
+
signing_key:
|
194
|
+
specification_version: 4
|
195
|
+
summary: Run individual smith agents directly from the command line
|
196
|
+
test_files: []
|