bunnicula 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +22 -0
- data/README +30 -0
- data/Rakefile +45 -0
- data/Relayfile +9 -0
- data/bin/bunnicula +7 -0
- data/bunnicula.gemspec +84 -0
- data/config/arguments.rb +70 -0
- data/config/boot.rb +59 -0
- data/config/environment.rb +33 -0
- data/config/environments/development.rb +2 -0
- data/config/environments/production.rb +5 -0
- data/config/environments/test.rb +2 -0
- data/config/post-daemonize/readme +5 -0
- data/config/pre-daemonize/bunnicula.rb +2 -0
- data/config/pre-daemonize/readme +12 -0
- data/lib/bunnicula.rb +61 -0
- data/lib/bunnicula/amqp.rb +38 -0
- data/lib/bunnicula/bunny_farm.rb +21 -0
- data/lib/bunnicula/dsl_base.rb +25 -0
- data/lib/bunnicula/exchange.rb +13 -0
- data/lib/bunnicula/rabbit.rb +37 -0
- data/lib/bunnicula/relay.rb +87 -0
- data/lib/bunnicula/support.rb +11 -0
- data/lib/bunnicula/vampire_rabbit.rb +36 -0
- data/lib/bunnicula/version.rb +3 -0
- data/libexec/bunnicula-daemon.rb +63 -0
- data/test/config/Relayfile.rb +29 -0
- data/test/lib/bunnicula_test.rb +72 -0
- data/test/test_helper.rb +8 -0
- metadata +161 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2010 Nathan Stults
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
== Overview
|
2
|
+
|
3
|
+
== Motivation
|
4
|
+
|
5
|
+
== Approach
|
6
|
+
|
7
|
+
== Install
|
8
|
+
|
9
|
+
Install the gem with:
|
10
|
+
|
11
|
+
gem install bunnicula
|
12
|
+
or
|
13
|
+
sudo gem install bunnicula
|
14
|
+
|
15
|
+
|
16
|
+
== An Example
|
17
|
+
|
18
|
+
== Limitations
|
19
|
+
* Cubicle cannot currently cause child documents to be emitted in the map reduce. This is a pretty big limitation, and will be resolved shortly.
|
20
|
+
* Documentation is non-existent. This is being worked on (head that one before?)
|
21
|
+
* Test coverage is OK, but the tests could be better organized
|
22
|
+
* Code needs to be modularized a bit, main classes are pretty hairy at the moment
|
23
|
+
|
24
|
+
|
25
|
+
== Bugs/Issues
|
26
|
+
Please report them {on github}[http://github.com/plasticlizard/bunnicula/issues].
|
27
|
+
|
28
|
+
== Links
|
29
|
+
|
30
|
+
== Todo
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/config/boot'
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'daemon_kit/tasks'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |rake| load rake }
|
8
|
+
|
9
|
+
Rake::TestTask.new do |t|
|
10
|
+
t.libs << 'libs' << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = false
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'jeweler'
|
17
|
+
require File.dirname(__FILE__) + "/lib/bunnicula/version"
|
18
|
+
|
19
|
+
Jeweler::Tasks.new do |s|
|
20
|
+
s.name = "bunnicula"
|
21
|
+
s.version = Bunnicula::VERSION
|
22
|
+
s.summary = "A very simple relay for moving messages from a local broker to a remote broker"
|
23
|
+
s.description = "A very simple relay for moving messages from a local broker to a remote broker"
|
24
|
+
s.email = "hereiam@sonic.net"
|
25
|
+
s.homepage = "http://github.com/PlasticLizard/bunnicula"
|
26
|
+
s.authors = ["Nathan Stults"]
|
27
|
+
s.has_rdoc = false #=>Should be true, someday
|
28
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
|
29
|
+
s.files = FileList["[A-Z]*", "{bin,lib,config,libexec,test}/**/*"]
|
30
|
+
|
31
|
+
s.add_dependency('daemon-kit', '0.1.7.12')
|
32
|
+
s.add_dependency('bunny', '0.6.0')
|
33
|
+
s.add_dependency('amqp', '0.6.7')
|
34
|
+
|
35
|
+
s.add_development_dependency('shoulda', '2.11.1')
|
36
|
+
end
|
37
|
+
|
38
|
+
Jeweler::GemcutterTasks.new
|
39
|
+
|
40
|
+
rescue LoadError => ex
|
41
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default => :test
|
data/Relayfile
ADDED
data/bin/bunnicula
ADDED
data/bunnicula.gemspec
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{bunnicula}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nathan Stults"]
|
12
|
+
s.date = %q{2010-07-09}
|
13
|
+
s.default_executable = %q{bunnicula}
|
14
|
+
s.description = %q{A very simple relay for moving messages from a local broker to a remote broker}
|
15
|
+
s.email = %q{hereiam@sonic.net}
|
16
|
+
s.executables = ["bunnicula"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
"LICENSE.txt",
|
22
|
+
"README",
|
23
|
+
"Rakefile",
|
24
|
+
"Relayfile",
|
25
|
+
"bin/bunnicula",
|
26
|
+
"bunnicula.gemspec",
|
27
|
+
"config/arguments.rb",
|
28
|
+
"config/boot.rb",
|
29
|
+
"config/environment.rb",
|
30
|
+
"config/environments/development.rb",
|
31
|
+
"config/environments/production.rb",
|
32
|
+
"config/environments/test.rb",
|
33
|
+
"config/post-daemonize/readme",
|
34
|
+
"config/pre-daemonize/bunnicula.rb",
|
35
|
+
"config/pre-daemonize/readme",
|
36
|
+
"lib/bunnicula.rb",
|
37
|
+
"lib/bunnicula/amqp.rb",
|
38
|
+
"lib/bunnicula/bunny_farm.rb",
|
39
|
+
"lib/bunnicula/dsl_base.rb",
|
40
|
+
"lib/bunnicula/exchange.rb",
|
41
|
+
"lib/bunnicula/rabbit.rb",
|
42
|
+
"lib/bunnicula/relay.rb",
|
43
|
+
"lib/bunnicula/support.rb",
|
44
|
+
"lib/bunnicula/vampire_rabbit.rb",
|
45
|
+
"lib/bunnicula/version.rb",
|
46
|
+
"libexec/bunnicula-daemon.rb",
|
47
|
+
"test/config/Relayfile.rb",
|
48
|
+
"test/lib/bunnicula_test.rb",
|
49
|
+
"test/test_helper.rb"
|
50
|
+
]
|
51
|
+
s.homepage = %q{http://github.com/PlasticLizard/bunnicula}
|
52
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
53
|
+
s.require_paths = ["lib"]
|
54
|
+
s.rubygems_version = %q{1.3.7}
|
55
|
+
s.summary = %q{A very simple relay for moving messages from a local broker to a remote broker}
|
56
|
+
s.test_files = [
|
57
|
+
"test/config/Relayfile.rb",
|
58
|
+
"test/lib/bunnicula_test.rb",
|
59
|
+
"test/test_helper.rb"
|
60
|
+
]
|
61
|
+
|
62
|
+
if s.respond_to? :specification_version then
|
63
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
64
|
+
s.specification_version = 3
|
65
|
+
|
66
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
67
|
+
s.add_runtime_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
|
68
|
+
s.add_runtime_dependency(%q<bunny>, ["= 0.6.0"])
|
69
|
+
s.add_runtime_dependency(%q<amqp>, ["= 0.6.7"])
|
70
|
+
s.add_development_dependency(%q<shoulda>, ["= 2.11.1"])
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
|
73
|
+
s.add_dependency(%q<bunny>, ["= 0.6.0"])
|
74
|
+
s.add_dependency(%q<amqp>, ["= 0.6.7"])
|
75
|
+
s.add_dependency(%q<shoulda>, ["= 2.11.1"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<daemon-kit>, ["= 0.1.7.12"])
|
79
|
+
s.add_dependency(%q<bunny>, ["= 0.6.0"])
|
80
|
+
s.add_dependency(%q<amqp>, ["= 0.6.7"])
|
81
|
+
s.add_dependency(%q<shoulda>, ["= 2.11.1"])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
data/config/arguments.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Argument handling for your daemon is configured here.
|
2
|
+
#
|
3
|
+
# You have access to two variables when this file is
|
4
|
+
# parsed. The first is +opts+, which is the object yielded from
|
5
|
+
# +OptionParser.new+, the second is +@options+ which is a standard
|
6
|
+
# Ruby hash that is later accessible through
|
7
|
+
# DaemonKit.arguments.options and can be used in your daemon process.
|
8
|
+
|
9
|
+
# Here is an example:
|
10
|
+
# opts.on('-f', '--foo FOO', 'Set foo') do |foo|
|
11
|
+
# @options[:foo] = foo
|
12
|
+
# end
|
13
|
+
|
14
|
+
|
15
|
+
opts.on('-c PATH','--config_path PATH') do |config|
|
16
|
+
@options[:configuration_file] = config
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-s SOURCE','--source SOURCE') do |source_uri|
|
20
|
+
@options[:source_uri] = source_uri
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-t TARGET','--target TARGET') do |target_uri|
|
24
|
+
@options[:targets] ||= []
|
25
|
+
@options[:targets] << {:target_uri=>target_uri}
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('-r RELAY', '--relay RELAY') do |relay|
|
29
|
+
raise "You specified a RELAY parameter, but did not first specify a TARGET parameter. Please indicate the target AMQP server in the form of -t TARGET or --target TARGET" unless @options[:targets]
|
30
|
+
@options[:targets][-1][:relays] ||= []
|
31
|
+
@options[:targets][-1][:relays] << {:from=>relay}
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on('-y TO', '--to TO') do |to|
|
35
|
+
puts to
|
36
|
+
raise "You specified a RELAY_TO parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
|
37
|
+
relay = @options[:targets][-1][:relays][-1]
|
38
|
+
relay[:to] = to
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-x TYPE', '--type TYPE') do |type|
|
42
|
+
raise "You specified a TYPE parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
|
43
|
+
relay = @options[:targets][-1][:relays][-1]
|
44
|
+
if relay[:to]
|
45
|
+
relay[:to_type] = type.downcase.to_sym
|
46
|
+
else
|
47
|
+
relay[:from_type] = type.downcase.to_sym
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('-d','--durable') do |durable|
|
52
|
+
raise "You specified a DURABLE parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
|
53
|
+
relay = @options[:targets][-1][:relays][-1]
|
54
|
+
if relay[:to]
|
55
|
+
relay[:to_durable] = durable
|
56
|
+
else
|
57
|
+
relay[:from_durable] = durable
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on('-a','--ack') do |ack|
|
62
|
+
raise "You specified a ACK parameter, but did not first specify a RELAY parameter. Please indicate the target AMQP server in the form of -r RELAY or --relay RELAY" unless @options[:targets] && @options[:targets][-1][:relays]
|
63
|
+
relay = @options[:targets][-1][:relays][-1]
|
64
|
+
if relay[:to]
|
65
|
+
relay[:to_ack] = ack
|
66
|
+
else
|
67
|
+
relay[:from_ack] = ack
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/config/boot.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Don't change this file!
|
2
|
+
# Configure your daemon in config/environment.rb
|
3
|
+
|
4
|
+
DAEMON_ROOT = "#{File.expand_path(File.dirname(__FILE__))}/.." unless defined?( DAEMON_ROOT )
|
5
|
+
|
6
|
+
module DaemonKit
|
7
|
+
class << self
|
8
|
+
def boot!
|
9
|
+
unless booted?
|
10
|
+
pick_boot.run
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def booted?
|
15
|
+
defined? DaemonKit::Initializer
|
16
|
+
end
|
17
|
+
|
18
|
+
def pick_boot
|
19
|
+
(vendor_kit? ? VendorBoot : GemBoot).new
|
20
|
+
end
|
21
|
+
|
22
|
+
def vendor_kit?
|
23
|
+
File.exists?( "#{DAEMON_ROOT}/vendor/daemon_kit" )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Boot
|
28
|
+
def run
|
29
|
+
load_initializer
|
30
|
+
DaemonKit::Initializer.run
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class VendorBoot < Boot
|
35
|
+
def load_initializer
|
36
|
+
require "#{DAEMON_ROOT}/vendor/daemon_kit/lib/daemon_kit/initializer"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class GemBoot < Boot
|
41
|
+
def load_initializer
|
42
|
+
begin
|
43
|
+
gem 'daemon-kit'
|
44
|
+
require 'daemon_kit/initializer'
|
45
|
+
rescue Gem::LoadError => e
|
46
|
+
msg = <<EOF
|
47
|
+
You are missing the daemon-kit gem. Please install the following gems:
|
48
|
+
|
49
|
+
* Stable - sudo gem install daemon-kit
|
50
|
+
|
51
|
+
EOF
|
52
|
+
$stderr.puts msg
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
DaemonKit.boot!
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Be sure to restart your daemon when you modify this file
|
2
|
+
|
3
|
+
# Uncomment below to force your daemon into production mode
|
4
|
+
ENV['DAEMON_ENV'] ||= 'production'
|
5
|
+
|
6
|
+
# Boot up
|
7
|
+
require "rubygems"
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'amqp'
|
11
|
+
require 'mq'
|
12
|
+
rescue LoadError
|
13
|
+
$stderr.puts "Missing amqp gem. Please run 'gem install amqp'."
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
18
|
+
|
19
|
+
DaemonKit::Initializer.run do |config|
|
20
|
+
|
21
|
+
# The name of the daemon as reported by process monitoring tools
|
22
|
+
config.daemon_name = 'bunnicula'
|
23
|
+
|
24
|
+
# Force the daemon to be killed after X seconds from asking it to
|
25
|
+
# config.force_kill_wait = 30
|
26
|
+
|
27
|
+
# Log backraces when a thread/daemon dies (Recommended)
|
28
|
+
# config.backtraces = true
|
29
|
+
|
30
|
+
# Configure the safety net (see DaemonKit::Safety)
|
31
|
+
# config.safety_net.handler = :mail # (or :hoptoad )
|
32
|
+
# config.safety_net.mail.host = 'localhost'
|
33
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# You can place files in here to be loaded before the code is daemonized.
|
2
|
+
#
|
3
|
+
# DaemonKit looks for a file named '<config.daemon_name>.rb' and loads
|
4
|
+
# that file first, and inside a DaemonKit::Initializer block. The
|
5
|
+
# remaning files then simply required into the running process.
|
6
|
+
#
|
7
|
+
# These files are mostly useful for operations that should fail blatantly
|
8
|
+
# before daemonizing, like loading gems.
|
9
|
+
#
|
10
|
+
# Be careful not to open any form of IO in here and expecting it to be
|
11
|
+
# open inside the running daemon since all IO instances are closed when
|
12
|
+
# daemonizing (including STDIN, STDOUT & STDERR).
|
data/lib/bunnicula.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bunny"
|
3
|
+
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
["support",
|
6
|
+
"amqp",
|
7
|
+
"bunny_farm",
|
8
|
+
"dsl_base",
|
9
|
+
"exchange",
|
10
|
+
"relay",
|
11
|
+
"rabbit",
|
12
|
+
"vampire_rabbit",].each {|lib|require File.join(dir,'bunnicula',lib)}
|
13
|
+
|
14
|
+
|
15
|
+
module Bunnicula
|
16
|
+
class << self
|
17
|
+
|
18
|
+
|
19
|
+
def suck(amq)
|
20
|
+
vampire_rabbits.each {|vampire|vampire.suck(amq)}
|
21
|
+
end
|
22
|
+
|
23
|
+
def vampire_rabbits
|
24
|
+
@@vampire_rabbits ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def bite(&block)
|
28
|
+
instance_eval(&block)
|
29
|
+
end
|
30
|
+
alias configure bite
|
31
|
+
|
32
|
+
def victim(host=nil,&block)
|
33
|
+
return (@@victim ||= Bunnicula::Rabbit.new) unless host || block_given?
|
34
|
+
@@victim = Bunnicula::Rabbit.new(host)
|
35
|
+
if block_given?
|
36
|
+
if block.arity > 0
|
37
|
+
block.call(@@victim)
|
38
|
+
else
|
39
|
+
@@victim.instance_eval(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
alias source victim
|
44
|
+
|
45
|
+
def transfusion_to(host=nil,&block)
|
46
|
+
rabbit = Bunnicula::VampireRabbit.new(host)
|
47
|
+
if block_given?
|
48
|
+
if (block.arity > 0)
|
49
|
+
block.call(rabbit)
|
50
|
+
else
|
51
|
+
rabbit.instance_eval(&block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
vampire_rabbits << rabbit
|
55
|
+
rabbit
|
56
|
+
end
|
57
|
+
alias target transfusion_to
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module Bunnicula
|
3
|
+
# Thin wrapper around the amqp gem, specifically designed to ease
|
4
|
+
# configuration of a AMQP consumer daemon and provide some added
|
5
|
+
# simplicity
|
6
|
+
class AMQP
|
7
|
+
|
8
|
+
@@instance = nil
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def instance
|
13
|
+
@instance ||= new
|
14
|
+
end
|
15
|
+
|
16
|
+
private :new
|
17
|
+
|
18
|
+
def run(config=nil,&block)
|
19
|
+
@instance = new(config) if config
|
20
|
+
instance.run(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize( config = {} )
|
25
|
+
@config = config || DaemonKit::Config.load('amqp').to_h( true )
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(&block)
|
29
|
+
# Ensure graceful shutdown of the connection to the broker
|
30
|
+
DaemonKit.trap('INT') { ::AMQP.stop { ::EM.stop } }
|
31
|
+
DaemonKit.trap('TERM') { ::AMQP.stop { ::EM.stop } }
|
32
|
+
|
33
|
+
# Start our event loop and AMQP client
|
34
|
+
DaemonKit.logger.debug("AMQP.start(#{@config.inspect})")
|
35
|
+
::AMQP.start(@config, &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bunnicula
|
2
|
+
class BunnyFarm
|
3
|
+
|
4
|
+
def self.bunnies
|
5
|
+
@@bunnies ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.breed(connection_options={})
|
9
|
+
bunny = Bunny.new(connection_options)
|
10
|
+
bunnies << bunny
|
11
|
+
bunny.start
|
12
|
+
bunny
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
def self.stop
|
18
|
+
bunnies.each {|bunny|bunny.stop}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bunnicula
|
2
|
+
module DslBase
|
3
|
+
def dsl_attr(*args)
|
4
|
+
options = args.extract_options!
|
5
|
+
[args].flatten.each {|attr|create_property_for(attr,options)}
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def create_property_for(attr,options)
|
11
|
+
class_eval <<-end_eval
|
12
|
+
@@dsl_attr_defaults ||= {}
|
13
|
+
@@dsl_attr_defaults['#{attr}'] = #{options[:default].inspect}
|
14
|
+
|
15
|
+
def #{attr}(val = nil)
|
16
|
+
return @#{attr} || @@dsl_attr_defaults['#{attr}'] unless val
|
17
|
+
@#{attr} = val
|
18
|
+
end
|
19
|
+
end_eval
|
20
|
+
if (alias_list = options[:alias])
|
21
|
+
[alias_list].flatten.each {|attr_alias| alias_method(attr_alias, attr.to_sym)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Bunnicula::Exchange
|
2
|
+
|
3
|
+
attr_reader :name, :type, :durable, :ack, :routing_key
|
4
|
+
|
5
|
+
def initialize(exchange_name=nil,options={})
|
6
|
+
@name = exchange_name
|
7
|
+
@type = options.delete(:type) || options.delete(:exchange_type)
|
8
|
+
@durable = options.delete(:durable)
|
9
|
+
@ack = options.delete(:ack)
|
10
|
+
@routing_key = options.delete(:routing_key)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Bunnicula
|
4
|
+
class Rabbit
|
5
|
+
extend Bunnicula::DslBase
|
6
|
+
#DSL
|
7
|
+
dsl_attr :host, :default=>"localhost"
|
8
|
+
dsl_attr :username, :password, :default=>"guest"
|
9
|
+
dsl_attr :vhost, :default=>"/"
|
10
|
+
dsl_attr :port, :default=>5672
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(constr=nil)
|
14
|
+
return unless constr
|
15
|
+
if (constr =~ /amqp:\/\//)
|
16
|
+
uri = URI.parse(constr)
|
17
|
+
host uri.host
|
18
|
+
port uri.port || 5672
|
19
|
+
username uri.user || "guest"
|
20
|
+
password uri.password || "guest"
|
21
|
+
vhost uri.path.strip.length < 1 ? "/" : uri.path
|
22
|
+
else
|
23
|
+
host(constr)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
{
|
29
|
+
:host=>host,
|
30
|
+
:port=>port,
|
31
|
+
:username=>username,
|
32
|
+
:password=>password,
|
33
|
+
:vhost=>vhost
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Bunnicula::Relay
|
2
|
+
extend Bunnicula::DslBase
|
3
|
+
|
4
|
+
dsl_attr :filter, :alias=>:where
|
5
|
+
attr_reader :source_exchange, :target_exchange
|
6
|
+
|
7
|
+
def from(*args)
|
8
|
+
return @source_exchange if args.length == 0
|
9
|
+
options = args.extract_options!
|
10
|
+
exchange_name = args.pop
|
11
|
+
@source_exchange = Bunnicula::Exchange.new(exchange_name,options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to(*args)
|
15
|
+
return @target_exchange if args.length == 0
|
16
|
+
options = args.extract_options!
|
17
|
+
exchange_name = args.pop
|
18
|
+
@target_exchange = Bunnicula::Exchange.new(exchange_name,options)
|
19
|
+
end
|
20
|
+
|
21
|
+
#Operational
|
22
|
+
def queue_name
|
23
|
+
"bunnicula.#{from.name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def start(amq,bunny)
|
27
|
+
DaemonKit.logger.info "Starting relay from:#{from.name} to:#{to.name || from.name}"
|
28
|
+
@channel = amq
|
29
|
+
@bunny = bunny
|
30
|
+
|
31
|
+
prepare_destination_exchange
|
32
|
+
bind_to_source_exchange
|
33
|
+
subscribe_to_queue
|
34
|
+
|
35
|
+
DaemonKit.logger.info "Relay started"
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def prepare_destination_exchange
|
42
|
+
exchange_options = {:type=>to.type || from.type || :direct,
|
43
|
+
:durable=>to.durable || from.durable || true}
|
44
|
+
@destination = @bunny.exchange(to.name || from.name, exchange_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def bind_to_source_exchange
|
48
|
+
DaemonKit.logger.info "Binding queue #{queue_name} to the source exchange #{from.name}"
|
49
|
+
@queue = @channel.queue(queue_name,:durable=>from.durable.to_b)
|
50
|
+
@exchange = @channel.instance_eval("#{from.type || :direct}('#{from.name}', :durable=>#{from.durable.to_b})")
|
51
|
+
binding_options = from.type == :topic ? {:key=>from.routing_key || "#"} : {}
|
52
|
+
@queue.bind(@exchange,binding_options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe_to_queue
|
56
|
+
@queue.subscribe(:ack=>from.ack) do |header,message|
|
57
|
+
routing_key = header.properties[:routing_key]
|
58
|
+
DaemonKit.logger.debug "Received message on #{from.name}. Routing Key:#{routing_key}"
|
59
|
+
if relay?(header,message)
|
60
|
+
DaemonKit.logger.debug "Trasmitting #{routing_key} to #{to.name || from.name}"
|
61
|
+
persistent = to.durable || from.durable || true
|
62
|
+
begin
|
63
|
+
@destination.publish(message, :key=>routing_key, :persistent=>persistent)
|
64
|
+
header.ack
|
65
|
+
DaemonKit.logger.debug "#{routing_key} transmitted and acknowledged"
|
66
|
+
rescue RuntimeError=>ex
|
67
|
+
Debug.error "#{routing_key} could not be delivered due to an error"
|
68
|
+
DaemonKit.logger.exception ex
|
69
|
+
end
|
70
|
+
else
|
71
|
+
DaemonKit.logger.debug "#{routing_key} did not satisfy the configured filter (#{filter.inspect})"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def relay?(header,message)
|
77
|
+
return true unless filter
|
78
|
+
return header.properties[:routing_key] =~ filter if filter.is_a?(Regexp)
|
79
|
+
if (filter.is_a?(Proc))
|
80
|
+
return filter.call if proc.arity <= 0
|
81
|
+
return filter.call(header) if proc.arity == 1
|
82
|
+
return filter.call(header,message)
|
83
|
+
end
|
84
|
+
#default behavior
|
85
|
+
header.properties[:routing_key] == filter.to_s
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Bunnicula::VampireRabbit < Bunnicula::Rabbit
|
2
|
+
|
3
|
+
attr_reader :relays
|
4
|
+
|
5
|
+
def initialize(host=nil)
|
6
|
+
super(host)
|
7
|
+
@relays = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def relay(*args,&block)
|
11
|
+
options = args.extract_options!
|
12
|
+
args = [""] if args.length == 0 && block_given?
|
13
|
+
args.each do |from_exchange_name|
|
14
|
+
relay = Bunnicula::Relay.new
|
15
|
+
relay.from(from_exchange_name,options.dup)
|
16
|
+
if block_given?
|
17
|
+
if (block.arity > 0)
|
18
|
+
block.call(relay)
|
19
|
+
else
|
20
|
+
relay.instance_eval(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@relays << relay
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def suck(amq)
|
28
|
+
DaemonKit.logger.info "Setting up relays to #{host}:#{port}, vhost=#{vhost} as #{username}"
|
29
|
+
@bunny = Bunnicula::BunnyFarm.breed(:host=>host, :port=>port, :vhost=>vhost, :user=>username, :pass=>password)
|
30
|
+
@relays.each do |relay|
|
31
|
+
relay.start(amq,@bunny)
|
32
|
+
end
|
33
|
+
DaemonKit.logger.info "Let the good times roll, #{host}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated amqp daemon
|
2
|
+
|
3
|
+
# Do your post daemonization configuration here
|
4
|
+
# At minimum you need just the first line (without the block), or a lot
|
5
|
+
# of strange things might start happening...
|
6
|
+
DaemonKit::Application.running! do |config|
|
7
|
+
# Trap signals with blocks or procs
|
8
|
+
# config.trap( 'INT' ) do
|
9
|
+
# # do something clever
|
10
|
+
# end
|
11
|
+
config.trap( 'TERM', Proc.new { Bunnicula::BunnyFarm.stop } )
|
12
|
+
end
|
13
|
+
|
14
|
+
if( config = DaemonKit.arguments.options[:configuration_file])
|
15
|
+
load config if File.exist?(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
if (source_uri = DaemonKit.arguments.options[:source_uri])
|
19
|
+
Bunnicula.victim(source_uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
if (DaemonKit.arguments.options[:targets])
|
23
|
+
targets = DaemonKit.arguments.options[:targets]
|
24
|
+
targets.each do |target_config|
|
25
|
+
Bunnicula.transfusion_to(target_config[:target_uri]) do |vamp|
|
26
|
+
target_config[:relays].each do |relay_config|
|
27
|
+
vamp.relay do |r|
|
28
|
+
from_options = {}
|
29
|
+
from_options[:durable] = relay_config[:from_durable] if relay_config[:from_durable]
|
30
|
+
from_options[:ack] = relay_config[:from_ack] if relay_config[:from_ack]
|
31
|
+
from_options[:type] = relay_config[:from_type] if relay_config[:from_type]
|
32
|
+
r.from relay_config[:from], from_options
|
33
|
+
if (to_exchange = relay_config[:to])
|
34
|
+
to_options = {}
|
35
|
+
to_options[:durable] = relay_config[:to_durable] if relay_config[:to_durable]
|
36
|
+
to_options[:ack] = relay_config[:to_ack] if relay_config[:to_ack]
|
37
|
+
to_options[:type] = relay_config[:to_type] if relay_config[:to_type]
|
38
|
+
r.to to_exchange, to_options
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Run an event-loop for processing
|
47
|
+
raise "Bunnicula requires a victim. Please specify a source rabbitmq instance via a configuration file or commandline argument" unless Bunnicula.victim
|
48
|
+
Bunnicula::AMQP.run(Bunnicula.victim.to_h) do
|
49
|
+
# Inside this block we're running inside the reactor setup by the
|
50
|
+
# amqp gem. Any code in the examples (from the gem) would work just
|
51
|
+
# fine here.
|
52
|
+
|
53
|
+
# Uncomment this for connection keep-alive
|
54
|
+
# AMQP.conn.connection_status do |status|
|
55
|
+
# DaemonKit.logger.debug("AMQP connection status changed: #{status}")
|
56
|
+
# if status == :disconnected
|
57
|
+
# AMQP.conn.reconnect(true)
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
|
61
|
+
amq = ::MQ.new
|
62
|
+
Bunnicula.suck(amq)
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Bunnicula.bite {
|
2
|
+
victim "amqp://a:b@a-host:12345/tada"
|
3
|
+
|
4
|
+
transfusion_to "example.com" do
|
5
|
+
relay "an exchange name"
|
6
|
+
end
|
7
|
+
|
8
|
+
transfusion_to {
|
9
|
+
host "target_server_1"
|
10
|
+
port 12345
|
11
|
+
username "a"
|
12
|
+
password "b"
|
13
|
+
vhost "tada"
|
14
|
+
|
15
|
+
relay do
|
16
|
+
from "test_source_exchange", :type=>:topic, :durable=>true, :ack=>true
|
17
|
+
to "test_destination_exchange", :durable=>true
|
18
|
+
end
|
19
|
+
|
20
|
+
relay "another_exchange",
|
21
|
+
"and_another_exchange",
|
22
|
+
"and_even_another",
|
23
|
+
:type=>:fanout,
|
24
|
+
:durable=>false,
|
25
|
+
:ack=>true
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class BunniculaTest < Test::Unit::TestCase
|
4
|
+
context "Excuting a representative relay file" do
|
5
|
+
setup do
|
6
|
+
require "config/Relayfile"
|
7
|
+
end
|
8
|
+
|
9
|
+
should "configure the victim" do
|
10
|
+
victim = Bunnicula.victim
|
11
|
+
assert_equal "a-host", victim.host
|
12
|
+
assert_equal 12345, victim.port
|
13
|
+
assert_equal "a", victim.username
|
14
|
+
assert_equal "b", victim.password
|
15
|
+
assert_equal "/tada", victim.vhost
|
16
|
+
end
|
17
|
+
should "configure two vampire rabbits" do
|
18
|
+
assert_equal 2, Bunnicula.vampire_rabbits.length
|
19
|
+
end
|
20
|
+
should "use default values for target rabbit connection when no overrides present" do
|
21
|
+
vamp = Bunnicula.vampire_rabbits[0]
|
22
|
+
assert_equal "example.com", vamp.host
|
23
|
+
assert_equal 5672, vamp.port
|
24
|
+
assert_equal "guest", vamp.username
|
25
|
+
assert_equal "guest", vamp.password
|
26
|
+
assert_equal "/", vamp.vhost
|
27
|
+
end
|
28
|
+
should "accept overrides to default connection values" do
|
29
|
+
vamp = Bunnicula.vampire_rabbits[1]
|
30
|
+
assert_equal "target_server_1", vamp.host
|
31
|
+
assert_equal 12345, vamp.port
|
32
|
+
assert_equal "a", vamp.username
|
33
|
+
assert_equal "b", vamp.password
|
34
|
+
assert_equal "tada", vamp.vhost
|
35
|
+
end
|
36
|
+
should "configure the correct number of relays per vampire rabbit" do
|
37
|
+
assert_equal 1, Bunnicula.vampire_rabbits[0].relays.length
|
38
|
+
assert_equal 4, Bunnicula.vampire_rabbits[1].relays.length
|
39
|
+
end
|
40
|
+
should "construct a default relay with matching from/to exchanges when provided with just a name" do
|
41
|
+
relay = Bunnicula.vampire_rabbits[0].relays[0]
|
42
|
+
assert_equal "an exchange name", relay.source_exchange.name
|
43
|
+
assert_nil relay.source_exchange.type
|
44
|
+
assert_nil relay.source_exchange.durable
|
45
|
+
assert_nil relay.source_exchange.ack
|
46
|
+
assert_nil relay.target_exchange
|
47
|
+
end
|
48
|
+
should "construct source and destination relays via a block" do
|
49
|
+
relay = Bunnicula.vampire_rabbits[1].relays[0]
|
50
|
+
assert_equal "test_source_exchange", relay.from.name
|
51
|
+
assert_equal :topic, relay.from.type
|
52
|
+
assert_equal true, relay.from.durable
|
53
|
+
assert_equal true, relay.from.ack
|
54
|
+
assert_equal "test_destination_exchange", relay.to.name
|
55
|
+
assert_equal true, relay.to.durable
|
56
|
+
assert_nil relay.to.ack
|
57
|
+
assert_nil relay.to.type
|
58
|
+
end
|
59
|
+
should "create a set of relays with common options when created via a list of names" do
|
60
|
+
relays = Bunnicula.vampire_rabbits[1].relays[1..3]
|
61
|
+
assert_equal "another_exchange",relays[0].from.name
|
62
|
+
assert_equal "and_another_exchange",relays[1].from.name
|
63
|
+
assert_equal "and_even_another",relays[2].from.name
|
64
|
+
relays.each do |relay|
|
65
|
+
assert_equal :fanout, relay.from.type
|
66
|
+
assert_equal false, relay.from.durable.to_b
|
67
|
+
assert_equal true, relay.from.ack
|
68
|
+
assert_nil relay.to
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bunnicula
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nathan Stults
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-09 00:00:00 -07:00
|
19
|
+
default_executable: bunnicula
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: daemon-kit
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 67
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 1
|
33
|
+
- 7
|
34
|
+
- 12
|
35
|
+
version: 0.1.7.12
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bunny
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - "="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 7
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 6
|
50
|
+
- 0
|
51
|
+
version: 0.6.0
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: amqp
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - "="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 9
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
- 6
|
66
|
+
- 7
|
67
|
+
version: 0.6.7
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: shoulda
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - "="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 33
|
79
|
+
segments:
|
80
|
+
- 2
|
81
|
+
- 11
|
82
|
+
- 1
|
83
|
+
version: 2.11.1
|
84
|
+
type: :development
|
85
|
+
version_requirements: *id004
|
86
|
+
description: A very simple relay for moving messages from a local broker to a remote broker
|
87
|
+
email: hereiam@sonic.net
|
88
|
+
executables:
|
89
|
+
- bunnicula
|
90
|
+
extensions: []
|
91
|
+
|
92
|
+
extra_rdoc_files:
|
93
|
+
- LICENSE.txt
|
94
|
+
files:
|
95
|
+
- LICENSE.txt
|
96
|
+
- README
|
97
|
+
- Rakefile
|
98
|
+
- Relayfile
|
99
|
+
- bin/bunnicula
|
100
|
+
- bunnicula.gemspec
|
101
|
+
- config/arguments.rb
|
102
|
+
- config/boot.rb
|
103
|
+
- config/environment.rb
|
104
|
+
- config/environments/development.rb
|
105
|
+
- config/environments/production.rb
|
106
|
+
- config/environments/test.rb
|
107
|
+
- config/post-daemonize/readme
|
108
|
+
- config/pre-daemonize/bunnicula.rb
|
109
|
+
- config/pre-daemonize/readme
|
110
|
+
- lib/bunnicula.rb
|
111
|
+
- lib/bunnicula/amqp.rb
|
112
|
+
- lib/bunnicula/bunny_farm.rb
|
113
|
+
- lib/bunnicula/dsl_base.rb
|
114
|
+
- lib/bunnicula/exchange.rb
|
115
|
+
- lib/bunnicula/rabbit.rb
|
116
|
+
- lib/bunnicula/relay.rb
|
117
|
+
- lib/bunnicula/support.rb
|
118
|
+
- lib/bunnicula/vampire_rabbit.rb
|
119
|
+
- lib/bunnicula/version.rb
|
120
|
+
- libexec/bunnicula-daemon.rb
|
121
|
+
- test/config/Relayfile.rb
|
122
|
+
- test/lib/bunnicula_test.rb
|
123
|
+
- test/test_helper.rb
|
124
|
+
has_rdoc: true
|
125
|
+
homepage: http://github.com/PlasticLizard/bunnicula
|
126
|
+
licenses: []
|
127
|
+
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options:
|
130
|
+
- --charset=UTF-8
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
hash: 3
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
version: "0"
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
requirements: []
|
152
|
+
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 1.3.7
|
155
|
+
signing_key:
|
156
|
+
specification_version: 3
|
157
|
+
summary: A very simple relay for moving messages from a local broker to a remote broker
|
158
|
+
test_files:
|
159
|
+
- test/config/Relayfile.rb
|
160
|
+
- test/lib/bunnicula_test.rb
|
161
|
+
- test/test_helper.rb
|