brown 1.0.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 +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: []
|