bunnicula 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|