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

|
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
|