gorgon 0.4.0 → 0.4.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/bin/gorgon +15 -0
- data/lib/gorgon/listener_installer.rb +153 -0
- data/lib/gorgon/settings/files_content.rb +26 -0
- data/lib/gorgon/settings/initial_files_creator.rb +77 -0
- data/lib/gorgon/settings/rails_project_files_content.rb +95 -0
- data/lib/gorgon/settings/simple_project_files_content.rb +12 -0
- data/lib/gorgon/version.rb +1 -1
- metadata +10 -5
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -26,7 +26,7 @@ This file contains project-specific settings for gorgon, such as:
|
|
26
26
|
* A glob for generating the list of test files
|
27
27
|
* The file used for Originator's logs
|
28
28
|
|
29
|
-
See [gorgon.json example](
|
29
|
+
See [gorgon.json example](/Fitzsimmons/Gorgon/blob/master/gorgon.json.sample) for more details.
|
30
30
|
|
31
31
|
### gorgon_listener.json
|
32
32
|
This file contains the listener-specific settings, such as:
|
@@ -35,7 +35,7 @@ This file contains the listener-specific settings, such as:
|
|
35
35
|
* How many worker slots are provided by this listener
|
36
36
|
* The file used for logs
|
37
37
|
|
38
|
-
See [gorgon_listener.json example](
|
38
|
+
See [gorgon_listener.json example](/Fitzsimmons/Gorgon/blob/master/gorgon_listener.json.sample) for more details.
|
39
39
|
|
40
40
|
### Setting up gorgon listener as a daemon process using Upstart
|
41
41
|
For an example on how to run listener as a daemon process, follow [these steps](/Fitzsimmons/Gorgon/blob/master/daemon_with_upstart_and_rvm.md)
|
@@ -54,4 +54,4 @@ Gorgon is maintained by:
|
|
54
54
|
* Victor Savkin
|
55
55
|
|
56
56
|
Gorgon is funded by [Nulogy Corp](http://www.nulogy.com/).
|
57
|
-
Thank you to all the [contributors](/contributors).
|
57
|
+
Thank you to all the [contributors](/Fitzsimmons/Gorgon/graphs/contributors).
|
data/bin/gorgon
CHANGED
@@ -5,6 +5,8 @@ require 'gorgon/worker_manager'
|
|
5
5
|
require 'gorgon/ping_service'
|
6
6
|
require 'gorgon/gem_service'
|
7
7
|
require 'gorgon/version'
|
8
|
+
require 'gorgon/listener_installer'
|
9
|
+
require 'gorgon/settings/initial_files_creator'
|
8
10
|
|
9
11
|
WELCOME_MSG = "Welcome to Gorgon #{Gorgon::VERSION}"
|
10
12
|
|
@@ -36,11 +38,20 @@ def run_gem command
|
|
36
38
|
GemService.new.run command
|
37
39
|
end
|
38
40
|
|
41
|
+
def init framework
|
42
|
+
Settings::InitialFilesCreator.run framework
|
43
|
+
end
|
44
|
+
|
45
|
+
def install_listener
|
46
|
+
ListenerInstaller.install
|
47
|
+
end
|
48
|
+
|
39
49
|
def usage
|
40
50
|
#print instructions on how to use gorgon
|
41
51
|
puts "\tstart - remotely runs all tests specified in gorgon.json"
|
42
52
|
puts "\tlisten - starts a listener process using the settings in gorgon_listener.json"
|
43
53
|
puts "\tping - pings listeners and shows hosts and gorgon's version they are running"
|
54
|
+
puts "\tinstall_listener - runs gorgon listener as a daemon process"
|
44
55
|
puts "\tgem command [options...] - execute the gem command on every listener and shutdown listener. e.g. 'gorgon gem install --version 1.0.0'"
|
45
56
|
end
|
46
57
|
|
@@ -62,6 +73,10 @@ when "gem"
|
|
62
73
|
run_gem ARGV.join(' ')
|
63
74
|
when "help"
|
64
75
|
usage
|
76
|
+
when "init"
|
77
|
+
init ARGV[1]
|
78
|
+
when "install_listener"
|
79
|
+
install_listener
|
65
80
|
else
|
66
81
|
puts "Unknown command!"
|
67
82
|
usage
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class ListenerInstaller
|
5
|
+
include Configuration
|
6
|
+
|
7
|
+
GORGON_DIR=".gorgon"
|
8
|
+
DEFAULT_NO_WORKERS = 4
|
9
|
+
LISTENER_CONFIG_FILE = "gorgon_listener.json"
|
10
|
+
GORGON_INIT_FILE = "/etc/init/gorgon.conf"
|
11
|
+
|
12
|
+
def self.install
|
13
|
+
ListenerInstaller.new.install
|
14
|
+
end
|
15
|
+
|
16
|
+
def install
|
17
|
+
@configuration = load_configuration_from_file("gorgon.json")
|
18
|
+
|
19
|
+
FileUtils.mkdir_p gorgon_dir_path
|
20
|
+
Dir.chdir gorgon_dir_path
|
21
|
+
|
22
|
+
create_listener_config_file
|
23
|
+
|
24
|
+
create_gorgon_service
|
25
|
+
|
26
|
+
start_gorgon_daemon
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def gorgon_dir_path
|
32
|
+
@gorgon_dir_path ||= "#{Dir.home}/#{GORGON_DIR}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_listener_config_file
|
36
|
+
worker_slots = worker_slots_prompt
|
37
|
+
puts "Using #{worker_slots} worker slots"
|
38
|
+
amqp_host = @configuration[:connection][:host]
|
39
|
+
puts "Amqp host is '#{amqp_host}'"
|
40
|
+
|
41
|
+
puts "Creating #{LISTENER_CONFIG_FILE} in #{Dir.pwd}"
|
42
|
+
File.open(LISTENER_CONFIG_FILE, 'w') do |f|
|
43
|
+
f.write listener_config(amqp_host, worker_slots)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_gorgon_service
|
48
|
+
params = {}
|
49
|
+
params[:username] = Etc.getlogin
|
50
|
+
params[:rvm_bin_path] = rvm_bin_path
|
51
|
+
params[:gemset] = get_current_gemset
|
52
|
+
|
53
|
+
tmp_gorgon_conf = '/tmp/gorgon.conf'
|
54
|
+
File.open(tmp_gorgon_conf, 'w') do |f|
|
55
|
+
f.write gorgon_conf(params)
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "Creating '#{GORGON_INIT_FILE}'"
|
59
|
+
|
60
|
+
system("sudo cp /tmp/gorgon.conf #{GORGON_INIT_FILE}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def rvm_bin_path
|
64
|
+
cmd = 'which rvm'
|
65
|
+
path = get_shell_cmd_output cmd, "Error getting rvm path. Make sure '#{cmd}' works"
|
66
|
+
puts "Using '#{path}'"
|
67
|
+
path
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_current_gemset
|
71
|
+
return @gemset unless @gemset.nil?
|
72
|
+
|
73
|
+
cmd = 'rvm current'
|
74
|
+
@gemset = get_shell_cmd_output cmd, "Error getting current gem. Make sure '#{cmd}' works"
|
75
|
+
puts "Using gemset '#{@gemset}'."
|
76
|
+
@gemset
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_shell_cmd_output cmd, error_message
|
80
|
+
result = `#{cmd}`.strip
|
81
|
+
if $?.exitstatus != 0
|
82
|
+
$stderr.puts "#{error_message}"
|
83
|
+
$stderr.puts "Aborting installation"
|
84
|
+
exit
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
def start_gorgon_daemon
|
90
|
+
system("sudo start gorgon")
|
91
|
+
end
|
92
|
+
|
93
|
+
def gem_available?(name)
|
94
|
+
Gem::Specification.find_by_name(name)
|
95
|
+
rescue Gem::LoadError
|
96
|
+
false
|
97
|
+
rescue
|
98
|
+
Gem.available?(name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def worker_slots_prompt
|
102
|
+
begin
|
103
|
+
puts "Number of worker slots (default #{DEFAULT_NO_WORKERS})?"
|
104
|
+
input = $stdin.gets.chomp
|
105
|
+
if input == ""
|
106
|
+
worker_slots = DEFAULT_NO_WORKERS
|
107
|
+
break
|
108
|
+
end
|
109
|
+
|
110
|
+
worker_slots = input.to_i
|
111
|
+
if worker_slots <= 0 || worker_slots >= 20
|
112
|
+
puts "Please, enter a valid number between 0 and 19"
|
113
|
+
end
|
114
|
+
end while worker_slots <= 0 || worker_slots >= 20
|
115
|
+
worker_slots
|
116
|
+
end
|
117
|
+
|
118
|
+
def listener_config amqp_host, worker_slots
|
119
|
+
<<-CONFIG
|
120
|
+
{
|
121
|
+
"connection": {
|
122
|
+
"host": "#{amqp_host}"
|
123
|
+
},
|
124
|
+
|
125
|
+
"worker_slots": #{worker_slots},
|
126
|
+
"log_file": "/tmp/gorgon-remote.log"
|
127
|
+
}
|
128
|
+
CONFIG
|
129
|
+
end
|
130
|
+
|
131
|
+
def gorgon_conf params
|
132
|
+
<<-CONF_FILE
|
133
|
+
description "Start gorgon listener"
|
134
|
+
|
135
|
+
start on filesystem or runlevel [2345]
|
136
|
+
stop on runlevel [!2345]
|
137
|
+
|
138
|
+
respawn
|
139
|
+
respawn limit 10 10
|
140
|
+
|
141
|
+
pre-start script
|
142
|
+
|
143
|
+
bash << "EOF"
|
144
|
+
mkdir -p /var/log/gorgon
|
145
|
+
chown -R #{params[:username]} /var/log/gorgon
|
146
|
+
EOF
|
147
|
+
|
148
|
+
end script
|
149
|
+
|
150
|
+
exec su - #{params[:username]} -c 'cd #{gorgon_dir_path} && #{params[:rvm_bin_path]} #{params[:gemset]} do gorgon listen >> /var/log/gorgon/gorgon.log 2>&1'
|
151
|
+
CONF_FILE
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Settings
|
2
|
+
class FilesContent
|
3
|
+
attr_accessor :amqp_host, :sync_exclude, :files, :originator_log_file,
|
4
|
+
:callbacks, :callbacks_dir
|
5
|
+
|
6
|
+
TEST_UNIT_GLOB = "test/**/*_test.rb"
|
7
|
+
RSPEC_GLOB = "spec/**/*_spec.rb"
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@files = []
|
11
|
+
@files << FilesContent::TEST_UNIT_GLOB if Dir.exist?('test')
|
12
|
+
@files << FilesContent::RSPEC_GLOB if Dir.exist?('spec')
|
13
|
+
end
|
14
|
+
|
15
|
+
DEFAULT_AMQP_HOST = 'localhost'
|
16
|
+
def self.get_amqp_host
|
17
|
+
puts "AMQP host (default '#{DEFAULT_AMQP_HOST}')? "
|
18
|
+
input = $stdin.gets.chomp
|
19
|
+
if input == ""
|
20
|
+
return DEFAULT_AMQP_HOST
|
21
|
+
else
|
22
|
+
return input
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'gorgon/settings/simple_project_files_content'
|
2
|
+
require 'gorgon/settings/rails_project_files_content'
|
3
|
+
|
4
|
+
module Settings
|
5
|
+
class InitialFilesCreator
|
6
|
+
GORGON_JSON_FILE = 'gorgon.json'
|
7
|
+
|
8
|
+
# TODO: we may change this, so it knows what Creator to use according to the Gemfile, so the user doesn't need to specify 'framework'
|
9
|
+
def self.run framework
|
10
|
+
if framework.nil?
|
11
|
+
create_files SimpleProjectFilesContent.new
|
12
|
+
elsif framework == 'rails'
|
13
|
+
create_files RailsProjectFilesContent.new
|
14
|
+
else
|
15
|
+
$stderr.puts "Unknown framework"
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create_files content
|
21
|
+
self.create_gorgon_json content
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def self.create_gorgon_json content
|
26
|
+
if File.exist? GORGON_JSON_FILE
|
27
|
+
puts "gorgo.json exists. Skipping..."
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
config = {
|
32
|
+
connection: {host: content.amqp_host},
|
33
|
+
job: {
|
34
|
+
sync_exclude: content.sync_exclude
|
35
|
+
},
|
36
|
+
files: content.files
|
37
|
+
}
|
38
|
+
|
39
|
+
log_file = content.originator_log_file
|
40
|
+
config[:originator_log_file] = log_file if log_file
|
41
|
+
|
42
|
+
if content.callbacks
|
43
|
+
create_callback_files(content)
|
44
|
+
|
45
|
+
config[:job][:callbacks] = content.callbacks.inject({}) do |callbacks, e|
|
46
|
+
callbacks[e[:name]] = "#{content.callbacks_dir}/#{e[:file_name]}"
|
47
|
+
callbacks
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
puts "Creating #{GORGON_JSON_FILE}..."
|
52
|
+
File.open(GORGON_JSON_FILE, 'w') do |f|
|
53
|
+
Yajl::Encoder.encode(config, f, :pretty => true, :indent => " ")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.create_callback_files content
|
58
|
+
FileUtils.mkdir_p content.callbacks_dir
|
59
|
+
content.callbacks.each do |callback|
|
60
|
+
create_callback_file content.callbacks_dir, callback
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.create_callback_file dir, callback
|
65
|
+
file_path = "#{dir}/#{callback[:file_name]}"
|
66
|
+
if File.exist? file_path
|
67
|
+
puts "#{file_path} already exists. Skipping..."
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
puts "Creating #{file_path}..."
|
72
|
+
File.open(file_path, 'w') do |f|
|
73
|
+
f.write callback[:content]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'gorgon/settings/files_content'
|
2
|
+
|
3
|
+
module Settings
|
4
|
+
class RailsProjectFilesContent < FilesContent
|
5
|
+
def initialize
|
6
|
+
super
|
7
|
+
@amqp_host = FilesContent.get_amqp_host
|
8
|
+
@sync_exclude = [".git", ".rvmrc","tmp","log","doc"]
|
9
|
+
@originator_log_file = 'log/gorgon-originator.log'
|
10
|
+
create_callbacks
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def create_callbacks
|
16
|
+
@callbacks_dir = "#{get_app_subdir}gorgon_callbacks"
|
17
|
+
@callbacks = [{name: :after_sync, file_name: "after_sync.rb",
|
18
|
+
content: after_sync_content},
|
19
|
+
{name: :before_creating_workers, file_name: "before_creating_workers.rb",
|
20
|
+
content: before_creating_workers_content},
|
21
|
+
{name: :before_start, file_name: "before_start.rb",
|
22
|
+
content: before_start_content},
|
23
|
+
{name: :after_complete, file_name: "after_complete.rb",
|
24
|
+
content: after_complete_content}]
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_app_subdir
|
28
|
+
if Dir.exist? "test"
|
29
|
+
"test/"
|
30
|
+
elsif Dir.exist? "spec"
|
31
|
+
"spec/"
|
32
|
+
elsif Dir.exist? "lib"
|
33
|
+
"lib/"
|
34
|
+
else
|
35
|
+
""
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_sync_content
|
40
|
+
<<-CONTENT
|
41
|
+
require 'bundler'
|
42
|
+
require 'open4'
|
43
|
+
|
44
|
+
Bundler.with_clean_env do
|
45
|
+
BUNDLE_LOG_FILE||="/tmp/gorgon-bundle-install.log "
|
46
|
+
|
47
|
+
pid, stdin, stdout, stderr = Open4::popen4 "bundle install > \#\{BUNDLE_LOG_FILE\} 2>&1 "
|
48
|
+
|
49
|
+
ignore, status = Process.waitpid2 pid
|
50
|
+
|
51
|
+
if status.exitstatus != 0
|
52
|
+
raise "ERROR: 'bundle install' failed.\n\#\{stderr.read\}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
CONTENT
|
56
|
+
end
|
57
|
+
|
58
|
+
def before_creating_workers_content
|
59
|
+
content = ""
|
60
|
+
content << "require './test/test_helper'\n" if File.exist?("test/test_helper.rb")
|
61
|
+
content << "require './spec/spec_helper'\n" if File.exist?("spec/spec_helper.rb")
|
62
|
+
content
|
63
|
+
end
|
64
|
+
|
65
|
+
def before_start_content
|
66
|
+
<<-CONTENT
|
67
|
+
require 'rake'
|
68
|
+
load './Rakefile'
|
69
|
+
|
70
|
+
begin
|
71
|
+
Rails.env = 'remote_test'
|
72
|
+
ENV['TEST_ENV_NUMBER'] = Process.pid.to_s
|
73
|
+
|
74
|
+
Rake::Task['db:reset'].invoke
|
75
|
+
end
|
76
|
+
|
77
|
+
CONTENT
|
78
|
+
end
|
79
|
+
|
80
|
+
def after_complete_content
|
81
|
+
<<-CONTENT
|
82
|
+
require 'rake'
|
83
|
+
load './Rakefile'
|
84
|
+
|
85
|
+
begin
|
86
|
+
if Rails.env = 'remote_test'
|
87
|
+
Rake::Task['db:drop'].execute
|
88
|
+
end
|
89
|
+
rescue Exception => ex
|
90
|
+
puts "Error dropping test database:\n \#\{ex\}"
|
91
|
+
end
|
92
|
+
CONTENT
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'gorgon/settings/files_content'
|
2
|
+
|
3
|
+
module Settings
|
4
|
+
class SimpleProjectFilesContent < FilesContent
|
5
|
+
def initialize
|
6
|
+
super
|
7
|
+
@amqp_host = FilesContent.get_amqp_host
|
8
|
+
@sync_exclude = [".git", ".rvmrc"]
|
9
|
+
@originator_log_file = 'gorgon-originator.log'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/gorgon/version.rb
CHANGED
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gorgon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.1.rc1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Justin Fitzsimmons
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2012-12-
|
16
|
+
date: 2012-12-18 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: rake
|
@@ -228,6 +228,7 @@ files:
|
|
228
228
|
- lib/gorgon/job_definition.rb
|
229
229
|
- lib/gorgon/job_state.rb
|
230
230
|
- lib/gorgon/listener.rb
|
231
|
+
- lib/gorgon/listener_installer.rb
|
231
232
|
- lib/gorgon/mini_test_runner.rb
|
232
233
|
- lib/gorgon/originator.rb
|
233
234
|
- lib/gorgon/originator_logger.rb
|
@@ -236,6 +237,10 @@ files:
|
|
236
237
|
- lib/gorgon/pipe_forker.rb
|
237
238
|
- lib/gorgon/progress_bar_view.rb
|
238
239
|
- lib/gorgon/rspec_runner.rb
|
240
|
+
- lib/gorgon/settings/files_content.rb
|
241
|
+
- lib/gorgon/settings/initial_files_creator.rb
|
242
|
+
- lib/gorgon/settings/rails_project_files_content.rb
|
243
|
+
- lib/gorgon/settings/simple_project_files_content.rb
|
239
244
|
- lib/gorgon/source_tree_syncer.rb
|
240
245
|
- lib/gorgon/test_unit_runner.rb
|
241
246
|
- lib/gorgon/unknown_runner.rb
|
@@ -279,9 +284,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
279
284
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
280
285
|
none: false
|
281
286
|
requirements:
|
282
|
-
- - ! '
|
287
|
+
- - ! '>'
|
283
288
|
- !ruby/object:Gem::Version
|
284
|
-
version:
|
289
|
+
version: 1.3.1
|
285
290
|
requirements: []
|
286
291
|
rubyforge_project: gorgon
|
287
292
|
rubygems_version: 1.8.24
|