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