pipeline.rb 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 197e078f9b2e93718eedbde53a8c453536c166a2
4
+ data.tar.gz: ff87a1819aacea18aa67db93c54c1364d14d388e
5
+ SHA512:
6
+ metadata.gz: 90ab0ea4b657163aaf74ffd388377ac730e073292a56ccfab08fe9ea70f60d1e091dc6ed5205729b107bc22593e4725f0e2ce2ea4173231fb4d77d9b2ecd848d
7
+ data.tar.gz: 8e176b651fc34d7d05e18d510214c9967f5e867bcd6c40997377abf5603f1a1392f0e1b79fd6284eb399235d7b361921fef458142979e969e118b1e317b74a31
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ gemspec
2
+
3
+ group(:spec) do
4
+ gem 'rspec-core'
5
+ gem 'rspec-expectations'
6
+ end
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pipeline.rb (0.0.1)
5
+ amq-client
6
+
7
+ GEM
8
+ specs:
9
+ amq-client (1.0.4)
10
+ amq-protocol (>= 1.9.0)
11
+ eventmachine
12
+ amq-protocol (1.9.2)
13
+ diff-lcs (1.2.5)
14
+ eventmachine (1.0.3)
15
+ rspec-core (2.14.7)
16
+ rspec-expectations (2.14.4)
17
+ diff-lcs (>= 1.1.3, < 2.0)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ pipeline.rb!
24
+ rspec-core
25
+ rspec-expectations
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 @botanicus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,141 @@
1
+ # About
2
+
3
+ Plugin pipeline infrastructure connected **through RabbitMQ**. The plugins are just **normal Ruby gems**.
4
+
5
+ ```
6
+ Instance(s) of plugin 1 -> RabbitMQ broker -> Instance(s) of plugin 2
7
+ ```
8
+
9
+ It uses `amq.topic` exchange, so **you can use globs** to consume from within a plugin or a custom script. So if you want to see all the messages going through the system, simply create a queue a bind it to `amq.topic` with routing key `#`. Need all the events? `#event#`. Easy as that.
10
+
11
+ # Why?
12
+
13
+ It's been proved over and over that splitting app into services and is the way to go, because:
14
+
15
+ * It's **easier to maintain** such app.
16
+ * **Rewriting components** is easy.
17
+ * It's **easy to scale** by adding more consumers of given service.
18
+ * It's **easy to inspect** what's going on.
19
+ * It's much **easier to split work** on the project between multiple developers.
20
+ * You can **restart services without losing any data**, as they are kept in RabbitMQ.
21
+ * You don't have to introduce any code dependencies: just consume given queue and publish to another one.
22
+
23
+ # Settings
24
+
25
+ Pipeline.rb uses JSON for configuration.
26
+
27
+ The cool thing is it supports global and local configuration. That means for instance if you want to configure a database, you're probably going to use the same database drivers on either development machine or on production server.
28
+
29
+ So first let's create a global configuration file `config/database.json`:
30
+
31
+ ```json
32
+ {
33
+ "adapter": "mysql",
34
+ "username": "mysql",
35
+ "database": "example"
36
+ }
37
+ ```
38
+
39
+ And now the local `config/database.local.json`:
40
+
41
+ ```json
42
+ {
43
+ "password": "ae28cd87adb5c385117f89e9bd452d18"
44
+ }
45
+ ```
46
+
47
+ Don't forget to ignore the local settings `.gitignore`:
48
+
49
+ ```
50
+ config/*.local.json
51
+ ```
52
+
53
+ # HOWTO
54
+
55
+ Pipeline.rb expects you to provide AMQP configuration, so it knows how to connect to RabbitMQ:
56
+
57
+ `config/amqp.json`:
58
+
59
+ ```json
60
+ {
61
+ "vhost": "example",
62
+ "user": "example",
63
+ }
64
+ ```
65
+
66
+ `config/amqp.local.json`:
67
+
68
+ ```json
69
+ {
70
+ "password": "ae28cd87adb5c385117f89e9bd452d18"
71
+ }
72
+ ```
73
+
74
+ Don't forget to ignore the local settings `.gitignore`:
75
+
76
+ ```
77
+ config/*.local.json
78
+ ```
79
+
80
+ Shop for plugins and add them to your `Gemfile`:
81
+
82
+ ```ruby
83
+ source 'https://rubygems.org'
84
+
85
+ # The app uses pipeline.rb itself.
86
+ gem 'pipeline.rb'
87
+
88
+ # Tasks.
89
+ gem 'nake'
90
+
91
+ # Existing pipeline.rb plugins.
92
+ group(:plugins) do
93
+ gem 'mail_queue'
94
+ end
95
+ ```
96
+
97
+ ```ruby
98
+ #!/usr/bin/env bundle exec ruby
99
+
100
+ require 'nake/runner'
101
+
102
+ # Load all the plugins.
103
+ Bundler.require(:plugins)
104
+
105
+ # Tasks: declare.
106
+ require 'pipeline/tasks'
107
+
108
+ Nake.run
109
+ ```
110
+
111
+ Now run `./tasks.rb declare` to declare all the required queues.
112
+
113
+ And finally create your app:
114
+
115
+ ```ruby
116
+ require 'mail_queue'
117
+
118
+ # This is already loaded,
119
+ # but just to make it clear
120
+ # that the app is actually
121
+ # just another plugin.
122
+ require 'pipeline/plugin'
123
+
124
+ class App < Pipeline::Plugin
125
+ def run
126
+ EM.add_timer(0.5) do
127
+ client.publish("Hello World!", 'emails.random')
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ And finally run it all:
134
+
135
+ 1. Start RabbitMQ.
136
+ 2. Start `bin/mail_queue.rb` to send your emails.
137
+ 3. Run your app.
138
+
139
+ # Deployment
140
+
141
+ TODO: Show example Upstart scripts.
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Local Machine
4
+ # bundle config local.pipeline-mail_queue ~/Dropbox/Code/pipeline-plugins/mail_queue
5
+ # bundle config local.pipeline.rb ~/Dropbox/Code/sources/pipeline.rb
6
+ # bundle config local.nake ~/Dropbox/Code/sources/nake
7
+ gem 'pipeline.rb', github: 'botanicus/pipeline.rb', branch: 'master'
8
+ gem 'nake', github: 'botanicus/nake', branch: 'master'
9
+
10
+ group(:plugins) do
11
+ gem 'pipeline-mail_queue', github: 'botanicus/pipeline-mail_queue', branch: 'master'
12
+ end
@@ -0,0 +1,52 @@
1
+ GIT
2
+ remote: git://github.com/botanicus/nake.git
3
+ revision: 6361daacec1caa6af9e12e7e52e0fcb6cf1b91ac
4
+ branch: master
5
+ specs:
6
+ nake (0.0.9.5)
7
+ term-ansicolor
8
+
9
+ GIT
10
+ remote: git://github.com/botanicus/pipeline-mail_queue.git
11
+ revision: 5f860a6a01de0a85f181e918f1432c05a20b5b37
12
+ branch: master
13
+ specs:
14
+ pipeline-mail_queue (0.0.1)
15
+ mail
16
+ pipeline.rb
17
+
18
+ GIT
19
+ remote: git://github.com/botanicus/pipeline.rb.git
20
+ revision: 345259bc7bc48b8e3a5cc6e8e2594aaefe2bfa96
21
+ branch: master
22
+ specs:
23
+ pipeline.rb (0.0.1)
24
+ amq-client
25
+
26
+ GEM
27
+ remote: https://rubygems.org/
28
+ specs:
29
+ amq-client (1.0.4)
30
+ amq-protocol (>= 1.9.0)
31
+ eventmachine
32
+ amq-protocol (1.9.2)
33
+ eventmachine (1.0.3)
34
+ mail (2.5.4)
35
+ mime-types (~> 1.16)
36
+ treetop (~> 1.4.8)
37
+ mime-types (1.25.1)
38
+ polyglot (0.3.3)
39
+ term-ansicolor (1.2.2)
40
+ tins (~> 0.8)
41
+ tins (0.13.1)
42
+ treetop (1.4.15)
43
+ polyglot
44
+ polyglot (>= 0.3.1)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ nake!
51
+ pipeline-mail_queue!
52
+ pipeline.rb!
@@ -0,0 +1,38 @@
1
+ # About
2
+
3
+ # Setup
4
+
5
+ ## Set Up and Run RabbitMQ
6
+
7
+ ```bash
8
+ rabbitmqctl add_user example ae28cd87adb5c385117f89e9bd452d18
9
+ rabbitmqctl add_vhost example
10
+ rabbitmqctl set_permissions -p example example '.*' '.*' '.*'
11
+ ```
12
+
13
+ ## Fill In Your Gmail Details
14
+
15
+ `config/smtp.local.json`
16
+
17
+ ```json
18
+ {
19
+ "user_name": "you@gmail.com",
20
+ "password": "password",
21
+ }
22
+ ```
23
+
24
+ ## Run Bundler
25
+
26
+ `bundle install`
27
+
28
+ ## Declare Queues
29
+
30
+ `./tasks.rb declare`
31
+
32
+ ## Run `mail_queue` Consumer
33
+
34
+ `bundle exec mail_queue.rb`
35
+
36
+ ## Run The App
37
+
38
+ `./app.rb`
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bundle exec ruby
2
+
3
+ require 'mail_queue'
4
+
5
+ # This is already loaded,
6
+ # but just to make it clear
7
+ # that the app is actually
8
+ # just another plugin.
9
+ require 'pipeline/plugin'
10
+
11
+ $receiver_email = (ARGV.shift || abort("Usage: #{$0} [receiver_email"))
12
+
13
+ class App < Pipeline::Plugin
14
+ def run
15
+ email = Mail.new do
16
+ from 'james@101ideas.cz'
17
+ to $receiver_email
18
+ subject "Hello World!"
19
+ body "Hello World!"
20
+ end
21
+
22
+ EM.add_periodic_timer(1.5) do
23
+ puts "~ Publishing #{email.inspect}"
24
+ client.publish(email.to_s, 'emails.random')
25
+ end
26
+ end
27
+ end
28
+
29
+ App.run(Dir.pwd)
@@ -0,0 +1,5 @@
1
+ {
2
+ "vhost": "example",
3
+ "user": "example",
4
+ "password": "ae28cd87adb5c385117f89e9bd452d18"
5
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "address": "smtp.gmail.com",
3
+ "port": 587,
4
+ "domain": "101ideas.cz",
5
+ "user_name": "TODO: Override in config/smtp.local.json",
6
+ "password": "TODO: Override in config/smtp.local.json",
7
+ "authentication": "plain",
8
+ "enable_starttls_auto": true
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "user_name": "james@101ideas.cz",
3
+ "password": "3ff5s0sgd4dg1gdsFffhsds5s2s58sf6"
4
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bundle exec ruby
2
+
3
+ require 'nake/runner'
4
+
5
+ # Load all the plugins.
6
+ Bundler.require(:plugins)
7
+
8
+ # Tasks: declare.
9
+ require 'pipeline/tasks'
10
+
11
+ Nake.run
@@ -0,0 +1,98 @@
1
+ require 'eventmachine'
2
+ require 'amq/client'
3
+ require 'json'
4
+
5
+ # We aren't handling TCP connection lost, since this is running on the same machine.
6
+ # It means, however, that if we restart the broker, we have to restart all the services manually.
7
+ module Pipeline
8
+ class Client
9
+ def self.boot(config)
10
+ client = self.new
11
+
12
+ # Next tick, so we can use it with Thin.
13
+ EM.next_tick do
14
+ client.connect(config.merge(adapter: 'eventmachine'))
15
+
16
+ # Set up signals.
17
+ ['INT', 'TERM'].each do |signal|
18
+ Signal.trap(signal) do
19
+ puts "~ Received #{signal} signal, terminating."
20
+ client.disconnect { EM.stop }
21
+ end
22
+ end
23
+ end
24
+
25
+ client
26
+ end
27
+
28
+ def initialize
29
+ @on_open_callbacks = Array.new
30
+ end
31
+
32
+ attr_reader :connection, :channel, :exchange, :on_open_callbacks
33
+ def connect(opts)
34
+ @connection = AMQ::Client.connect(opts)
35
+ @channel = AMQ::Client::Channel.new(@connection, 1)
36
+
37
+ @connection.on_open do
38
+ puts "~ Connected to RabbitMQ."
39
+
40
+ @channel.open do
41
+ self.on_open_callbacks.each do |callback|
42
+ callback.call
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def exchange
49
+ @exchange ||= AMQ::Client::Exchange.new(@connection, @channel, 'amq.topic')
50
+ end
51
+
52
+ def declare_queue(name, routing_key)
53
+ queue = AMQ::Client::Queue.new(@connection, @channel, name)
54
+
55
+ self.on_open do
56
+ queue.declare(false, true, false, true) do
57
+ # puts "~ Queue #{queue.name.inspect} is ready"
58
+ end
59
+
60
+ queue.bind(self.exchange.name, routing_key) do
61
+ puts "~ Queue #{queue.name} is now bound to #{self.exchange.name} with routing key #{routing_key}"
62
+ end
63
+ end
64
+
65
+ queue
66
+ end
67
+
68
+ def consumer(name, routing_key = name, &block)
69
+ queue = self.declare_queue(name, routing_key)
70
+
71
+ queue.consume(true) do |consume_ok|
72
+ puts "Subscribed for messages routed to #{queue.name}, consumer tag is #{consume_ok.consumer_tag}, using no-ack mode"
73
+
74
+ queue.on_delivery do |basic_deliver, header, payload|
75
+ block.call(payload, header, basic_deliver)
76
+ end
77
+ end
78
+ end
79
+
80
+ # This runs after the channel is open.
81
+ # TODO: Why amq-client doesn't support adding multiple callbacks?
82
+ def on_open(&block)
83
+ if @channel.status == :opening
84
+ self.on_open_callbacks << block
85
+ else
86
+ block.call
87
+ end
88
+ end
89
+
90
+ def publish(*args)
91
+ self.exchange.publish(*args)
92
+ end
93
+
94
+ def disconnect(&block)
95
+ @connection.disconnect(&block)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,63 @@
1
+ require 'json'
2
+ require 'pathname'
3
+ require 'pipeline/client'
4
+
5
+ module Pipeline
6
+ class Plugin
7
+ def self.plugins
8
+ @@plugins ||= Array.new
9
+ end
10
+
11
+ def self.inherited(subclass)
12
+ Pipeline::Plugin.plugins << subclass
13
+ end
14
+
15
+ def self.run(root)
16
+ plugin = self.new(root)
17
+
18
+ Dir.chdir(plugin.root.to_s) do
19
+ EM.run do
20
+ # First runs next tick from Client.boot.
21
+ EM.next_tick do
22
+ plugin.run!
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ attr_reader :root
29
+ def initialize(root)
30
+ @root = Pathname.new(root)
31
+ @config = Hash.new
32
+
33
+ self.client
34
+ end
35
+
36
+ def config(key)
37
+ @config[key] ||= begin
38
+ path = self.root.join("config/#{key}.json")
39
+ data = JSON.parse(path.read)
40
+
41
+ path = self.root.join("config/#{key}.local.json")
42
+ data.merge!(JSON.parse(path.read)) if path.exist?
43
+
44
+ data.reduce(Hash.new) do |buffer, (key, value)|
45
+ buffer.merge!(key.to_sym => value)
46
+ end
47
+ end
48
+ end
49
+
50
+ def client
51
+ @client ||= Pipeline::Client.boot(self.config('amqp'))
52
+ end
53
+
54
+ def run!
55
+ self.client.on_open do
56
+ EM.add_timer(0.1) do # Oh my, yeah, whatever, it's for now only, dammit!
57
+ puts "~ Running #{self.class}#run."
58
+ self.run
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ require 'nake'
2
+
3
+ Nake::Task.new(:queues) do |task|
4
+ task.define do
5
+ Pipeline::Plugin.plugins.each do |plugin|
6
+ puts "#{plugin}: #{plugin::QUEUES.inspect rescue []}"
7
+ end
8
+ end
9
+ end
10
+
11
+
12
+ Nake::Task.new(:declare) do |task|
13
+ task.define do
14
+ abort "Still on the TODO list.\nFor now, just run all the consumers and everything gets declared at runtime."
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Pipeline
2
+ VERSION ||= '0.0.1'
3
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env gem build
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
4
+
5
+ require 'pipeline/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'pipeline.rb'
9
+ s.version = Pipeline::VERSION
10
+ s.authors = ['@botanicus']
11
+ s.homepage = 'http://github.com/botanicus/pipeline.rb'
12
+ s.summary = "Plugin pipeline infrastructure connected through RabbitMQ."
13
+ s.description = "#{s.summary}. Asynchronous and robust. Also contains configuration system."
14
+ s.email = 'james@101ideas.cz'
15
+ s.files = Dir.glob('**/*')
16
+ s.license = 'MIT'
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'amq-client'
20
+ end
@@ -0,0 +1,5 @@
1
+ {
2
+ "vhost": "example",
3
+ "user": "example",
4
+ "password": "ae28cd87adb5c385117f89e9bd452d18"
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "address": "smtp.gmail.com",
3
+ "port": 587,
4
+ "domain": "101ideas.cz",
5
+ "user_name": "user.global",
6
+ "authentication": "plain",
7
+ "enable_starttls_auto": true
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "user_name": "user.local",
3
+ "password": "password.local"
4
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "local": false
3
+ }
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ require 'pipeline/plugin'
4
+
5
+ describe Pipeline::Plugin do
6
+ describe '#new(root)' do
7
+ it "takes one argument which is the root directory" do
8
+ expect { described_class.new(File.join(Dir.pwd, 'spec', 'data', 'app')) }.not_to raise_error
9
+ end
10
+ end
11
+
12
+ describe '#root' do
13
+ subject { described_class.new(File.join(Dir.pwd, 'spec', 'data', 'app')) }
14
+
15
+ it "returns path to the root directory" do
16
+ subject.root.should be_kind_of(Pathname)
17
+ subject.root.to_s.should match('pipeline.rb/spec/data/app')
18
+ end
19
+ end
20
+
21
+ describe '#config' do
22
+ subject { described_class.new(File.join(Dir.pwd, 'spec', 'data', 'app')) }
23
+
24
+ let(:config) { subject.config('smtp') }
25
+
26
+ it "contains the global settings" do
27
+ config[:address].should eql('smtp.gmail.com')
28
+ end
29
+
30
+ it "contains the global settings" do
31
+ config[:password].should eql('password.local')
32
+ end
33
+
34
+ it "overrides the global settings by the local settings" do
35
+ config[:user_name].should eql('user.local')
36
+ end
37
+
38
+ it "should not require local settings" do
39
+ subject.config('test')[:local].should be_false
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ RSpec.configure do |config|
2
+ config.mock_framework = 'gimme a break will ya?'
3
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pipeline.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - '@botanicus'
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: amq-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Plugin pipeline infrastructure connected through RabbitMQ.. Asynchronous
28
+ and robust. Also contains configuration system.
29
+ email: james@101ideas.cz
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - example/app.rb
35
+ - example/config/amqp.json
36
+ - example/config/smtp.json
37
+ - example/config/smtp.local.json
38
+ - example/Gemfile
39
+ - example/Gemfile.lock
40
+ - example/README.md
41
+ - example/tasks.rb
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - lib/pipeline/client.rb
45
+ - lib/pipeline/plugin.rb
46
+ - lib/pipeline/tasks.rb
47
+ - lib/pipeline/version.rb
48
+ - LINCENSE
49
+ - pipeline.rb.gemspec
50
+ - README.md
51
+ - spec/data/app/config/amqp.json
52
+ - spec/data/app/config/smtp.json
53
+ - spec/data/app/config/smtp.local.json
54
+ - spec/data/app/config/test.json
55
+ - spec/plugin_spec.rb
56
+ - spec/spec_helper.rb
57
+ homepage: http://github.com/botanicus/pipeline.rb
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.0.3
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Plugin pipeline infrastructure connected through RabbitMQ.
81
+ test_files: []