gorgon 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Gemfile.lock +1 -1
- data/README.md +6 -30
- data/bin/gorgon +1 -0
- data/gorgon-done-screenshot.png +0 -0
- data/gorgon.json.sample +11 -9
- data/lib/gorgon.rb +7 -0
- data/lib/gorgon/callback_handler.rb +18 -8
- data/lib/gorgon/default_callbacks.rb +25 -0
- data/lib/gorgon/job_definition.rb +9 -4
- data/lib/gorgon/listener.rb +15 -8
- data/lib/gorgon/originator.rb +45 -22
- data/lib/gorgon/originator_protocol.rb +12 -3
- data/lib/gorgon/settings/initial_files_creator.rb +4 -1
- data/lib/gorgon/source_tree_syncer.rb +25 -10
- data/lib/gorgon/version.rb +1 -1
- data/spec/callback_handler_spec.rb +21 -76
- data/spec/job_definition_spec.rb +7 -1
- data/spec/listener_spec.rb +11 -5
- data/spec/originator_protocol_spec.rb +15 -2
- data/spec/originator_spec.rb +30 -6
- data/spec/source_tree_syncer_spec.rb +6 -6
- data/spec/worker_manager_spec.rb +1 -1
- data/tutorial.md +97 -0
- metadata +14 -11
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
YzljZGEyNTE5ZTU3OWFiYjhiNTdhMDU2YTkyMzg1NjU4YjA4MzI1Zg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2697c5404498b1f1605746aa77f4812b3c106538
|
4
|
+
data.tar.gz: 8785ea9e49a5994a35e83f08b65106cfbc9369a4
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
YmQ3NGRjNzM1MTMwNTY2Zjg5YzhmZWYxNDJiMTEzZWYyNmQ1ZGMxYzI5ZTAx
|
11
|
-
NzkzMDA4N2U3MDc3NzJhYmMzNDk4NzE1YWEwNDNlNTZlZTBjMzM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NjZiMTAyYTIxZjRjYzcxNmRhOTM0NmYwNmY2ZjU3MmU5MzdjMGE4Mjc4ZWUw
|
14
|
-
MzQ1M2ZmZGQxYjM1ZjI1NzAyMGMyNmI1NjFhNDBhZDE1OGEyNmFlNDllZWE4
|
15
|
-
NTJjOTY0ODhiYWYyZWZhYjZkODljYjZhMzczZWM5MWEwNjVhODY=
|
6
|
+
metadata.gz: b407fe9b9ccff0e2f6eefe038dff6bae1b71ab323622621fa98dbff3b92bb5ac55188990fdd7182b01349e7f6c13136f2c6b3ec1bdd1a76ddf214d0bce362361
|
7
|
+
data.tar.gz: e2846f05b7d0cd1213261356973a756dc75ee507a9c24e80e67bcbf3e22687ab59c15e8dd0449a07dcc9b27c3a9e3a8d319d7874cb54a33650adf94afe9c6144
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -11,34 +11,20 @@ Gorgon provides a method for distributing the workload of running ruby test suit
|
|
11
11
|
|
12
12
|
Installing Gorgon
|
13
13
|
-----------------
|
14
|
-
|
15
|
-
1. cd to your project
|
16
|
-
1. `gem install gorgon`
|
17
|
-
1. if using rails, `gorgon init rails` will create initial files for a typical rails project. Otherwise, you can use `gorgon init`
|
18
|
-
1. check gorgon.json to see and modify any necessary setting
|
19
|
-
1. add the following lines to your _database.yml_ file
|
20
|
-
|
21
|
-
```yaml
|
22
|
-
remote_test: &remote_test
|
23
|
-
<<: *defaults
|
24
|
-
database: <my-app>_remote_test_<%=ENV['TEST_ENV_NUMBER']%>
|
25
|
-
min_messages: warning
|
26
|
-
```
|
27
|
-
|
28
|
-
Where `<<: *defaults` are the default values used in _database.yml_, like for example, adapter, username, password, and host. Replace `<my-app>` with a name to identify this application's dbs
|
14
|
+
This [tutorial](/tutorial.md) explains how to install gorgon in a sample app.
|
29
15
|
|
30
16
|
Installing listener as a Daemon process (Ubuntu 9.10 or later)
|
31
17
|
----------------------------------------------------------------
|
32
18
|
1. run `gorgon install_listener` from the directory where gorgon.json is
|
33
19
|
1. run `gorgon ping` to check if the listener is running
|
34
20
|
|
35
|
-
|
36
|
-
|
37
|
-
1. run `gorgon` to run all the tests.
|
21
|
+
Gotchas
|
22
|
+
----------------------------------------------------------------
|
38
23
|
|
39
|
-
|
24
|
+
* if you get `cannot load such file -- qrack/qrack (LoadError)`, just add `gem 'gorgon', '~> 0.4.1' , :group => :remote_test` to your Gemfile, and run tests using `bundle exec gorgon`
|
25
|
+
* If `gorgon install_listener` didn't work for you, you can try [these steps](/daemon_with_upstart_and_rvm.md)
|
40
26
|
|
41
|
-
Also note that
|
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:
|
42
28
|
* gorgon.json
|
43
29
|
* test/gorgon_callbacks/after\_sync.rb
|
44
30
|
* test/gorgon_callbacks/before\_creating\_workers.rb
|
@@ -49,13 +35,6 @@ Also note that these steps are **not** meant to work on every project, they will
|
|
49
35
|
|
50
36
|
If you modify ~/.gorgon/gorgon_listener.json, make sure you restart the listener by running `sudo restart gorgon`
|
51
37
|
|
52
|
-
Usage
|
53
|
-
---------------------
|
54
|
-
|
55
|
-
To queue the current test suite, run `gorgon start`, or `gorgon`. _gorgon_ will read the application configuration out of _gorgon.json_, connect to the AMQP server, and publish the job.
|
56
|
-
|
57
|
-
If you want to run the listener manually (didn't install Daemon process), you must run _gorgon job listeners_. To start a gorgon listener, run `gorgon listen`. This command will read the listener configuration out of _gorgon\_listener.json_, then start the listener process in the background.
|
58
|
-
|
59
38
|
Configuration
|
60
39
|
---------------------
|
61
40
|
|
@@ -80,9 +59,6 @@ This file contains the listener-specific settings, such as:
|
|
80
59
|
|
81
60
|
See [gorgon_listener.json example](/gorgon_listener.json.sample) for more details.
|
82
61
|
|
83
|
-
### Manually setting up gorgon listener as a daemon process (Ubuntu 9.10 or later)
|
84
|
-
If `gorgon install_listener` didn't work for you, you can try [these steps](/daemon_with_upstart_and_rvm.md)
|
85
|
-
|
86
62
|
Contributing
|
87
63
|
---------------------
|
88
64
|
Read overview [architecture](/architecture.md)
|
data/bin/gorgon
CHANGED
Binary file
|
data/gorgon.json.sample
CHANGED
@@ -8,18 +8,20 @@
|
|
8
8
|
},
|
9
9
|
|
10
10
|
"job": {
|
11
|
-
"
|
11
|
+
"sync": {
|
12
|
+
"exclude": [
|
13
|
+
".git",
|
14
|
+
".rvmrc",
|
12
15
|
"tmp",
|
13
16
|
"log",
|
14
|
-
"doc"
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
"doc"
|
18
|
+
],
|
19
|
+
|
20
|
+
"rsync_transport": "ssh" // or "anonymous"
|
21
|
+
},
|
22
|
+
|
18
23
|
"callbacks": {
|
19
|
-
"
|
20
|
-
"after_complete": "test/gorgon_callbacks/after_complete.rb",
|
21
|
-
"before_creating_workers": "test/gorgon_callbacks/before_creating_workers.rb",
|
22
|
-
"after_sync": "test/gorgon_callbacks/after_sync.rb"
|
24
|
+
"callbacks_class_file": "test/gorgon_callbacks.rb"
|
23
25
|
}
|
24
26
|
},
|
25
27
|
|
data/lib/gorgon.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
require "gorgon/version"
|
2
2
|
require "gorgon/originator"
|
3
3
|
require "gorgon/listener"
|
4
|
+
require "gorgon/default_callbacks"
|
4
5
|
|
5
6
|
module Gorgon
|
7
|
+
class << self
|
8
|
+
attr_accessor :callbacks
|
9
|
+
end
|
6
10
|
end
|
11
|
+
|
12
|
+
# defaults
|
13
|
+
Gorgon.callbacks = Gorgon::DefaultCallbacks.new
|
@@ -1,25 +1,35 @@
|
|
1
1
|
class CallbackHandler
|
2
2
|
def initialize(config)
|
3
3
|
@config = config || {}
|
4
|
+
load(@config[:callbacks_class_file]) if @config[:callbacks_class_file]
|
4
5
|
end
|
5
6
|
|
6
|
-
def
|
7
|
-
|
7
|
+
def before_originate
|
8
|
+
cluster_id = Gorgon.callbacks.before_originate
|
9
|
+
return cluster_id if cluster_id.is_a?(String)
|
8
10
|
end
|
9
11
|
|
10
|
-
def
|
11
|
-
|
12
|
+
def after_sync
|
13
|
+
Gorgon.callbacks.after_sync
|
12
14
|
end
|
13
15
|
|
14
16
|
def before_creating_workers
|
15
|
-
|
17
|
+
Gorgon.callbacks.before_creating_workers
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
+
def before_start
|
21
|
+
Gorgon.callbacks.before_start
|
20
22
|
end
|
21
23
|
|
22
24
|
def after_creating_workers
|
23
|
-
|
25
|
+
Gorgon.callbacks.after_creating_workers
|
26
|
+
end
|
27
|
+
|
28
|
+
def after_complete
|
29
|
+
Gorgon.callbacks.after_complete
|
30
|
+
end
|
31
|
+
|
32
|
+
def after_job_finishes
|
33
|
+
Gorgon.callbacks.after_job_finishes
|
24
34
|
end
|
25
35
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Gorgon
|
2
|
+
class DefaultCallbacks
|
3
|
+
# @return cluster id. Cluster id is used to identify the queue in RabbitMQ
|
4
|
+
def before_originate
|
5
|
+
end
|
6
|
+
|
7
|
+
def after_sync
|
8
|
+
end
|
9
|
+
|
10
|
+
def before_creating_workers
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_start
|
14
|
+
end
|
15
|
+
|
16
|
+
def after_creating_workers
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_complete
|
20
|
+
end
|
21
|
+
|
22
|
+
def after_job_finishes
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'yajl'
|
2
2
|
|
3
3
|
class JobDefinition
|
4
|
-
attr_accessor :file_queue_name, :reply_exchange_name, :
|
4
|
+
attr_accessor :file_queue_name, :reply_exchange_name, :sync, :callbacks
|
5
5
|
|
6
6
|
def initialize(opts={})
|
7
7
|
@file_queue_name = opts[:file_queue_name]
|
8
8
|
@reply_exchange_name = opts[:reply_exchange_name]
|
9
|
-
@source_tree_path = opts[:source_tree_path]
|
10
9
|
@callbacks = opts[:callbacks]
|
11
|
-
@
|
10
|
+
@sync = opts[:sync]
|
12
11
|
end
|
13
12
|
|
14
13
|
def to_json
|
@@ -19,6 +18,12 @@ class JobDefinition
|
|
19
18
|
|
20
19
|
#This can probably be done with introspection somehow, but this is way easier despite being very verbose
|
21
20
|
def to_hash
|
22
|
-
{
|
21
|
+
{
|
22
|
+
:type => "job_definition",
|
23
|
+
:file_queue_name => @file_queue_name,
|
24
|
+
:reply_exchange_name => @reply_exchange_name,
|
25
|
+
:sync => @sync,
|
26
|
+
:callbacks => @callbacks
|
27
|
+
}
|
23
28
|
end
|
24
29
|
end
|
data/lib/gorgon/listener.rb
CHANGED
@@ -7,6 +7,7 @@ require "gorgon/version"
|
|
7
7
|
require "gorgon/worker_manager"
|
8
8
|
require "gorgon/crash_reporter"
|
9
9
|
require "gorgon/gem_command_handler"
|
10
|
+
require 'gorgon/originator_protocol'
|
10
11
|
|
11
12
|
require "yajl"
|
12
13
|
require "gorgon_bunny/lib/gorgon_bunny"
|
@@ -44,7 +45,7 @@ class Listener
|
|
44
45
|
|
45
46
|
def initialize_personal_job_queue
|
46
47
|
@job_queue = @bunny.queue("", :exclusive => true)
|
47
|
-
exchange = @bunny.exchange(
|
48
|
+
exchange = @bunny.exchange(job_queue_name, :type => :fanout)
|
48
49
|
@job_queue.bind(exchange)
|
49
50
|
end
|
50
51
|
|
@@ -78,8 +79,7 @@ class Listener
|
|
78
79
|
@job_definition = JobDefinition.new(payload)
|
79
80
|
@reply_exchange = @bunny.exchange(@job_definition.reply_exchange_name, :auto_delete => true)
|
80
81
|
|
81
|
-
|
82
|
-
copy_source_tree(@job_definition.source_tree_path, @job_definition.sync_exclude)
|
82
|
+
copy_source_tree(@job_definition.sync)
|
83
83
|
|
84
84
|
if !@syncer.success? || !run_after_sync
|
85
85
|
clean_up
|
@@ -100,7 +100,7 @@ class Listener
|
|
100
100
|
def run_after_sync
|
101
101
|
log "Running after_sync callback..."
|
102
102
|
begin
|
103
|
-
|
103
|
+
callback_handler.after_sync
|
104
104
|
rescue Exception => e
|
105
105
|
log_error "Exception raised when running after_sync callback_handler. Please, check your script in #{@job_definition.callbacks[:after_sync]}:"
|
106
106
|
log_error e.message
|
@@ -117,10 +117,13 @@ class Listener
|
|
117
117
|
true
|
118
118
|
end
|
119
119
|
|
120
|
-
def
|
120
|
+
def callback_handler
|
121
|
+
@callback_handler ||= CallbackHandler.new(@job_definition.callbacks)
|
122
|
+
end
|
123
|
+
|
124
|
+
def copy_source_tree(sync_configuration)
|
121
125
|
log "Downloading source tree to temp directory..."
|
122
|
-
@syncer = SourceTreeSyncer.new
|
123
|
-
@syncer.exclude = exclude
|
126
|
+
@syncer = SourceTreeSyncer.new sync_configuration
|
124
127
|
@syncer.sync
|
125
128
|
if @syncer.success?
|
126
129
|
log "Command '#{@syncer.sys_command}' completed successfully."
|
@@ -145,7 +148,7 @@ class Listener
|
|
145
148
|
stdin.write(@job_definition.to_json)
|
146
149
|
stdin.close
|
147
150
|
|
148
|
-
|
151
|
+
_, status = Process.waitpid2 pid
|
149
152
|
log "Worker Manager #{pid} finished"
|
150
153
|
|
151
154
|
if status.exitstatus != 0
|
@@ -172,6 +175,10 @@ class Listener
|
|
172
175
|
reply_exchange.publish(Yajl::Encoder.encode(message))
|
173
176
|
end
|
174
177
|
|
178
|
+
def job_queue_name
|
179
|
+
OriginatorProtocol.job_queue_name(configuration.fetch(:cluster_id, nil))
|
180
|
+
end
|
181
|
+
|
175
182
|
def connection_information
|
176
183
|
configuration[:connection]
|
177
184
|
end
|
data/lib/gorgon/originator.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'gorgon'
|
1
2
|
require 'gorgon/originator_protocol'
|
2
3
|
require 'gorgon/configuration'
|
3
4
|
require 'gorgon/job_state'
|
@@ -5,7 +6,8 @@ require 'gorgon/progress_bar_view'
|
|
5
6
|
require 'gorgon/originator_logger'
|
6
7
|
require 'gorgon/failures_printer'
|
7
8
|
require 'gorgon/source_tree_syncer'
|
8
|
-
require 'gorgon/shutdown_manager
|
9
|
+
require 'gorgon/shutdown_manager'
|
10
|
+
require 'gorgon/callback_handler'
|
9
11
|
|
10
12
|
require 'awesome_print'
|
11
13
|
require 'etc'
|
@@ -37,8 +39,7 @@ class Originator
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def cancel_job
|
40
|
-
ShutdownManager.new(protocol: @protocol,
|
41
|
-
job_state: @job_state).cancel_job
|
42
|
+
ShutdownManager.new(protocol: @protocol, job_state: @job_state).cancel_job
|
42
43
|
end
|
43
44
|
|
44
45
|
def ctrl_c
|
@@ -54,31 +55,42 @@ class Originator
|
|
54
55
|
exit 2
|
55
56
|
end
|
56
57
|
|
58
|
+
cluster_id = callback_handler.before_originate
|
59
|
+
|
57
60
|
push_source_code
|
58
61
|
|
59
|
-
@protocol = OriginatorProtocol.new
|
62
|
+
@protocol = OriginatorProtocol.new(@logger, cluster_id)
|
60
63
|
|
61
64
|
EventMachine.run do
|
62
|
-
|
63
|
-
@protocol.connect connection_information, :on_closed => method(:on_disconnect)
|
64
|
-
|
65
|
-
@logger.log "Publishing files..."
|
66
|
-
@protocol.publish_files files
|
67
|
-
create_job_state_and_observers
|
68
|
-
|
69
|
-
@logger.log "Publishing Job..."
|
70
|
-
@protocol.publish_job job_definition
|
71
|
-
@logger.log "Job Published"
|
65
|
+
publish_files_and_job
|
72
66
|
|
73
67
|
@protocol.receive_payloads do |payload|
|
74
68
|
handle_reply(payload)
|
75
69
|
end
|
76
70
|
end
|
71
|
+
|
72
|
+
callback_handler.after_job_finishes
|
73
|
+
end
|
74
|
+
|
75
|
+
def publish_files_and_job
|
76
|
+
@logger.log "Connecting..."
|
77
|
+
@protocol.connect connection_information, :on_closed => method(:on_disconnect)
|
78
|
+
|
79
|
+
@logger.log "Publishing files..."
|
80
|
+
@protocol.publish_files files
|
81
|
+
create_job_state_and_observers
|
82
|
+
|
83
|
+
@logger.log "Publishing Job..."
|
84
|
+
@protocol.publish_job job_definition
|
85
|
+
@logger.log "Job Published"
|
86
|
+
end
|
87
|
+
|
88
|
+
def callback_handler
|
89
|
+
@callback_handler ||= CallbackHandler.new(configuration[:job][:callbacks])
|
77
90
|
end
|
78
91
|
|
79
92
|
def push_source_code
|
80
|
-
syncer = SourceTreeSyncer.new(
|
81
|
-
syncer.exclude = configuration[:job][:sync_exclude]
|
93
|
+
syncer = SourceTreeSyncer.new(sync_configuration)
|
82
94
|
syncer.push
|
83
95
|
if syncer.success?
|
84
96
|
@logger.log "Command '#{syncer.sys_command}' completed successfully."
|
@@ -125,7 +137,7 @@ class Originator
|
|
125
137
|
@job_state = JobState.new files.count
|
126
138
|
@progress_bar_view = ProgressBarView.new @job_state
|
127
139
|
@progress_bar_view.show
|
128
|
-
|
140
|
+
FailuresPrinter.new @job_state
|
129
141
|
end
|
130
142
|
|
131
143
|
def on_disconnect
|
@@ -143,20 +155,31 @@ class Originator
|
|
143
155
|
end
|
144
156
|
|
145
157
|
def job_definition
|
158
|
+
# TODO: remove duplication. Use sync_configuration
|
146
159
|
job_config = configuration[:job]
|
147
|
-
|
148
|
-
|
149
|
-
end
|
160
|
+
job_config[:sync] = {} unless job_config.has_key?(:sync)
|
161
|
+
job_config[:sync][:source_tree_path] = source_tree_path(job_config[:sync])
|
150
162
|
JobDefinition.new(configuration[:job])
|
151
163
|
end
|
152
164
|
|
153
165
|
private
|
154
166
|
|
155
|
-
def
|
167
|
+
def sync_configuration
|
168
|
+
configuration[:job].
|
169
|
+
fetch(:sync, {}).
|
170
|
+
merge(source_tree_path: source_tree_path(configuration[:job][:sync])
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def source_tree_path(sync_config)
|
156
175
|
hostname = Socket.gethostname
|
157
176
|
source_code_root = File.basename(Dir.pwd)
|
158
177
|
|
159
|
-
|
178
|
+
if sync_config && sync_config[:rsync_transport] == SourceTreeSyncer::RSYNC_TRANSPORT_SSH
|
179
|
+
"#{file_server_host}:#{hostname}_#{source_code_root}"
|
180
|
+
else
|
181
|
+
"rsync://#{file_server_host}:43434/src/#{hostname}_#{source_code_root}"
|
182
|
+
end
|
160
183
|
end
|
161
184
|
|
162
185
|
def file_server_host
|
@@ -5,10 +5,19 @@ require 'amqp'
|
|
5
5
|
require 'uuidtools'
|
6
6
|
|
7
7
|
class OriginatorProtocol
|
8
|
-
def initialize
|
8
|
+
def initialize(logger, cluster_id=nil)
|
9
|
+
@job_queue_name = OriginatorProtocol.job_queue_name(cluster_id)
|
9
10
|
@logger = logger
|
10
11
|
end
|
11
12
|
|
13
|
+
def self.job_queue_name(cluster_id)
|
14
|
+
if cluster_id
|
15
|
+
"gorgon.jobs.#{cluster_id}"
|
16
|
+
else
|
17
|
+
'gorgon.jobs'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
12
21
|
def connect connection_information, options={}
|
13
22
|
@connection = AMQP.connect(connection_information)
|
14
23
|
@channel = AMQP::Channel.new(@connection)
|
@@ -28,13 +37,13 @@ class OriginatorProtocol
|
|
28
37
|
job_definition.file_queue_name = @file_queue.name
|
29
38
|
job_definition.reply_exchange_name = @reply_exchange.name
|
30
39
|
|
31
|
-
@channel.fanout(
|
40
|
+
@channel.fanout(@job_queue_name).publish(job_definition.to_json)
|
32
41
|
end
|
33
42
|
|
34
43
|
def send_message_to_listeners type, body={}
|
35
44
|
# TODO: we probably want to use a different exchange for this type of messages
|
36
45
|
message = {:type => type, :reply_exchange_name => @reply_exchange.name, :body => body}
|
37
|
-
@channel.fanout(
|
46
|
+
@channel.fanout(@job_queue_name).publish(Yajl::Encoder.encode(message))
|
38
47
|
end
|
39
48
|
|
40
49
|
def receive_payloads
|
@@ -33,7 +33,10 @@ module Settings
|
|
33
33
|
connection: {host: content.amqp_host},
|
34
34
|
file_server: {host: content.file_server_host},
|
35
35
|
job: {
|
36
|
-
|
36
|
+
sync: {
|
37
|
+
exclude: content.sync_exclude
|
38
|
+
# TODO: add rsync_transport config
|
39
|
+
}
|
37
40
|
},
|
38
41
|
files: content.files
|
39
42
|
}
|
@@ -1,25 +1,32 @@
|
|
1
1
|
require 'open4'
|
2
2
|
|
3
3
|
class SourceTreeSyncer
|
4
|
-
|
4
|
+
RSYNC_TRANSPORT_SSH = 'ssh'
|
5
|
+
RSYNC_TRANSPORT_ANONYMOUS = 'anonymous'
|
6
|
+
|
5
7
|
attr_reader :sys_command, :output, :errors
|
6
8
|
|
7
9
|
SYS_COMMAND = 'rsync'
|
8
|
-
OPTS =
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
OPTS = '-azr --timeout=5 --delete'
|
11
|
+
RSH_OPTS = 'ssh -o NumberOfPasswordPrompts=0 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i gorgon.pem'
|
12
|
+
EXCLUDE_OPT = '--exclude'
|
13
|
+
|
14
|
+
def initialize(sync_config)
|
15
|
+
if sync_config
|
16
|
+
@source_tree_path = sync_config[:source_tree_path]
|
17
|
+
@exclude = sync_config[:exclude]
|
18
|
+
@rsync_transport = sync_config[:rsync_transport]
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
22
|
+
# TODO: rename sync to pull
|
16
23
|
def sync
|
17
24
|
return if blank_source_tree_path?
|
18
25
|
|
19
26
|
@tempdir = Dir.mktmpdir("gorgon")
|
20
27
|
Dir.chdir(@tempdir)
|
21
28
|
|
22
|
-
@sys_command = "#{SYS_COMMAND} #{
|
29
|
+
@sys_command = "#{SYS_COMMAND} #{rsync_options} #{@source_tree_path}/ ."
|
23
30
|
|
24
31
|
execute_command
|
25
32
|
end
|
@@ -27,7 +34,7 @@ class SourceTreeSyncer
|
|
27
34
|
def push
|
28
35
|
return if blank_source_tree_path?
|
29
36
|
|
30
|
-
@sys_command = "#{SYS_COMMAND} #{
|
37
|
+
@sys_command = "#{SYS_COMMAND} #{rsync_options} . #{@source_tree_path}"
|
31
38
|
|
32
39
|
execute_command
|
33
40
|
end
|
@@ -68,7 +75,15 @@ class SourceTreeSyncer
|
|
68
75
|
end
|
69
76
|
end
|
70
77
|
|
71
|
-
def
|
78
|
+
def rsync_options
|
79
|
+
if @rsync_transport == RSYNC_TRANSPORT_SSH
|
80
|
+
"#{OPTS} #{exclude_options} --rsh='#{RSH_OPTS}'"
|
81
|
+
else
|
82
|
+
"#{OPTS} #{exclude_options}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def exclude_options
|
72
87
|
return "" if @exclude.nil? or @exclude.empty?
|
73
88
|
|
74
89
|
exclude = [""] + @exclude
|
data/lib/gorgon/version.rb
CHANGED
@@ -1,94 +1,39 @@
|
|
1
|
-
require 'gorgon
|
1
|
+
require 'gorgon'
|
2
2
|
|
3
3
|
describe CallbackHandler do
|
4
4
|
|
5
5
|
let(:config) {
|
6
6
|
{
|
7
|
-
:
|
8
|
-
:after_complete => "some/other/file.rb",
|
9
|
-
:before_creating_workers => "callbacks/before_creating_workers_file.rb",
|
10
|
-
:after_sync => "callbacks/after_sync_file.rb",
|
11
|
-
:after_creating_workers => "callbacks/after_creating_workers.rb"
|
7
|
+
:callbacks_class_file => "callback_file.rb"
|
12
8
|
}
|
13
9
|
}
|
14
|
-
|
15
|
-
|
16
|
-
handler = CallbackHandler.new(config)
|
17
|
-
|
18
|
-
handler.should_receive(:load).with("some/file.rb")
|
19
|
-
|
20
|
-
handler.before_start
|
21
|
-
end
|
22
|
-
|
23
|
-
it "does not attempt to load the before start script when before_start is not defined" do
|
24
|
-
handler = CallbackHandler.new({})
|
25
|
-
|
26
|
-
handler.should_not_receive(:load)
|
27
|
-
|
28
|
-
handler.before_start
|
29
|
-
end
|
30
|
-
|
31
|
-
it "calls after hook" do
|
32
|
-
handler = CallbackHandler.new(config)
|
33
|
-
|
34
|
-
handler.should_receive(:load).with("some/other/file.rb")
|
35
|
-
|
36
|
-
handler.after_complete
|
37
|
-
end
|
38
|
-
|
39
|
-
it "does not attempt to load the after complete script when before_start is not defined" do
|
40
|
-
handler = CallbackHandler.new({})
|
41
|
-
|
42
|
-
handler.should_not_receive(:load)
|
43
|
-
|
44
|
-
handler.after_complete
|
10
|
+
before do
|
11
|
+
CallbackHandler.any_instance.stub(:load)
|
45
12
|
end
|
46
13
|
|
47
|
-
it "
|
48
|
-
|
14
|
+
it "loads callback file" do
|
15
|
+
CallbackHandler.any_instance.should_receive(:load).with config[:callbacks_class_file]
|
49
16
|
|
50
|
-
|
51
|
-
|
52
|
-
handler.before_creating_workers
|
53
|
-
end
|
54
|
-
|
55
|
-
it "does not attempt to load the before creating workers script when before_creating_workers is not defined" do
|
56
|
-
handler = CallbackHandler.new({})
|
57
|
-
|
58
|
-
handler.should_not_receive(:load)
|
59
|
-
|
60
|
-
handler.before_creating_workers
|
17
|
+
CallbackHandler.new(config)
|
61
18
|
end
|
62
19
|
|
63
|
-
it "
|
64
|
-
|
65
|
-
|
66
|
-
handler.should_receive(:load).with("callbacks/after_sync_file.rb")
|
20
|
+
it "does not load callback file if it's not specified" do
|
21
|
+
CallbackHandler.any_instance.should_not_receive(:load)
|
67
22
|
|
68
|
-
|
23
|
+
CallbackHandler.new({})
|
69
24
|
end
|
70
25
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
it "calls the after creating workers hook" do
|
80
|
-
handler = CallbackHandler.new(config)
|
81
|
-
|
82
|
-
handler.should_receive(:load).with("callbacks/after_creating_workers.rb")
|
83
|
-
|
84
|
-
handler.after_creating_workers
|
85
|
-
end
|
86
|
-
|
87
|
-
it "does not attempt to load the after creating workers hook when after_creating_workers is not defined" do
|
88
|
-
handler = CallbackHandler.new({})
|
89
|
-
|
90
|
-
handler.should_not_receive(:load)
|
26
|
+
context "#before_originate" do
|
27
|
+
it "returns value from callbacks#before_originate if it's a string" do
|
28
|
+
handler = CallbackHandler.new(config)
|
29
|
+
Gorgon.callbacks.stub(:before_originate).and_return('my_job_id')
|
30
|
+
expect(handler.before_originate).to eq('my_job_id')
|
31
|
+
end
|
91
32
|
|
92
|
-
|
33
|
+
it "returns nil if callbacks#before_originate did not return a string" do
|
34
|
+
handler = CallbackHandler.new(config)
|
35
|
+
Gorgon.callbacks.stub(:before_originate).and_return(Object.new)
|
36
|
+
expect(handler.before_originate).to eq(nil)
|
37
|
+
end
|
93
38
|
end
|
94
39
|
end
|
data/spec/job_definition_spec.rb
CHANGED
@@ -8,7 +8,13 @@ describe JobDefinition do
|
|
8
8
|
|
9
9
|
describe "#to_json" do
|
10
10
|
it "should serialize itself to json" do
|
11
|
-
expected_hash = {
|
11
|
+
expected_hash = {
|
12
|
+
:type => "job_definition",
|
13
|
+
:file_queue_name => "string 1",
|
14
|
+
:reply_exchange_name => "string 2",
|
15
|
+
:sync => {:exclude => "string 4"},
|
16
|
+
:callbacks => {}
|
17
|
+
}
|
12
18
|
|
13
19
|
jd = JobDefinition.new(expected_hash)
|
14
20
|
|
data/spec/listener_spec.rb
CHANGED
@@ -88,7 +88,13 @@ describe Listener do
|
|
88
88
|
listener.initialize_personal_job_queue
|
89
89
|
end
|
90
90
|
|
91
|
-
it "
|
91
|
+
it "build job_queue_name using job_id from configuration" do
|
92
|
+
Listener.any_instance.stub(:configuration).and_return(:cluster_id => 'cluster5')
|
93
|
+
bunny.should_receive(:exchange).with('gorgon.jobs.cluster5', anything).and_return(exchange)
|
94
|
+
listener.initialize_personal_job_queue
|
95
|
+
end
|
96
|
+
|
97
|
+
it "binds the exchange to the queue. Uses gorgon.jobs if there is no job_queue_name in configuration" do
|
92
98
|
bunny.should_receive(:exchange).with("gorgon.jobs", :type => :fanout).and_return(exchange)
|
93
99
|
queue.should_receive(:bind).with(exchange)
|
94
100
|
listener.initialize_personal_job_queue
|
@@ -177,8 +183,7 @@ describe Listener do
|
|
177
183
|
|
178
184
|
describe "#run_job" do
|
179
185
|
let(:payload) {{
|
180
|
-
:source_tree_path => "path/to/source",
|
181
|
-
:sync_exclude => ["log"], :callbacks => {:a_callback => "path/to/callback"}
|
186
|
+
:sync => {:source_tree_path => "path/to/source", :exclude => ["log"]}, :callbacks => {:a_callback => "path/to/callback"}
|
182
187
|
}}
|
183
188
|
|
184
189
|
let(:syncer) { double("SourceTreeSyncer", :sync => nil, :exclude= => nil, :success? => true,
|
@@ -196,8 +201,9 @@ describe Listener do
|
|
196
201
|
end
|
197
202
|
|
198
203
|
it "copy source tree" do
|
199
|
-
SourceTreeSyncer.should_receive(:new).once.
|
200
|
-
|
204
|
+
SourceTreeSyncer.should_receive(:new).once.
|
205
|
+
with(source_tree_path: "path/to/source", exclude: ["log"]).
|
206
|
+
and_return(syncer)
|
201
207
|
syncer.should_receive(:sync)
|
202
208
|
syncer.should_receive(:success?).and_return(true)
|
203
209
|
@listener.run_job(payload)
|
@@ -67,8 +67,7 @@ describe OriginatorProtocol do
|
|
67
67
|
|
68
68
|
describe "#publish_job" do
|
69
69
|
before do
|
70
|
-
@originator_p
|
71
|
-
@originator_p.publish_files []
|
70
|
+
connect_and_publish_files(@originator_p)
|
72
71
|
end
|
73
72
|
|
74
73
|
it "add queue's names to job_definition and fanout using 'gorgon.jobs' exchange" do
|
@@ -80,6 +79,14 @@ describe OriginatorProtocol do
|
|
80
79
|
exchange.should_receive(:publish).with(exp_job_definition.to_json)
|
81
80
|
@originator_p.publish_job JobDefinition.new
|
82
81
|
end
|
82
|
+
|
83
|
+
it "uses cluster_id in job_queue_name, when it is specified" do
|
84
|
+
originator_p = connect_and_publish_files(OriginatorProtocol.new(logger, "cluster1"))
|
85
|
+
|
86
|
+
channel.should_receive(:fanout).with("gorgon.jobs.cluster1")
|
87
|
+
|
88
|
+
originator_p.publish_job JobDefinition.new
|
89
|
+
end
|
83
90
|
end
|
84
91
|
|
85
92
|
describe "#send_message_to_listeners" do
|
@@ -150,4 +157,10 @@ describe OriginatorProtocol do
|
|
150
157
|
@originator_p.disconnect
|
151
158
|
end
|
152
159
|
end
|
160
|
+
|
161
|
+
def connect_and_publish_files(originator_p)
|
162
|
+
originator_p.connect @conn_information
|
163
|
+
originator_p.publish_files []
|
164
|
+
originator_p
|
165
|
+
end
|
153
166
|
end
|
data/spec/originator_spec.rb
CHANGED
@@ -20,7 +20,7 @@ describe Originator do
|
|
20
20
|
@originator = Originator.new
|
21
21
|
end
|
22
22
|
|
23
|
-
describe "#
|
23
|
+
describe "#publish" do
|
24
24
|
before do
|
25
25
|
stub_methods
|
26
26
|
end
|
@@ -55,6 +55,24 @@ describe Originator do
|
|
55
55
|
|
56
56
|
expect { @originator.publish }.to raise_error(SystemExit)
|
57
57
|
end
|
58
|
+
|
59
|
+
it "calls before_originate callback" do
|
60
|
+
CallbackHandler.any_instance.should_receive(:before_originate)
|
61
|
+
@originator.publish
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses results of before_originate callback to build a job_queue_name" do
|
65
|
+
CallbackHandler.any_instance.stub(:before_originate).and_return('job_1')
|
66
|
+
OriginatorProtocol.should_receive(:new).with(anything, 'job_1')
|
67
|
+
|
68
|
+
@originator.publish
|
69
|
+
end
|
70
|
+
|
71
|
+
it "calls after_job_finishes callback" do
|
72
|
+
CallbackHandler.any_instance.should_receive(:after_job_finishes)
|
73
|
+
|
74
|
+
@originator.publish
|
75
|
+
end
|
58
76
|
end
|
59
77
|
|
60
78
|
describe "#cancel_job" do
|
@@ -136,17 +154,23 @@ describe Originator do
|
|
136
154
|
@originator.job_definition.should equal job_definition
|
137
155
|
end
|
138
156
|
|
139
|
-
it "builds source_tree_path if it was not specified in the configuration" do
|
157
|
+
it "builds anonymous source_tree_path if it was not specified in the configuration" do
|
140
158
|
@originator.stub(:configuration).and_return(configuration.merge(:file_server => {:host => 'host-name'}))
|
141
159
|
Socket.stub(:gethostname => 'my-host')
|
142
160
|
Dir.stub(:pwd => 'dir')
|
143
161
|
|
144
|
-
@originator.job_definition.source_tree_path.should == "rsync://host-name:43434/src/my-host_dir"
|
162
|
+
@originator.job_definition.sync[:source_tree_path].should == "rsync://host-name:43434/src/my-host_dir"
|
145
163
|
end
|
146
164
|
|
147
|
-
it "
|
148
|
-
@originator.stub(:configuration).and_return(
|
149
|
-
|
165
|
+
it "builds ssh source_tree_path if using ssh rsync transport" do
|
166
|
+
@originator.stub(:configuration).and_return(configuration.merge(
|
167
|
+
:file_server => {:host => 'host-name'},
|
168
|
+
:job => { :sync => { :rsync_transport => 'ssh'}}
|
169
|
+
))
|
170
|
+
Socket.stub(:gethostname => 'my-host')
|
171
|
+
Dir.stub(:pwd => 'dir')
|
172
|
+
|
173
|
+
@originator.job_definition.sync[:source_tree_path].should == "host-name:my-host_dir"
|
150
174
|
end
|
151
175
|
end
|
152
176
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'gorgon/source_tree_syncer'
|
2
2
|
|
3
|
-
describe SourceTreeSyncer.new("") do
|
4
|
-
it { should respond_to :exclude= }
|
3
|
+
describe SourceTreeSyncer.new(source_tree_path: "") do
|
5
4
|
it { should respond_to :sync }
|
6
5
|
it { should respond_to :push }
|
7
6
|
it { should respond_to :sys_command }
|
@@ -16,7 +15,7 @@ describe SourceTreeSyncer.new("") do
|
|
16
15
|
let(:status) { double("Process Status", :exitstatus => 0)}
|
17
16
|
|
18
17
|
before do
|
19
|
-
@syncer = SourceTreeSyncer.new "path/to/source"
|
18
|
+
@syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source")
|
20
19
|
stub_utilities_methods
|
21
20
|
end
|
22
21
|
|
@@ -25,11 +24,12 @@ describe SourceTreeSyncer.new("") do
|
|
25
24
|
Dir.should_receive(:mktmpdir).and_return("tmp/dir")
|
26
25
|
Dir.should_receive(:chdir).with("tmp/dir")
|
27
26
|
@syncer.sync
|
27
|
+
expect(@syncer.success?).to be_true, "Syncer error: #{@syncer.errors}"
|
28
28
|
end
|
29
29
|
|
30
30
|
context "invalid source_tree_path" do
|
31
31
|
it "gives error if source_tree_path is empty string" do
|
32
|
-
syncer = SourceTreeSyncer.new " "
|
32
|
+
syncer = SourceTreeSyncer.new(source_tree_path: " ")
|
33
33
|
Dir.should_not_receive(:mktmpdir)
|
34
34
|
syncer.sync
|
35
35
|
syncer.success?.should be_false
|
@@ -53,7 +53,7 @@ describe SourceTreeSyncer.new("") do
|
|
53
53
|
end
|
54
54
|
|
55
55
|
it "exclude files when they are specified" do
|
56
|
-
@syncer.exclude
|
56
|
+
@syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source", exclude: ["log", ".git"])
|
57
57
|
Open4.should_receive(:popen4).with(/--exclude log --exclude .git/)
|
58
58
|
@syncer.sync
|
59
59
|
end
|
@@ -98,7 +98,7 @@ describe SourceTreeSyncer.new("") do
|
|
98
98
|
|
99
99
|
describe "#remove_temp_dir" do
|
100
100
|
before do
|
101
|
-
@syncer = SourceTreeSyncer.new "path/to/source"
|
101
|
+
@syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source")
|
102
102
|
stub_utilities_methods
|
103
103
|
@syncer.sync
|
104
104
|
end
|
data/spec/worker_manager_spec.rb
CHANGED
@@ -20,7 +20,7 @@ describe WorkerManager do
|
|
20
20
|
describe ".build" do
|
21
21
|
it "should load_configuration_from_file" do
|
22
22
|
STDIN.should_receive(:read).and_return '{"source_tree_path":"path/to/source",
|
23
|
-
"
|
23
|
+
"sync":{"exclude":["log"]}}'
|
24
24
|
|
25
25
|
Configuration.should_receive(:load_configuration_from_file).with("file.json").and_return({})
|
26
26
|
|
data/tutorial.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Gorgon Setup Tutorial
|
2
|
+
|
3
|
+
This is an example on how to setup [Gorgon](https://github.com/Fitzsimmons/Gorgon). For this tutorial, we will use a version of a Rails app from [Ruby on Rails Tutorial](https://www.railstutorial.org/) by Michael Hartl.
|
4
|
+
|
5
|
+
### Setup the sample Rails app
|
6
|
+
|
7
|
+
First, let's clone and setup the sample Rails app.
|
8
|
+
|
9
|
+
```bash
|
10
|
+
git clone git@github.com:arturopie/sample_app-1.git
|
11
|
+
cd sample_app-1/
|
12
|
+
bundle
|
13
|
+
rake db:setup
|
14
|
+
rake db:test:prepare
|
15
|
+
```
|
16
|
+
|
17
|
+
Run `rspec` and make sure all tests pass.
|
18
|
+
|
19
|
+
|
20
|
+
### Setup Gorgon
|
21
|
+
|
22
|
+
1. Install [RabbitMQ](https://www.rabbitmq.com/download.html).
|
23
|
+
|
24
|
+
2. Add Gorgon to the Gemfile: `gem 'gorgon', '0.7.1'`
|
25
|
+
|
26
|
+
3. `bundle`
|
27
|
+
|
28
|
+
4. Run `gorgon init rails` to create initial files for a typical Rails project. Gorgon will ask you to enter the AMQP host name and the File Server host name. Just press enter to use the default host (localhost).
|
29
|
+
|
30
|
+
Gorgon does not use Rails environment 'test' to run tests. Instead, one of the callbacks generated by `gorgon init rails` will set Rails environment to 'remote_test' before running any test. We need to setup this new environment in our Rails app.
|
31
|
+
|
32
|
+
5. Add the following lines to config/database.yml. The callbacks will set TEST_ENV_NUMBER to its process id so that no two Gorgon runners use the same database.
|
33
|
+
|
34
|
+
```yml
|
35
|
+
remote_test: &remote_test
|
36
|
+
adapter: sqlite3
|
37
|
+
database: db/remote_test_<%=ENV['TEST_ENV_NUMBER']%>
|
38
|
+
pool: 5
|
39
|
+
timeout: 5000
|
40
|
+
```
|
41
|
+
|
42
|
+
6. In the Gemfile, add :remote_test to every :test group. For example:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
...
|
46
|
+
group :development, :test, :remote_test do
|
47
|
+
gem 'sqlite3', '1.3.5'
|
48
|
+
...
|
49
|
+
group :test, :remote_test do
|
50
|
+
gem 'capybara', '1.1.2'
|
51
|
+
...
|
52
|
+
```
|
53
|
+
|
54
|
+
|
55
|
+
7. Create config/environments/remote_test.rb file. This file can be just a copy of config/environments/test.rb
|
56
|
+
|
57
|
+
```bash
|
58
|
+
cp config/environments/test.rb config/environments/remote_test.rb
|
59
|
+
```
|
60
|
+
|
61
|
+
### Run Gorgon Listener
|
62
|
+
|
63
|
+
1. Download a listener configuration sample:
|
64
|
+
|
65
|
+
```bash
|
66
|
+
wget https://raw.githubusercontent.com/Fitzsimmons/Gorgon/master/gorgon_listener.json.sample
|
67
|
+
mv gorgon_listener.json.sample gorgon_listener.json
|
68
|
+
```
|
69
|
+
|
70
|
+
2. Start listener
|
71
|
+
|
72
|
+
```bash
|
73
|
+
gorgon listen
|
74
|
+
```
|
75
|
+
|
76
|
+
### Run File Server
|
77
|
+
|
78
|
+
```bash
|
79
|
+
mkdir -p ~/.gorgon/file_dir # here is where gorgon will push files under test
|
80
|
+
gorgon start_rsync ~/.gorgon/file_dir
|
81
|
+
```
|
82
|
+
|
83
|
+
### Run Gorgon
|
84
|
+
|
85
|
+
Now, you are ready to run Gorgon.
|
86
|
+
|
87
|
+
```bash
|
88
|
+
gorgon
|
89
|
+
```
|
90
|
+
|
91
|
+
### Result
|
92
|
+
|
93
|
+
If everything went well, this is what you should see:
|
94
|
+
|
95
|
+
![image](/gorgon-done-screenshot.png)
|
96
|
+
|
97
|
+
|
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.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Fitzsimmons
|
@@ -12,34 +12,34 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2014-
|
15
|
+
date: 2014-11-14 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: rake
|
19
19
|
requirement: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - '>='
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: '0'
|
24
24
|
type: :development
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
|
-
- -
|
28
|
+
- - '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: '0'
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: test-unit
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
34
34
|
requirements:
|
35
|
-
- -
|
35
|
+
- - '>='
|
36
36
|
- !ruby/object:Gem::Version
|
37
37
|
version: '0'
|
38
38
|
type: :development
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
|
-
- -
|
42
|
+
- - '>='
|
43
43
|
- !ruby/object:Gem::Version
|
44
44
|
version: '0'
|
45
45
|
- !ruby/object:Gem::Dependency
|
@@ -74,14 +74,14 @@ dependencies:
|
|
74
74
|
name: awesome_print
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
|
-
- -
|
77
|
+
- - '>='
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0'
|
80
80
|
type: :runtime
|
81
81
|
prerelease: false
|
82
82
|
version_requirements: !ruby/object:Gem::Requirement
|
83
83
|
requirements:
|
84
|
-
- -
|
84
|
+
- - '>='
|
85
85
|
- !ruby/object:Gem::Version
|
86
86
|
version: '0'
|
87
87
|
- !ruby/object:Gem::Dependency
|
@@ -173,6 +173,7 @@ files:
|
|
173
173
|
- architecture.md
|
174
174
|
- bin/gorgon
|
175
175
|
- daemon_with_upstart_and_rvm.md
|
176
|
+
- gorgon-done-screenshot.png
|
176
177
|
- gorgon.gemspec
|
177
178
|
- gorgon.json.sample
|
178
179
|
- gorgon_listener.json.sample
|
@@ -182,6 +183,7 @@ files:
|
|
182
183
|
- lib/gorgon/colors.rb
|
183
184
|
- lib/gorgon/configuration.rb
|
184
185
|
- lib/gorgon/crash_reporter.rb
|
186
|
+
- lib/gorgon/default_callbacks.rb
|
185
187
|
- lib/gorgon/failures_printer.rb
|
186
188
|
- lib/gorgon/g_logger.rb
|
187
189
|
- lib/gorgon/gem_command_handler.rb
|
@@ -346,6 +348,7 @@ files:
|
|
346
348
|
- spec/unknown_runner_spec.rb
|
347
349
|
- spec/worker_manager_spec.rb
|
348
350
|
- spec/worker_spec.rb
|
351
|
+
- tutorial.md
|
349
352
|
homepage: ''
|
350
353
|
licenses: []
|
351
354
|
metadata: {}
|
@@ -355,17 +358,17 @@ require_paths:
|
|
355
358
|
- lib
|
356
359
|
required_ruby_version: !ruby/object:Gem::Requirement
|
357
360
|
requirements:
|
358
|
-
- -
|
361
|
+
- - '>='
|
359
362
|
- !ruby/object:Gem::Version
|
360
363
|
version: '0'
|
361
364
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
362
365
|
requirements:
|
363
|
-
- -
|
366
|
+
- - '>='
|
364
367
|
- !ruby/object:Gem::Version
|
365
368
|
version: '0'
|
366
369
|
requirements: []
|
367
370
|
rubyforge_project: gorgon
|
368
|
-
rubygems_version: 2.
|
371
|
+
rubygems_version: 2.0.14
|
369
372
|
signing_key:
|
370
373
|
specification_version: 4
|
371
374
|
summary: Distributed testing for ruby with centralized management
|