gorgon 0.8.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile.lock +3 -3
- data/README.md +1 -1
- data/lib/gorgon/listener.rb +15 -4
- data/lib/gorgon/originator.rb +16 -1
- data/lib/gorgon/originator_protocol.rb +38 -5
- data/lib/gorgon/settings/rails_project_files_content.rb +2 -2
- data/lib/gorgon/version.rb +1 -1
- data/spec/listener_spec.rb +17 -21
- data/spec/originator_protocol_spec.rb +26 -9
- data/spec/originator_spec.rb +16 -5
- data/tutorial.md +8 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NTA0MWUwMjQ4ZmIzZmM0ZjAyYWJjOWYxMTA1NWQwZGFiZTQ0MzQ5Yg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MTA4NGNjZjU2OWQxOWQxMmZkYTlkOGNjYjI0MmIxYTI5ODMzZGEzYQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
N2U2ZWZlMWNkNDI2YTEyOTkwNGYwZjQwNGI0NWJmMGEzOGI0ZDg2YTA0MWYw
|
10
|
+
MDgxNGQzMTcwOTZmYmY3OTk4MzY3YTk3NDBmNjEyYjhlMjQwMGEwNDQ0Y2Jk
|
11
|
+
MGJhYTk3M2EyZWE2ZjU5MzZiNTAyNWNhOTdkZTYzYWU0YzViYzk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Mzg3NDc4MWZjNzE0MDYwMjZmNWMzMjE1MzNhY2JkYTU2MzBhZTQ0ZmYzNTdm
|
14
|
+
MjdkYTM3YTY5MWVlMjE2ZjRiZjMyYjdmMDNjZDZmMTQ4ODM5Njk3NGU0ZjIy
|
15
|
+
MmYyMDYxOTJkNGFiMzVjZjgxZWRlMDk3NzZhMzQ3M2E5NzEwNjk=
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gorgon (0.
|
4
|
+
gorgon (0.9.0)
|
5
5
|
amqp (~> 1.1.0)
|
6
6
|
awesome_print
|
7
7
|
colorize (~> 0.5.8)
|
@@ -17,10 +17,10 @@ GEM
|
|
17
17
|
amqp (1.1.8)
|
18
18
|
amq-protocol (>= 1.9.2)
|
19
19
|
eventmachine
|
20
|
-
awesome_print (1.
|
20
|
+
awesome_print (1.6.1)
|
21
21
|
colorize (0.5.8)
|
22
22
|
diff-lcs (1.1.3)
|
23
|
-
eventmachine (1.0.
|
23
|
+
eventmachine (1.0.7)
|
24
24
|
open4 (1.3.4)
|
25
25
|
rake (0.9.2.2)
|
26
26
|
rspec (2.11.0)
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Installing listener as a Daemon process (Ubuntu 9.10 or later)
|
|
21
21
|
Gotchas
|
22
22
|
----------------------------------------------------------------
|
23
23
|
|
24
|
-
* if you get `cannot load such file -- qrack/qrack (LoadError)`, just add `gem 'gorgon', '~> 0.8.
|
24
|
+
* if you get `cannot load such file -- qrack/qrack (LoadError)`, just add `gem 'gorgon', '~> 0.8.4' , :group => :remote_test` to your Gemfile, and run tests using `bundle exec gorgon`
|
25
25
|
* If `gorgon install_listener` didn't work for you, you can try [these steps](/daemon_with_upstart_and_rvm.md)
|
26
26
|
|
27
27
|
Also note that the steps in the tutorial are **not** meant to work on every project, they will only give you initial settings. You will probably have to modify the following files:
|
data/lib/gorgon/listener.rb
CHANGED
@@ -28,6 +28,7 @@ class Listener
|
|
28
28
|
log "Listener #{Gorgon::VERSION} initializing"
|
29
29
|
connect
|
30
30
|
initialize_personal_job_queue
|
31
|
+
announce_readiness_to_originators
|
31
32
|
end
|
32
33
|
|
33
34
|
def listen
|
@@ -44,11 +45,17 @@ class Listener
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def initialize_personal_job_queue
|
47
|
-
@job_queue = @bunny.queue("", :
|
48
|
-
exchange = @bunny.exchange(
|
48
|
+
@job_queue = @bunny.queue("job_queue_" + UUIDTools::UUID.timestamp_create.to_s, :auto_delete => true)
|
49
|
+
exchange = @bunny.exchange(job_exchange_name, :type => :fanout)
|
49
50
|
@job_queue.bind(exchange)
|
50
51
|
end
|
51
52
|
|
53
|
+
def announce_readiness_to_originators
|
54
|
+
exchange = @bunny.exchange(originator_exchange_name, :type => :fanout)
|
55
|
+
data = {:listener_queue_name => @job_queue.name}
|
56
|
+
exchange.publish(Yajl::Encoder.encode(data))
|
57
|
+
end
|
58
|
+
|
52
59
|
def poll
|
53
60
|
message = @job_queue.pop
|
54
61
|
return false if message == [nil, nil, nil]
|
@@ -175,8 +182,12 @@ class Listener
|
|
175
182
|
reply_exchange.publish(Yajl::Encoder.encode(message))
|
176
183
|
end
|
177
184
|
|
178
|
-
def
|
179
|
-
OriginatorProtocol.
|
185
|
+
def job_exchange_name
|
186
|
+
OriginatorProtocol.job_exchange_name(configuration.fetch(:cluster_id, nil))
|
187
|
+
end
|
188
|
+
|
189
|
+
def originator_exchange_name
|
190
|
+
OriginatorProtocol.originator_exchange_name(configuration.fetch(:cluster_id, nil))
|
180
191
|
end
|
181
192
|
|
182
193
|
def connection_information
|
data/lib/gorgon/originator.rb
CHANGED
@@ -69,6 +69,10 @@ class Originator
|
|
69
69
|
@protocol.receive_payloads do |payload|
|
70
70
|
handle_reply(payload)
|
71
71
|
end
|
72
|
+
|
73
|
+
@protocol.receive_new_listener_notifications do |payload|
|
74
|
+
handle_new_listener_notification(payload)
|
75
|
+
end
|
72
76
|
end
|
73
77
|
|
74
78
|
callback_handler.after_job_finishes
|
@@ -83,7 +87,7 @@ class Originator
|
|
83
87
|
create_job_state_and_observers
|
84
88
|
|
85
89
|
@logger.log "Publishing Job..."
|
86
|
-
@protocol.
|
90
|
+
@protocol.publish_job_to_all job_definition
|
87
91
|
@logger.log "Job Published"
|
88
92
|
end
|
89
93
|
|
@@ -135,6 +139,17 @@ class Originator
|
|
135
139
|
cleanup_if_job_complete
|
136
140
|
end
|
137
141
|
|
142
|
+
def handle_new_listener_notification(payload)
|
143
|
+
payload = Yajl::Parser.new(:symbolize_keys => true).parse(payload)
|
144
|
+
|
145
|
+
if payload[:listener_queue_name]
|
146
|
+
@protocol.publish_job_to_one(job_definition, payload[:listener_queue_name])
|
147
|
+
else
|
148
|
+
puts "Received unexpected payload on originator queue"
|
149
|
+
ap payload
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
138
153
|
def create_job_state_and_observers
|
139
154
|
@job_state = JobState.new files.count
|
140
155
|
RuntimeRecorder.new @job_state, configuration[:runtime_file]
|
@@ -6,11 +6,20 @@ require 'uuidtools'
|
|
6
6
|
|
7
7
|
class OriginatorProtocol
|
8
8
|
def initialize(logger, cluster_id=nil)
|
9
|
-
@
|
9
|
+
@originator_exchange_name = OriginatorProtocol.originator_exchange_name(cluster_id)
|
10
|
+
@job_exchange_name = OriginatorProtocol.job_exchange_name(cluster_id)
|
10
11
|
@logger = logger
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.
|
14
|
+
def self.originator_exchange_name(cluster_id)
|
15
|
+
if cluster_id
|
16
|
+
"gorgon.originators.#{cluster_id}"
|
17
|
+
else
|
18
|
+
"gorgon.originators"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.job_exchange_name(cluster_id)
|
14
23
|
if cluster_id
|
15
24
|
"gorgon.jobs.#{cluster_id}"
|
16
25
|
else
|
@@ -33,17 +42,29 @@ class OriginatorProtocol
|
|
33
42
|
end
|
34
43
|
end
|
35
44
|
|
36
|
-
def
|
45
|
+
def publish_job_to_all job_definition
|
46
|
+
job_definition = append_protocol_information_to_job_definition(job_definition)
|
47
|
+
@channel.fanout(@job_exchange_name).publish(job_definition.to_json)
|
48
|
+
end
|
49
|
+
|
50
|
+
def publish_job_to_one job_definition, listener_queue_name
|
51
|
+
job_definition = append_protocol_information_to_job_definition(job_definition)
|
52
|
+
@channel.default_exchange.publish(job_definition.to_json, :routing_key => listener_queue_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def append_protocol_information_to_job_definition job_definition
|
56
|
+
job_definition = job_definition.dup
|
57
|
+
|
37
58
|
job_definition.file_queue_name = @file_queue.name
|
38
59
|
job_definition.reply_exchange_name = @reply_exchange.name
|
39
60
|
|
40
|
-
|
61
|
+
return job_definition
|
41
62
|
end
|
42
63
|
|
43
64
|
def send_message_to_listeners type, body={}
|
44
65
|
# TODO: we probably want to use a different exchange for this type of messages
|
45
66
|
message = {:type => type, :reply_exchange_name => @reply_exchange.name, :body => body}
|
46
|
-
@channel.fanout(@
|
67
|
+
@channel.fanout(@job_exchange_name).publish(Yajl::Encoder.encode(message))
|
47
68
|
end
|
48
69
|
|
49
70
|
def receive_payloads
|
@@ -52,6 +73,12 @@ class OriginatorProtocol
|
|
52
73
|
end
|
53
74
|
end
|
54
75
|
|
76
|
+
def receive_new_listener_notifications
|
77
|
+
@originator_queue.subscribe do |payload|
|
78
|
+
yield payload
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
55
82
|
def cancel_job
|
56
83
|
@file_queue.purge if @file_queue
|
57
84
|
@channel.fanout("gorgon.worker_managers").publish(cancel_message) if @channel
|
@@ -69,12 +96,18 @@ class OriginatorProtocol
|
|
69
96
|
@reply_queue = @channel.queue("reply_queue_" + UUIDTools::UUID.timestamp_create.to_s, :auto_delete => true)
|
70
97
|
@reply_exchange = @channel.direct("reply_exchange_" + UUIDTools::UUID.timestamp_create.to_s, :auto_delete => true)
|
71
98
|
@reply_queue.bind(@reply_exchange)
|
99
|
+
|
100
|
+
# Provides a way for new listeners to announce their presence to originators that have already started the job
|
101
|
+
@originator_queue = @channel.queue("originator_queue_" + UUIDTools::UUID.timestamp_create.to_s, :auto_delete => true)
|
102
|
+
@originator_exchange = @channel.fanout(@originator_exchange_name)
|
103
|
+
@originator_queue.bind(@originator_exchange)
|
72
104
|
end
|
73
105
|
|
74
106
|
def cleanup_queues_and_exchange
|
75
107
|
@reply_queue.delete if @reply_queue
|
76
108
|
@file_queue.delete if @file_queue
|
77
109
|
@reply_exchange.delete if @reply_exchange
|
110
|
+
@originator_queue.delete if @originator_queue
|
78
111
|
end
|
79
112
|
|
80
113
|
def cancel_message
|
@@ -8,7 +8,7 @@ module Settings
|
|
8
8
|
@file_server_host = FilesContent.get_file_server_host
|
9
9
|
@sync_exclude = [".git", ".rvmrc","doc","log","tmp"]
|
10
10
|
@originator_log_file = 'log/gorgon-originator.log'
|
11
|
-
@failed_files = '
|
11
|
+
@failed_files = 'gorgon-failed-files.json'
|
12
12
|
create_callbacks
|
13
13
|
end
|
14
14
|
|
@@ -88,7 +88,7 @@ class GorgonCallbacks < Gorgon::DefaultCallbacks
|
|
88
88
|
load './Rakefile'
|
89
89
|
|
90
90
|
begin
|
91
|
-
if Rails.env
|
91
|
+
if Rails.env == 'remote_test'
|
92
92
|
Rake::Task['db:drop'].execute
|
93
93
|
end
|
94
94
|
rescue Exception => ex
|
data/lib/gorgon/version.rb
CHANGED
data/spec/listener_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'gorgon/listener'
|
|
2
2
|
|
3
3
|
describe Listener do
|
4
4
|
let(:connection_information) { double }
|
5
|
-
let(:queue) { double("GorgonBunny Queue", :bind => nil) }
|
5
|
+
let(:queue) { double("GorgonBunny Queue", :bind => nil, :name => "some supposedly unique string") }
|
6
6
|
let(:exchange) { double("GorgonBunny Exchange", :publish => nil) }
|
7
7
|
let(:bunny) { double("GorgonBunny", :start => nil, :queue => queue, :exchange => exchange) }
|
8
8
|
let(:logger) { double("Logger", :info => true, :datetime_format= => "")}
|
@@ -14,23 +14,6 @@ describe Listener do
|
|
14
14
|
Listener.any_instance.stub(:connection_information => connection_information)
|
15
15
|
end
|
16
16
|
|
17
|
-
describe "initialization" do
|
18
|
-
|
19
|
-
before do
|
20
|
-
Listener.any_instance.stub(:connect => nil, :initialize_personal_job_queue => nil)
|
21
|
-
end
|
22
|
-
|
23
|
-
it "connects" do
|
24
|
-
Listener.any_instance.should_receive(:connect)
|
25
|
-
Listener.new
|
26
|
-
end
|
27
|
-
|
28
|
-
it "initializes the personal job queue" do
|
29
|
-
Listener.any_instance.should_receive(:initialize_personal_job_queue)
|
30
|
-
Listener.new
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
17
|
describe "logging to a file" do
|
35
18
|
context "passing a log file path in the configuration" do
|
36
19
|
before do
|
@@ -84,23 +67,36 @@ describe Listener do
|
|
84
67
|
|
85
68
|
describe "#initialize_personal_job_queue" do
|
86
69
|
it "creates the job queue" do
|
87
|
-
|
70
|
+
UUIDTools::UUID.stub(:timestamp_create => "abcd1234")
|
71
|
+
|
72
|
+
bunny.should_receive(:queue).with("job_queue_abcd1234", :auto_delete => true)
|
88
73
|
listener.initialize_personal_job_queue
|
89
74
|
end
|
90
75
|
|
91
|
-
it "
|
76
|
+
it "builds job_exchange_name using cluster_id from configuration" do
|
92
77
|
Listener.any_instance.stub(:configuration).and_return(:cluster_id => 'cluster5')
|
93
78
|
bunny.should_receive(:exchange).with('gorgon.jobs.cluster5', anything).and_return(exchange)
|
94
79
|
listener.initialize_personal_job_queue
|
95
80
|
end
|
96
81
|
|
97
|
-
it "binds the exchange to the queue. Uses gorgon.jobs if there is no
|
82
|
+
it "binds the exchange to the queue. Uses gorgon.jobs if there is no job_exchange_name in configuration" do
|
98
83
|
bunny.should_receive(:exchange).with("gorgon.jobs", :type => :fanout).and_return(exchange)
|
99
84
|
queue.should_receive(:bind).with(exchange)
|
100
85
|
listener.initialize_personal_job_queue
|
101
86
|
end
|
102
87
|
end
|
103
88
|
|
89
|
+
describe "#announce_readiness_to_originators" do
|
90
|
+
it "publishes data to the originator exchange" do
|
91
|
+
originator_exchange = double
|
92
|
+
|
93
|
+
bunny.should_receive(:exchange).with("gorgon.originators", :type => :fanout).and_return(originator_exchange)
|
94
|
+
originator_exchange.should_receive(:publish).with({:listener_queue_name => "some supposedly unique string"}.to_json)
|
95
|
+
|
96
|
+
listener.announce_readiness_to_originators
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
104
100
|
describe "#poll" do
|
105
101
|
|
106
102
|
let(:empty_queue) { [nil, nil, nil] }
|
@@ -65,19 +65,19 @@ describe OriginatorProtocol do
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
describe "#
|
68
|
+
describe "#publish_job_to_all" do
|
69
69
|
before do
|
70
70
|
connect_and_publish_files(@originator_p)
|
71
71
|
end
|
72
72
|
|
73
|
-
it "
|
73
|
+
it "adds queue's names to job_definition and fanout using 'gorgon.jobs' exchange" do
|
74
74
|
channel.should_receive(:fanout).with("gorgon.jobs")
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
expected_job_definition = JobDefinition.new
|
76
|
+
expected_job_definition.file_queue_name = "queue"
|
77
|
+
expected_job_definition.reply_exchange_name = "exchange"
|
78
78
|
|
79
|
-
exchange.should_receive(:publish).with(
|
80
|
-
@originator_p.
|
79
|
+
exchange.should_receive(:publish).with(expected_job_definition.to_json)
|
80
|
+
@originator_p.publish_job_to_all JobDefinition.new
|
81
81
|
end
|
82
82
|
|
83
83
|
it "uses cluster_id in job_queue_name, when it is specified" do
|
@@ -85,7 +85,24 @@ describe OriginatorProtocol do
|
|
85
85
|
|
86
86
|
channel.should_receive(:fanout).with("gorgon.jobs.cluster1")
|
87
87
|
|
88
|
-
originator_p.
|
88
|
+
originator_p.publish_job_to_all JobDefinition.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#publish_job_to_one" do
|
93
|
+
before do
|
94
|
+
connect_and_publish_files(@originator_p)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "publishes the job to the specified listener queue" do
|
98
|
+
expected_listener_queue_name = "abcd1234"
|
99
|
+
expected_job_definition = JobDefinition.new
|
100
|
+
expected_job_definition.file_queue_name = "queue"
|
101
|
+
expected_job_definition.reply_exchange_name = "exchange"
|
102
|
+
|
103
|
+
exchange.should_receive(:publish).with(expected_job_definition.to_json, {:routing_key => expected_listener_queue_name})
|
104
|
+
|
105
|
+
@originator_p.publish_job_to_one(JobDefinition.new, expected_listener_queue_name)
|
89
106
|
end
|
90
107
|
end
|
91
108
|
|
@@ -147,7 +164,7 @@ describe OriginatorProtocol do
|
|
147
164
|
|
148
165
|
it "deletes reply_exchange and reply and file queues" do
|
149
166
|
@originator_p.publish_files []
|
150
|
-
queue.should_receive(:delete).
|
167
|
+
queue.should_receive(:delete).exactly(3).times
|
151
168
|
exchange.should_receive(:delete)
|
152
169
|
@originator_p.disconnect
|
153
170
|
end
|
data/spec/originator_spec.rb
CHANGED
@@ -2,16 +2,17 @@ require 'gorgon/originator'
|
|
2
2
|
|
3
3
|
describe Originator do
|
4
4
|
let(:protocol){ double("Originator Protocol", :connect => nil, :publish_files => nil,
|
5
|
-
|
6
|
-
|
5
|
+
:publish_job_to_all => nil, :publish_job_to_one => nil, :receive_payloads => nil, :cancel_job => nil,
|
6
|
+
:disconnect => nil, :receive_new_listener_notifications => nil)}
|
7
7
|
|
8
8
|
let(:configuration){ {:job => {}, :files => ["some/file"], :file_server => {:host => 'host-name'}}}
|
9
9
|
let(:job_state){ double("JobState", :is_job_complete? => false, :file_finished => nil,
|
10
|
-
|
10
|
+
:add_observer => nil)}
|
11
11
|
let(:progress_bar_view){ double("Progress Bar View", :show => nil)}
|
12
12
|
let(:originator_logger){ double("Originator Logger", :log => nil, :log_message => nil)}
|
13
13
|
let(:source_tree_syncer) { double("Source Tree Syncer", :push => nil, :exclude= => nil, :success? => true,
|
14
|
-
|
14
|
+
:sys_command => 'command')}
|
15
|
+
let(:job_definition){ JobDefinition.new }
|
15
16
|
|
16
17
|
before do
|
17
18
|
OriginatorLogger.stub(:new).and_return originator_logger
|
@@ -146,6 +147,16 @@ describe Originator do
|
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
150
|
+
describe "#handle_new_listener_notification" do
|
151
|
+
it "re-publishes the job definition directly to the queue specified by the notification" do
|
152
|
+
stub_methods
|
153
|
+
@originator.publish
|
154
|
+
|
155
|
+
protocol.should_receive(:publish_job_to_one).with(job_definition, 'abcd1234')
|
156
|
+
@originator.handle_new_listener_notification({:listener_queue_name => 'abcd1234'}.to_json)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
149
160
|
describe "#job_definition" do
|
150
161
|
it "returns a JobDefinition object" do
|
151
162
|
@originator.stub(:configuration).and_return configuration
|
@@ -182,7 +193,7 @@ describe Originator do
|
|
182
193
|
OriginatorProtocol.stub(:new).and_return protocol
|
183
194
|
@originator.stub(:configuration).and_return configuration
|
184
195
|
@originator.stub(:connection_information).and_return 'host'
|
185
|
-
@originator.stub(:job_definition).and_return
|
196
|
+
@originator.stub(:job_definition).and_return job_definition
|
186
197
|
end
|
187
198
|
|
188
199
|
def start_payload
|
data/tutorial.md
CHANGED
@@ -21,7 +21,14 @@ Run `rspec` and make sure all tests pass.
|
|
21
21
|
|
22
22
|
1. Install [RabbitMQ](https://www.rabbitmq.com/download.html).
|
23
23
|
|
24
|
-
|
24
|
+
If you are using Homebrew on OSX, run the following:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
brew install rabbitmq
|
28
|
+
/usr/local/sbin/rabbitmq-server # runs the server
|
29
|
+
```
|
30
|
+
|
31
|
+
2. Add Gorgon to the Gemfile: `gem 'gorgon', '0.8.4'`
|
25
32
|
|
26
33
|
3. `bundle`
|
27
34
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gorgon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Fitzsimmons
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2015-
|
15
|
+
date: 2015-05-04 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: rake
|