cumuli 0.3.4 → 0.4.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 +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
|