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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e61d96a9203fd5fed28980e2d3817ab7afcdfd5a
4
- data.tar.gz: 0721f35292c999e27c9e7ad25904c93b2d59f353
3
+ metadata.gz: 99ef0de4fd4c50b58aaeb4ebe381eea7f06ebba6
4
+ data.tar.gz: 90149d60a66ab52a9b0769e611706d673fea480d
5
5
  SHA512:
6
- metadata.gz: 557916c9a94781c074aad2eb6646a7d7cf99755f17a4c9800d0a89ae45061a0bc58ba4725700fe018a88b81ab653a21caaa53c0990701ed9583ea30278b203bf
7
- data.tar.gz: c83034cfc14815ca7d13d9b3aed3a109993ddaeb5be4d3ff6ed4f3777514038477a9f6155f2a097c8d9ee2843c4edc97a62e47fac1a1dcb57c833c4cd2306aa4
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
- If you start the Cumuli app and stop it the first time, it will successfully start and stop all processes. However, if you use the same Cumuli app class to
94
- start and stop the processes again, it will fail to stop any of the
95
- processes.
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
 
@@ -1,5 +1,5 @@
1
1
  Given(/^the app suite is loaded$/) do
2
- @app = Cumuli::App.new({
2
+ @app = Cumuli::Spawner::App.new({
3
3
  env: 'test',
4
4
  wait_time: 5,
5
5
  log_dir: File.dirname(__FILE__) + "/../../spec/fixtures/log",
@@ -25,7 +25,7 @@ module Cumuli
25
25
 
26
26
  def get_passed_command
27
27
  matched = argv.first.match(/\[(.*)\]/)
28
- matched && matched[1]
28
+ (matched && matched[1]) || argv.first
29
29
  end
30
30
 
31
31
  def command
@@ -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,2 @@
1
+ require "cumuli/project_manager/project"
2
+ require "cumuli/project_manager/manager"
@@ -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,6 +1,6 @@
1
1
  module Cumuli
2
- class App
3
- class Spawner
2
+ module Spawner
3
+ class ForemanProc
4
4
  attr_reader :pid, :env, :log_dir
5
5
 
6
6
  def initialize(env, log_dir)
@@ -1,5 +1,5 @@
1
1
  module Cumuli
2
- class App
2
+ module Spawner
3
3
  class StdoutLogger
4
4
  GRAY = "\033[0;37m"
5
5
  RESET = "\033[0m"
@@ -0,0 +1,4 @@
1
+ require "cumuli/spawner/app"
2
+ require "cumuli/spawner/foreman_proc"
3
+ require "cumuli/spawner/stdout_logger"
4
+
@@ -1,42 +1,53 @@
1
- require_relative "../ps"
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 :kill do
8
- desc "kill all foreman related processes"
9
- task :all do
10
- ps = Cumuli::PS.new
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 "kill the first spawned foreman process"
15
- task :root do
16
- ps = Cumuli::PS.new
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
- desc "kill the root process"
22
- task :kill => ['cumuli:kill:root']
23
-
24
- namespace :int do
25
- desc "interrupt all foreman related processes"
26
- task :all do
27
- ps = Cumuli::PS.new
28
- ps.int(ps.foremans.map(&:pid))
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 first spawned foreman process"
32
- task :root do
33
- ps = Cumuli::PS.new
34
- ps.int
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
- desc "interrupt the root process"
39
- task :int => ['cumuli:int:root']
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
@@ -1,3 +1,3 @@
1
1
  module Cumuli
2
- VERSION = "0.3.4"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/cumuli.rb CHANGED
@@ -11,5 +11,6 @@ require "cumuli/facade"
11
11
  require "cumuli/ps"
12
12
  require "cumuli/waiter"
13
13
  require "cumuli/cli"
14
- require "cumuli/app"
14
+ require "cumuli/spawner"
15
+ require "cumuli/project_manager"
15
16
  require "cumuli/version"
@@ -0,0 +1,6 @@
1
+ loopy:
2
+ type: service
3
+ nodified:
4
+ path: ./noded
5
+ port: 2323
6
+
@@ -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.3.4
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-25 00:00:00.000000000 Z
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
@@ -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
@@ -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
@@ -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
@@ -1,6 +0,0 @@
1
- require "cumuli/app/app"
2
- require "cumuli/app/spawner"
3
- require "cumuli/app/stdout_logger"
4
- require "cumuli/app/procs"
5
- require "cumuli/app/sub_app"
6
-
@@ -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
@@ -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