cumuli 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/features/step_definitions/app_steps.rb +1 -1
- data/lib/cumuli/cli/remote_command.rb +1 -1
- data/lib/cumuli/project_manager/manager.rb +47 -0
- data/lib/cumuli/project_manager/project.rb +106 -0
- data/lib/cumuli/project_manager.rb +2 -0
- data/lib/cumuli/spawner/app.rb +81 -0
- data/lib/cumuli/{app/spawner.rb → spawner/foreman_proc.rb} +2 -2
- data/lib/cumuli/{app → spawner}/stdout_logger.rb +1 -1
- data/lib/cumuli/spawner.rb +4 -0
- data/lib/cumuli/tasks/cumuli.rake +39 -28
- data/lib/cumuli/version.rb +1 -1
- data/lib/cumuli.rb +2 -1
- data/spec/fixtures/app_set/config/projects.yml +6 -0
- data/spec/fixtures/project_manager/config/projects.yml +71 -0
- data/spec/project_manager/manager_spec.rb +28 -0
- data/spec/project_manager/project_spec.rb +95 -0
- data/spec/{app → spawner}/app_spec.rb +2 -2
- metadata +19 -14
- data/lib/cumuli/app/app.rb +0 -86
- data/lib/cumuli/app/procs.rb +0 -33
- data/lib/cumuli/app/sub_app.rb +0 -50
- data/lib/cumuli/app.rb +0 -6
- data/spec/app/procs_spec.rb +0 -21
- data/spec/app/sub_app_spec.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99ef0de4fd4c50b58aaeb4ebe381eea7f06ebba6
|
4
|
+
data.tar.gz: 90149d60a66ab52a9b0769e611706d673fea480d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 934f662727f2fa25fa6c64fe7f93572b237f1fcf4b141e9a249e4e81d2cedc18850d8a73a5f43572bfb0f12b08ada252d67c57f63dbe8d6ef69e4b10812b5a40
|
7
|
+
data.tar.gz: b7305c29e3ae04e9cdcfc8201253dd72dcdd762b23ade163f3a48c57812ec927c50cc587644077bea517b941eb7a674fe737cf3e58a8b1fbc3fd45c741a69c06
|
data/README.md
CHANGED
@@ -55,7 +55,7 @@ framework.
|
|
55
55
|
# first argument: environment
|
56
56
|
# second argument: whether to try to establish a connection to
|
57
57
|
# each of the apps with a port before continuing in the thread
|
58
|
-
app = Cumuli::App.new('test', false)
|
58
|
+
app = Cumuli::Spawner::App.new('test', false)
|
59
59
|
|
60
60
|
app.start # starts all the applications and services in the Procfile
|
61
61
|
|
@@ -90,9 +90,9 @@ are likely related to cumuli.
|
|
90
90
|
|
91
91
|
## Known Issues
|
92
92
|
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
* An occasional orphan will be left around, still debugging
|
94
|
+
* Mechanism for alerting spawner app that the foreman process has killed
|
95
|
+
children needs to be faster and more direct
|
96
96
|
|
97
97
|
## Contributing
|
98
98
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cumuli
|
2
|
+
module ProjectManager
|
3
|
+
class Manager
|
4
|
+
DEFAULT_PROJECT_PATH = Dir.pwd
|
5
|
+
|
6
|
+
attr_accessor :config_path, :procfile_path
|
7
|
+
|
8
|
+
def initialize(path=DEFAULT_PROJECT_PATH)
|
9
|
+
@path = path
|
10
|
+
@config_path = "#{path}/config/projects.yml"
|
11
|
+
@procfile_path = "#{path}/Procfile"
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish
|
15
|
+
File.open(procfile_path, 'w') do |f|
|
16
|
+
projects.each do |project|
|
17
|
+
f.write project.to_procfile
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
submodules_init
|
24
|
+
system('git submodule init')
|
25
|
+
system('git submodule update')
|
26
|
+
system("git submodule foreach git pull")
|
27
|
+
setup_projects
|
28
|
+
end
|
29
|
+
|
30
|
+
def submodules_init
|
31
|
+
projects.each { |project| project.submodule_init }
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_projects
|
35
|
+
projects.each { |project| project.setup }
|
36
|
+
end
|
37
|
+
|
38
|
+
def projects
|
39
|
+
@projects ||= config.map{ |name, opts| Cumuli::ProjectManager::Project.new(name, opts) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def config
|
43
|
+
@config ||= YAML.load( File.read(config_path) )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Cumuli
|
2
|
+
module ProjectManager
|
3
|
+
class Project
|
4
|
+
DEFAULT_WAIT_TIME = 10
|
5
|
+
LOCALHOST = '127.0.0.1'
|
6
|
+
|
7
|
+
attr_reader :name, :database_config, :database_sample_config, :port, :path,
|
8
|
+
:repository, :type, :setup_scripts, :wait_time, :domain
|
9
|
+
|
10
|
+
def initialize(name, opts)
|
11
|
+
@name = name
|
12
|
+
|
13
|
+
@repository = opts['repository']
|
14
|
+
@path = opts['path'] || "./#{name}"
|
15
|
+
@port = opts['port']
|
16
|
+
@type = opts['type'] || 'app'
|
17
|
+
@wait_time = opts['wait_time'] || DEFAULT_WAIT_TIME
|
18
|
+
@domain = opts['domain'] || '127.0.0.1'
|
19
|
+
|
20
|
+
@database_config = opts['database_config'] || []
|
21
|
+
@database_sample_config = opts['database_sample_config'] || []
|
22
|
+
@setup_scripts = opts['setup_scripts'] || []
|
23
|
+
|
24
|
+
@database_config = [@database_config] unless @database_config.is_a?(Array)
|
25
|
+
@database_sample_config = [@database_sample_config] unless @database_sample_config.is_a?(Array)
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait?
|
29
|
+
type == 'app' && (wait_time && wait_time > 0) && (port || domain != LOCALHOST)
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait_for_start
|
33
|
+
return unless wait?
|
34
|
+
Waiter.new("Unable to start #{name}").wait_until(wait_time) { socket_available? }
|
35
|
+
end
|
36
|
+
|
37
|
+
def wait_for_stop
|
38
|
+
return unless wait?
|
39
|
+
Waiter.new("Unable to stop #{name}").wait_until(wait_time) { !socket_available? }
|
40
|
+
end
|
41
|
+
|
42
|
+
def socket_available?
|
43
|
+
TCPSocket.new(domain, port)
|
44
|
+
true
|
45
|
+
rescue Errno::ECONNREFUSED
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def url
|
50
|
+
"http://#{domain}#{port ? ":#{port}" : ''}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def database_sample_paths
|
54
|
+
database_sample_config.map { |config_path| "#{path}/#{config_path}" }
|
55
|
+
end
|
56
|
+
|
57
|
+
def database_config_paths
|
58
|
+
database_config.map { |config_path| "#{path}/#{config_path}" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def database_configured?(path)
|
62
|
+
File.exist?(path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_database_config!
|
66
|
+
database_config_paths.each_with_index do |config_path, i|
|
67
|
+
FileUtils.cp(database_sample_paths[i], config_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def write_database_config
|
72
|
+
database_config_paths.each_with_index do |config_path, i|
|
73
|
+
FileUtils.cp(database_sample_paths[i], config_path) unless configured?(config_path)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_procfile
|
78
|
+
send("#{type}_to_procfile") rescue ''
|
79
|
+
end
|
80
|
+
|
81
|
+
def app_to_procfile
|
82
|
+
"#{name}: cumuli #{path} -p #{port}\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def service_to_procfile
|
86
|
+
"#{name}: cumuli #{path}\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
def submodule_init
|
90
|
+
system "git submodule add #{repository} #{path}" if repository
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup
|
94
|
+
# checkout master, because sometimes on setup no branch is
|
95
|
+
# checked out
|
96
|
+
CLI::RemoteCommand.new(['git checkout master', "DIR=#{path}"])
|
97
|
+
|
98
|
+
write_database_config
|
99
|
+
|
100
|
+
setup_scripts.each do |script|
|
101
|
+
CLI::RemoteCommand.new([script, "DIR=#{path}"]).perform
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Cumuli
|
2
|
+
module Spawner
|
3
|
+
class App
|
4
|
+
SIGNALS = ['TERM', 'INT', 'HUP']
|
5
|
+
|
6
|
+
attr_accessor :env, :app_dir, :log_dir
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@env = options[:env]
|
10
|
+
@log_dir = options[:log_dir] || "#{Dir.pwd}/log"
|
11
|
+
@app_dir = options[:app_dir] || Dir.pwd
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
return if foreman_process.started?
|
16
|
+
|
17
|
+
Dir.chdir(app_dir) do
|
18
|
+
listen_for_signals
|
19
|
+
logger.print "Starting ..."
|
20
|
+
foreman_process.start
|
21
|
+
wait_for_apps
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
if foreman_process.started?
|
27
|
+
foreman_process.stop
|
28
|
+
wait_for_apps('stop')
|
29
|
+
@foreman_process = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
@logger = StdoutLogger.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def foreman_process
|
38
|
+
@foreman_process ||= Spawner::ForemanProc.new(env, log_dir)
|
39
|
+
end
|
40
|
+
|
41
|
+
def apps
|
42
|
+
@apps ||= ProjectManager::Manager.new(app_dir).projects
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_pids
|
46
|
+
PS.new.family
|
47
|
+
end
|
48
|
+
|
49
|
+
def pid
|
50
|
+
foreman_process.group_id
|
51
|
+
end
|
52
|
+
|
53
|
+
def listen_for_signals
|
54
|
+
SIGNALS.each do |signal|
|
55
|
+
trap(signal) do
|
56
|
+
puts "#{self.class}: trapped signal #{signal} in #{Process.pid} ... stopping"
|
57
|
+
stop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def wait_for_apps(direction = 'start')
|
63
|
+
logger.add_space
|
64
|
+
apps.each do |app|
|
65
|
+
log_and_wait(app, direction)
|
66
|
+
end
|
67
|
+
logger.add_space
|
68
|
+
end
|
69
|
+
|
70
|
+
def log_and_wait(app, direction)
|
71
|
+
logger.print "#{direction}: waiting for app named '#{app.name}' at #{app.url}"
|
72
|
+
app.send("wait_for_#{direction}")
|
73
|
+
logger.print "#{direction}: application '#{app.name}' on #{app.url} complete"
|
74
|
+
logger.add_space
|
75
|
+
rescue Exception => e
|
76
|
+
stop
|
77
|
+
raise e
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -1,42 +1,53 @@
|
|
1
|
-
require_relative "
|
2
|
-
require_relative "../cli/remote_command"
|
3
|
-
require_relative "../cli/commander"
|
4
|
-
require_relative "../cli/terminal"
|
1
|
+
require_relative "../../cumuli"
|
5
2
|
|
6
3
|
namespace :cumuli do
|
7
|
-
namespace :
|
8
|
-
desc "
|
9
|
-
task :
|
10
|
-
|
11
|
-
ps.kill(ps.foremans.map(&:pid))
|
4
|
+
namespace :project do
|
5
|
+
desc "install submodules and run setup scripts based on the project.yml"
|
6
|
+
task :setup do
|
7
|
+
Cumuli::ProjectManager::Manager.new.setup
|
12
8
|
end
|
13
9
|
|
14
|
-
desc "
|
15
|
-
task :
|
16
|
-
|
17
|
-
ps.kill
|
10
|
+
desc "copy database config and rerun setup scripts"
|
11
|
+
task :config do
|
12
|
+
Cumuli::ProjectManager::Manager.new.setup_projects
|
18
13
|
end
|
19
14
|
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
namespace :ps do
|
17
|
+
namespace :kill do
|
18
|
+
desc "kill all foreman related processes"
|
19
|
+
task :all do
|
20
|
+
ps = Cumuli::PS.new
|
21
|
+
ps.kill(ps.foremans.map(&:pid))
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "kill the first spawned foreman process"
|
25
|
+
task :root do
|
26
|
+
ps = Cumuli::PS.new
|
27
|
+
ps.kill
|
28
|
+
end
|
29
29
|
end
|
30
30
|
|
31
|
-
desc "kill the
|
32
|
-
task :root
|
33
|
-
|
34
|
-
|
31
|
+
desc "kill the root process"
|
32
|
+
task :kill => ['cumuli:kill:root']
|
33
|
+
|
34
|
+
namespace :int do
|
35
|
+
desc "interrupt all foreman related processes"
|
36
|
+
task :all do
|
37
|
+
ps = Cumuli::PS.new
|
38
|
+
ps.int(ps.foremans.map(&:pid))
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "kill the first spawned foreman process"
|
42
|
+
task :root do
|
43
|
+
ps = Cumuli::PS.new
|
44
|
+
ps.int
|
45
|
+
end
|
35
46
|
end
|
36
|
-
end
|
37
47
|
|
38
|
-
|
39
|
-
|
48
|
+
desc "interrupt the root process"
|
49
|
+
task :int => ['cumuli:int:root']
|
50
|
+
end
|
40
51
|
|
41
52
|
desc "look at the list of foreman related processes"
|
42
53
|
task :ps do
|
data/lib/cumuli/version.rb
CHANGED
data/lib/cumuli.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
activator:
|
2
|
+
repository: git@github.com:socialchorus/activator.git
|
3
|
+
database_yml: config/database.yml
|
4
|
+
database_sample_yml: config/database.example.yml
|
5
|
+
type: app
|
6
|
+
port: 3000
|
7
|
+
setup_scripts:
|
8
|
+
- bundle
|
9
|
+
- rake db:create:all
|
10
|
+
- rake db:migrate
|
11
|
+
|
12
|
+
mactivator:
|
13
|
+
repository: git@github.com:socialchorus/mactivator.git
|
14
|
+
database_yml: config/database.yml
|
15
|
+
database_sample_yml: config/database.example.yml
|
16
|
+
type: app
|
17
|
+
port: 4000
|
18
|
+
setup_scripts:
|
19
|
+
- bundle
|
20
|
+
|
21
|
+
bossanova:
|
22
|
+
repository: git@github.com:socialchorus/bossanova.git
|
23
|
+
database_yml: config/database.yml
|
24
|
+
database_sample_yml: config/database.example.yml
|
25
|
+
type: app
|
26
|
+
port: 6000
|
27
|
+
setup_scripts:
|
28
|
+
- bundle
|
29
|
+
- rake db:drop:all
|
30
|
+
- rake db:create:all
|
31
|
+
- rake db:migrate
|
32
|
+
|
33
|
+
linktimigrator:
|
34
|
+
repository: git@github.com:socialchorus/linktimigrator.git
|
35
|
+
database_yml: database.json
|
36
|
+
database_sample_yml: ../linktivator/database.json.sample
|
37
|
+
type: helper
|
38
|
+
setup_scripts:
|
39
|
+
- bundle
|
40
|
+
- ruby bin/migrate.rb
|
41
|
+
|
42
|
+
linktivator:
|
43
|
+
repository: git@github.com:socialchorus/linktivator.git
|
44
|
+
database_yml: database.json
|
45
|
+
database_sample_yml: database.json.sample
|
46
|
+
type: app
|
47
|
+
port: 5000
|
48
|
+
setup_scripts:
|
49
|
+
- bundle
|
50
|
+
- npm install
|
51
|
+
|
52
|
+
augury:
|
53
|
+
repository: git@github.com:socialchorus/augury.git
|
54
|
+
type: node
|
55
|
+
port: 5010
|
56
|
+
setup_scripts:
|
57
|
+
- bundle
|
58
|
+
- npm install
|
59
|
+
|
60
|
+
transducer:
|
61
|
+
repository: git@github.com:socialchorus/transducer.git
|
62
|
+
database_yml:
|
63
|
+
- config/activator_database.yml
|
64
|
+
- config/bossanova_database.yml']
|
65
|
+
database_sample_yml:
|
66
|
+
- config/activator_database.example.yml
|
67
|
+
- config/bossanova_database.example.yml
|
68
|
+
type: service
|
69
|
+
setup_scripts:
|
70
|
+
- bundle
|
71
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cumuli::ProjectManager::Manager do
|
4
|
+
let(:app_path) { File.dirname(__FILE__) + "/../fixtures/project_manager" }
|
5
|
+
let(:loader) {
|
6
|
+
Cumuli::ProjectManager::Manager.new(app_path)
|
7
|
+
}
|
8
|
+
let(:procfile_path) { "#{app_path}/Procfile" }
|
9
|
+
let(:procfile_contents) { File.read(procfile_path) }
|
10
|
+
|
11
|
+
describe "#publish" do
|
12
|
+
before do
|
13
|
+
File.delete(procfile_path) if File.exist?(procfile_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "write a procfile with a line for every service or app" do
|
17
|
+
loader.publish
|
18
|
+
procfile_contents.lines.size.should == 5
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#projects" do
|
23
|
+
it "is a collection of project objects, one for each entry in the yml" do
|
24
|
+
loader.projects.size.should == 7
|
25
|
+
loader.projects.map{|a| a.class }.uniq.should == [Cumuli::ProjectManager::Project]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cumuli::ProjectManager::Project do
|
4
|
+
let(:type) { 'app' }
|
5
|
+
let(:port) { 3000 }
|
6
|
+
let(:wait_time) { 0.1 }
|
7
|
+
let(:app) {
|
8
|
+
Cumuli::ProjectManager::Project.new('activator', {
|
9
|
+
'repository' => 'git@github.com:socialchorus/activator.git',
|
10
|
+
'database_config' => 'config/database.config',
|
11
|
+
'database_sample_config' => 'config/database.example.config',
|
12
|
+
'type' => type,
|
13
|
+
'port' => port,
|
14
|
+
'wait_time' => wait_time,
|
15
|
+
'setup_scripts' => ['bundle', 'rake db:create:all', 'rake db:migrate']
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
it "stores configuration data" do
|
20
|
+
app.name.should == 'activator'
|
21
|
+
app.path.should == './activator'
|
22
|
+
app.repository.should == 'git@github.com:socialchorus/activator.git'
|
23
|
+
app.type.should == 'app'
|
24
|
+
app.port.should == 3000
|
25
|
+
app.wait_time.should == 0.1
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#wait?' do
|
29
|
+
context "when the type is 'app'" do
|
30
|
+
context "when there is no port" do
|
31
|
+
let(:port) { nil }
|
32
|
+
|
33
|
+
it "should be false" do
|
34
|
+
app.wait?.should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when there is a port" do
|
39
|
+
it "should be true" do
|
40
|
+
app.wait?.should be_true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the type is something else" do
|
46
|
+
let(:type) { 'service' }
|
47
|
+
|
48
|
+
it "is false" do
|
49
|
+
app.wait?.should be_false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#to_procfile' do
|
55
|
+
context "when an app" do
|
56
|
+
it "returns a line with a port number" do
|
57
|
+
app.to_procfile.should == "activator: cumuli ./activator -p 3000\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when a service" do
|
62
|
+
let(:type) { 'service' }
|
63
|
+
|
64
|
+
it "returns a line without a port" do
|
65
|
+
app.to_procfile.should == "activator: cumuli ./activator\n"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when something else" do
|
70
|
+
let(:type) { 'helper' }
|
71
|
+
|
72
|
+
it "returns an empty string" do
|
73
|
+
app.to_procfile.should == ""
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'waiting' do
|
79
|
+
describe '#wait_for_start' do
|
80
|
+
it "raises an error if the app in unavailable via TCP socket after timeout" do
|
81
|
+
TCPSocket.should_receive(:new).with('127.0.0.1', 3000).and_raise(Exception)
|
82
|
+
expect {
|
83
|
+
app.wait_for_start
|
84
|
+
}.to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not raise an error if the TCP socket is available" do
|
88
|
+
TCPSocket.should_receive(:new).with('127.0.0.1', 3000)
|
89
|
+
expect {
|
90
|
+
app.wait_for_start
|
91
|
+
}.not_to raise_error
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Cumuli::App do
|
3
|
+
describe Cumuli::Spawner::App do
|
4
4
|
let(:opts) {
|
5
5
|
{
|
6
6
|
env: 'test',
|
@@ -9,7 +9,7 @@ describe Cumuli::App do
|
|
9
9
|
app_dir: app_set_dir
|
10
10
|
}
|
11
11
|
}
|
12
|
-
let(:app) { Cumuli::App.new(opts) }
|
12
|
+
let(:app) { Cumuli::Spawner::App.new(opts) }
|
13
13
|
let(:logs) { File.readlines("#{log_dir}/test.log") }
|
14
14
|
|
15
15
|
describe '#start' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cumuli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SocialChorus
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-07-
|
15
|
+
date: 2013-07-27 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: foreman
|
@@ -106,12 +106,6 @@ files:
|
|
106
106
|
- features/step_definitions/app_steps.rb
|
107
107
|
- features/support/env.rb
|
108
108
|
- lib/cumuli.rb
|
109
|
-
- lib/cumuli/app.rb
|
110
|
-
- lib/cumuli/app/app.rb
|
111
|
-
- lib/cumuli/app/procs.rb
|
112
|
-
- lib/cumuli/app/spawner.rb
|
113
|
-
- lib/cumuli/app/stdout_logger.rb
|
114
|
-
- lib/cumuli/app/sub_app.rb
|
115
109
|
- lib/cumuli/cli.rb
|
116
110
|
- lib/cumuli/cli/args.rb
|
117
111
|
- lib/cumuli/cli/cli.rb
|
@@ -119,27 +113,36 @@ files:
|
|
119
113
|
- lib/cumuli/cli/remote_command.rb
|
120
114
|
- lib/cumuli/cli/terminal.rb
|
121
115
|
- lib/cumuli/facade.rb
|
116
|
+
- lib/cumuli/project_manager.rb
|
117
|
+
- lib/cumuli/project_manager/manager.rb
|
118
|
+
- lib/cumuli/project_manager/project.rb
|
122
119
|
- lib/cumuli/ps.rb
|
120
|
+
- lib/cumuli/spawner.rb
|
121
|
+
- lib/cumuli/spawner/app.rb
|
122
|
+
- lib/cumuli/spawner/foreman_proc.rb
|
123
|
+
- lib/cumuli/spawner/stdout_logger.rb
|
123
124
|
- lib/cumuli/tasks.rb
|
124
125
|
- lib/cumuli/tasks/cumuli.rake
|
125
126
|
- lib/cumuli/version.rb
|
126
127
|
- lib/cumuli/waiter.rb
|
127
|
-
- spec/app/app_spec.rb
|
128
|
-
- spec/app/procs_spec.rb
|
129
|
-
- spec/app/sub_app_spec.rb
|
130
128
|
- spec/cli/args_spec.rb
|
131
129
|
- spec/cli/commander_spec.rb
|
132
130
|
- spec/cli/remote_command_spec.rb
|
133
131
|
- spec/cli/terminal_spec.rb
|
134
132
|
- spec/fixtures/app_set/Procfile
|
133
|
+
- spec/fixtures/app_set/config/projects.yml
|
135
134
|
- spec/fixtures/app_set/loopy/.rvmrc
|
136
135
|
- spec/fixtures/app_set/loopy/Procfile
|
137
136
|
- spec/fixtures/app_set/loopy/half_loop.rb
|
138
137
|
- spec/fixtures/app_set/loopy/loop.rb
|
139
138
|
- spec/fixtures/app_set/noded/.rvmrc
|
140
139
|
- spec/fixtures/app_set/noded/nodified.js
|
140
|
+
- spec/fixtures/project_manager/config/projects.yml
|
141
141
|
- spec/fixtures/ps.txt
|
142
|
+
- spec/project_manager/manager_spec.rb
|
143
|
+
- spec/project_manager/project_spec.rb
|
142
144
|
- spec/ps_spec.rb
|
145
|
+
- spec/spawner/app_spec.rb
|
143
146
|
- spec/spec_helper.rb
|
144
147
|
- spec/support/functional_helpers.rb
|
145
148
|
- spec/support/log_helpers.rb
|
@@ -175,22 +178,24 @@ test_files:
|
|
175
178
|
- features/graceful_exits.feature
|
176
179
|
- features/step_definitions/app_steps.rb
|
177
180
|
- features/support/env.rb
|
178
|
-
- spec/app/app_spec.rb
|
179
|
-
- spec/app/procs_spec.rb
|
180
|
-
- spec/app/sub_app_spec.rb
|
181
181
|
- spec/cli/args_spec.rb
|
182
182
|
- spec/cli/commander_spec.rb
|
183
183
|
- spec/cli/remote_command_spec.rb
|
184
184
|
- spec/cli/terminal_spec.rb
|
185
185
|
- spec/fixtures/app_set/Procfile
|
186
|
+
- spec/fixtures/app_set/config/projects.yml
|
186
187
|
- spec/fixtures/app_set/loopy/.rvmrc
|
187
188
|
- spec/fixtures/app_set/loopy/Procfile
|
188
189
|
- spec/fixtures/app_set/loopy/half_loop.rb
|
189
190
|
- spec/fixtures/app_set/loopy/loop.rb
|
190
191
|
- spec/fixtures/app_set/noded/.rvmrc
|
191
192
|
- spec/fixtures/app_set/noded/nodified.js
|
193
|
+
- spec/fixtures/project_manager/config/projects.yml
|
192
194
|
- spec/fixtures/ps.txt
|
195
|
+
- spec/project_manager/manager_spec.rb
|
196
|
+
- spec/project_manager/project_spec.rb
|
193
197
|
- spec/ps_spec.rb
|
198
|
+
- spec/spawner/app_spec.rb
|
194
199
|
- spec/spec_helper.rb
|
195
200
|
- spec/support/functional_helpers.rb
|
196
201
|
- spec/support/log_helpers.rb
|
data/lib/cumuli/app/app.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
module Cumuli
|
2
|
-
class App
|
3
|
-
DEFAULT_WAIT_TIME = 5 #120
|
4
|
-
SIGNALS = ['TERM', 'INT', 'HUP']
|
5
|
-
|
6
|
-
attr_accessor :env, :wait_time, :app_dir, :log_dir
|
7
|
-
|
8
|
-
def initialize(options={})
|
9
|
-
@env = options[:env]
|
10
|
-
@wait_time = options[:wait_time]
|
11
|
-
@log_dir = options[:log_dir] || "#{Dir.pwd}/log"
|
12
|
-
@app_dir = options[:app_dir] || Dir.pwd
|
13
|
-
end
|
14
|
-
|
15
|
-
def start
|
16
|
-
return if foreman_process.started?
|
17
|
-
|
18
|
-
Dir.chdir(app_dir) do
|
19
|
-
listen_for_signals
|
20
|
-
logger.print "Starting ..."
|
21
|
-
foreman_process.start
|
22
|
-
wait_for_apps if wait?
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def stop
|
27
|
-
if foreman_process.started?
|
28
|
-
foreman_process.stop
|
29
|
-
wait_for_apps('stop') if wait?
|
30
|
-
@foreman_process = nil
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def logger
|
35
|
-
@logger = StdoutLogger.new
|
36
|
-
end
|
37
|
-
|
38
|
-
def foreman_process
|
39
|
-
@foreman_process ||= Spawner.new(env, log_dir)
|
40
|
-
end
|
41
|
-
|
42
|
-
def apps
|
43
|
-
@apps ||= Procs.new(app_dir).apps
|
44
|
-
end
|
45
|
-
|
46
|
-
def process_pids
|
47
|
-
PS.new.family
|
48
|
-
end
|
49
|
-
|
50
|
-
def pid
|
51
|
-
foreman_process.group_id
|
52
|
-
end
|
53
|
-
|
54
|
-
def wait?
|
55
|
-
wait_time && wait_time > 0
|
56
|
-
end
|
57
|
-
|
58
|
-
def listen_for_signals
|
59
|
-
SIGNALS.each do |signal|
|
60
|
-
trap(signal) do
|
61
|
-
puts "#{self.class}: trapped signal #{signal} in #{Process.pid} ... stopping"
|
62
|
-
stop
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def wait_for_apps(direction = 'start')
|
68
|
-
logger.add_space
|
69
|
-
apps.each do |app|
|
70
|
-
log_and_wait(app, direction)
|
71
|
-
end
|
72
|
-
logger.add_space
|
73
|
-
end
|
74
|
-
|
75
|
-
def log_and_wait(app, direction)
|
76
|
-
timeout = wait_time || DEFAULT_WAIT_TIME
|
77
|
-
logger.print "#{direction}: waiting for app named '#{app.name}' at #{app.url}"
|
78
|
-
app.send("wait_for_#{direction}", timeout)
|
79
|
-
logger.print "#{direction}: application '#{app.name}' on #{app.url} complete"
|
80
|
-
logger.add_space
|
81
|
-
rescue Exception => e
|
82
|
-
stop
|
83
|
-
raise e
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
data/lib/cumuli/app/procs.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module Cumuli
|
2
|
-
class App
|
3
|
-
class Procs
|
4
|
-
attr_reader :port_map, :path, :apps
|
5
|
-
|
6
|
-
def initialize(path=nil)
|
7
|
-
@path = (path || Dir.pwd) + "/Procfile"
|
8
|
-
parse_procfile
|
9
|
-
end
|
10
|
-
|
11
|
-
def map
|
12
|
-
@map ||= apps.inject({}) do |hash, app|
|
13
|
-
hash[app.name] = app.port
|
14
|
-
hash
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def file
|
19
|
-
@file ||= File.read(path)
|
20
|
-
end
|
21
|
-
|
22
|
-
def parse_procfile
|
23
|
-
@apps = file.lines.map do |line|
|
24
|
-
SubApp.new(line)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def names
|
29
|
-
map.keys
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/lib/cumuli/app/sub_app.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
module Cumuli
|
2
|
-
class App
|
3
|
-
class SubApp
|
4
|
-
attr_reader :parts
|
5
|
-
attr_accessor :host
|
6
|
-
|
7
|
-
def initialize(line)
|
8
|
-
@parts = line.split
|
9
|
-
@host = 'localhost'
|
10
|
-
end
|
11
|
-
|
12
|
-
def name
|
13
|
-
parts.first.gsub(':', '')
|
14
|
-
end
|
15
|
-
|
16
|
-
def port
|
17
|
-
if index = parts.find_index('-p')
|
18
|
-
parts[index + 1] && parts[index + 1].to_i
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def waitable?
|
23
|
-
host != 'localhost' || port
|
24
|
-
end
|
25
|
-
|
26
|
-
def wait_for_start(timeout)
|
27
|
-
return unless waitable?
|
28
|
-
Waiter.new("Application #{name} on port #{port} unavailable after #{timeout} seconds")
|
29
|
-
.wait_until(timeout) { socket_available? }
|
30
|
-
end
|
31
|
-
|
32
|
-
def wait_for_stop(timeout)
|
33
|
-
return unless waitable?
|
34
|
-
Waiter.new("Application #{name} on port #{port} still connected after #{timeout} seconds")
|
35
|
-
.wait_until(timeout) { !socket_available? }
|
36
|
-
end
|
37
|
-
|
38
|
-
def url
|
39
|
-
"http://#{host}#{port ? ':' + port.to_s : ''}"
|
40
|
-
end
|
41
|
-
|
42
|
-
def socket_available?
|
43
|
-
TCPSocket.new(host, port)
|
44
|
-
true
|
45
|
-
rescue Errno::ECONNREFUSED
|
46
|
-
false
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
data/lib/cumuli/app.rb
DELETED
data/spec/app/procs_spec.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Cumuli::App::Procs do
|
4
|
-
let(:procs) { Cumuli::App::Procs.new(app_set_dir) }
|
5
|
-
|
6
|
-
it "#names includes all the app names listed in the Procfile" do
|
7
|
-
procs.names.should =~ [
|
8
|
-
'loopy', 'nodified'
|
9
|
-
]
|
10
|
-
end
|
11
|
-
|
12
|
-
describe '#map' do
|
13
|
-
it "maps names to ports" do
|
14
|
-
procs.map['nodified'].should == 2323
|
15
|
-
end
|
16
|
-
|
17
|
-
it "return a nil port when the app has no port" do
|
18
|
-
procs.map['loopy'].should == nil
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/spec/app/sub_app_spec.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Cumuli::App::SubApp do
|
4
|
-
let(:sub_app) {
|
5
|
-
Cumuli::App::SubApp.new(
|
6
|
-
"nodified: ../../../bin/cumuli ./noded -p 2323"
|
7
|
-
)
|
8
|
-
}
|
9
|
-
|
10
|
-
describe '#wait_for_start' do
|
11
|
-
it "raises an error if the app in unavailable via TCP socket after timeout" do
|
12
|
-
TCPSocket.should_receive(:new).with('localhost', 2323).and_raise(Exception)
|
13
|
-
expect {
|
14
|
-
sub_app.wait_for_start(0.002)
|
15
|
-
}.to raise_error
|
16
|
-
end
|
17
|
-
|
18
|
-
it "does not raise an error if the TCP socket is available" do
|
19
|
-
TCPSocket.should_receive(:new).with('localhost', 2323)
|
20
|
-
expect {
|
21
|
-
sub_app.wait_for_start(10)
|
22
|
-
}.not_to raise_error
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|