pipeline.rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []