appserver 0.0.1 → 0.0.2

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.
@@ -1,18 +1,18 @@
1
1
  require File.expand_path('../../appserver', __FILE__)
2
- # We assume that the last argument to unicorn is the full path to config.ru
3
- Dir.chdir(File.expand_path('..', Unicorn::HttpServer::START_CTX[:argv][-1]))
4
- app = Appserver::Server.new.app(File.basename(Dir.pwd))
2
+ # We assume that the last argument to unicorn is the full path to the app's config.ru
3
+ app_path = File.dirname(Unicorn::HttpServer::START_CTX[:argv][-1])
4
+ app = Appserver::ServerDir.discover(app_path).app(File.basename(app_path))
5
5
 
6
- working_directory app.dir
6
+ working_directory app.path
7
7
  stderr_path app.server_log
8
8
  stdout_path app.server_log
9
- puts "Appserver unicorn configuration for #{app.dir}"
9
+ app.setup_env!
10
10
  pid app.pid_file
11
11
  listen "unix:#{app.socket}", :backlog => 64
12
- #user 'user', 'group'
12
+ user app.user, app.group
13
13
  worker_processes app.instances
14
+ preload_app app.preload
14
15
  timeout 30
15
- preload_app true
16
16
 
17
17
  # Use COW-friendly REE for memory saving, especially with preloaded apps
18
18
  # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
@@ -25,26 +25,22 @@ before_fork do |server, worker|
25
25
  ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
26
26
  end
27
27
 
28
- # The following is only recommended for memory/DB-constrained
29
- # installations. It is not needed if your system can house
30
- # twice as many worker_processes as you have configured.
31
- #
32
- # # This allows a new master process to incrementally
33
- # # phase out the old master process with SIGTTOU to avoid a
34
- # # thundering herd (especially in the "preload_app false" case)
35
- # # when doing a transparent upgrade. The last worker spawned
36
- # # will then kill off the old master process with a SIGQUIT.
37
- # old_pid = "#{server.config[:pid]}.oldbin"
38
- # if old_pid != server.pid
39
- # begin
40
- # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
41
- # Process.kill(sig, File.read(old_pid).to_i)
42
- # rescue Errno::ENOENT, Errno::ESRCH
43
- # end
44
- # end
45
- #
46
- # # *optionally* throttle the master from forking too quickly by sleeping
47
- # sleep 1
28
+ # This allows a new master process to incrementally
29
+ # phase out the old master process with SIGTTOU to avoid a
30
+ # thundering herd (especially in the "preload_app false" case)
31
+ # when doing a transparent upgrade. The last worker spawned
32
+ # will then kill off the old master process with a SIGQUIT.
33
+ old_pid = "#{server.config[:pid]}.oldbin"
34
+ if old_pid != server.pid
35
+ begin
36
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
37
+ Process.kill(sig, File.read(old_pid).to_i)
38
+ rescue Errno::ENOENT, Errno::ESRCH
39
+ end
40
+ end
41
+
42
+ # *optionally* throttle the master from forking too quickly by sleeping
43
+ sleep 1
48
44
  end
49
45
 
50
46
  after_fork do |server, worker|
@@ -1,15 +1,32 @@
1
+ require 'socket'
1
2
  require 'tempfile'
2
3
 
3
4
  module Appserver
4
5
  module Utils
5
- def self.included (base)
6
- base.class_eval do
7
- extend Methods
8
- include Methods
6
+
7
+ class << self
8
+ def find_in_path (name)
9
+ ENV['PATH'].split(':').each do |path|
10
+ fn = File.join(path, name)
11
+ return fn if File.executable?(fn)
12
+ end
13
+ nil
14
+ end
15
+
16
+ def system_hostname
17
+ Socket.gethostname
18
+ end
19
+
20
+ def system_domainname
21
+ system_hostname.sub(/^[^.]+\./, '')
22
+ end
23
+
24
+ def number_of_cpus
25
+ if File.exist?('/proc/cpuinfo')
26
+ File.readlines('/proc/cpuinfo').grep(/^processor\s+:\s+\d+/).size
27
+ end
9
28
  end
10
- end
11
29
 
12
- module Methods
13
30
  def safe_replace_file (filename)
14
31
  tempfile = Tempfile.new(File.basename(filename) + '.', File.dirname(filename))
15
32
  if File.exist?(filename)
@@ -19,15 +36,7 @@ module Appserver
19
36
  yield tempfile
20
37
  tempfile.close
21
38
  File.unlink(filename) if File.exist?(filename)
22
- File.rename(tempfile, filename)
23
- end
24
-
25
- def symbolize_keys (hash)
26
- hash.inject({}) do |memo, (key, value)|
27
- value = symbolize_keys(value) if Hash === value
28
- memo[key.to_sym] = value
29
- memo
30
- end
39
+ File.rename(tempfile.path, filename)
31
40
  end
32
41
  end
33
42
  end
@@ -0,0 +1,7 @@
1
+ class HelloWorld
2
+ def call (env)
3
+ [200, { 'Content-Type' => 'text/plain' }, ['Hello world!']]
4
+ end
5
+ end
6
+
7
+ run HelloWorld.new
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ gem 'sinatra', '>= 1.0'
4
+ gem 'sinatra-erb'
@@ -0,0 +1,2 @@
1
+ require ::File.expand_path('../hello', __FILE__)
2
+ run Sinatra::Application
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+ require 'erb'
4
+
5
+ set :app_file, __FILE__
6
+
7
+ get '/' do
8
+ erb :index
9
+ end
@@ -0,0 +1 @@
1
+ Hello world from Sinatra
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ gem 'test-unit'
3
+ require 'test/unit'
4
+ require 'mocha'
5
+
6
+ require 'appserver'
7
+ require 'tmpdir'
8
+ require 'git'
9
+
10
+ class Test::Unit::TestCase
11
+
12
+ SAMPLE_APPS_PATH = File.expand_path('../apps', __FILE__)
13
+
14
+ # Runs the given block in an empty, temporary direcory
15
+ def in_empty_dir (&block)
16
+ Dir.mktmpdir do |tmpdir|
17
+ Dir.chdir(tmpdir) do
18
+ yield
19
+ end
20
+ end
21
+ end
22
+
23
+ # Runs the given block in an initialized appserver directory
24
+ def in_server_dir (&block)
25
+ in_empty_dir do
26
+ yield Appserver::ServerDir.init('.', :force => true)
27
+ end
28
+ end
29
+
30
+ # Copies the named sample app to the given path
31
+ def create_app (name, path)
32
+ raise 'Target path already exist' if File.exist?(path)
33
+ FileUtils.cp_r File.join(SAMPLE_APPS_PATH, name), path
34
+ end
35
+
36
+ # Creates a bare git repository of the named sample app
37
+ def create_app_repo (name, path)
38
+ raise 'Target path already exist' if File.exist?(path)
39
+ path = File.expand_path(path)
40
+ Dir.chdir(File.join(SAMPLE_APPS_PATH, name)) do
41
+ raise 'Already a git repository' if File.exist?('.git')
42
+ git = Git.init('.')
43
+ git.add('.')
44
+ git.commit('Initial commit')
45
+ git.config('core.bare', true)
46
+ FileUtils.mv '.git', path
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ require 'helper'
2
+ require 'bundler'
3
+
4
+ class TestApp < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @env = { 'PATH' => '/some/where:/no/where', 'FOO_KEY' => 'FooFoo', 'MY_DIR' => '/no/where' }
8
+ Appserver::App.send(:const_set, :ENV, @env)
9
+ end
10
+
11
+ def teardown
12
+ Appserver::App.send(:remove_const, :ENV)
13
+ end
14
+
15
+ def test_default_hostname
16
+ in_server_dir do |server_dir|
17
+ Appserver::App::SETTINGS_DEFAULTS[:domain] = 'example.com'
18
+ app = server_dir.app('myapp')
19
+ assert_equal 'example.com', app.domain
20
+ assert_equal 'myapp.example.com', app.hostname
21
+ end
22
+ end
23
+
24
+ def test_setup_env_default_environment
25
+ in_server_dir do |server_dir|
26
+ app = server_dir.app('myapp')
27
+ assert_equal([], app.env_whitelist)
28
+ assert_equal({}, app.env)
29
+ app.setup_env!
30
+ assert_equal({ 'PATH' => '/some/where:/no/where' }, @env)
31
+ end
32
+ end
33
+
34
+ def test_setup_env_whitelist
35
+ in_server_dir do |server_dir|
36
+ app = server_dir.app('myapp')
37
+ app.stubs(:env_whitelist => ['MY_DIR'])
38
+ app.setup_env!
39
+ assert_equal({ 'PATH' => '/some/where:/no/where', 'MY_DIR' => '/no/where' }, @env)
40
+ end
41
+ end
42
+
43
+ def test_setup_env_full_environment
44
+ in_server_dir do |server_dir|
45
+ app = server_dir.app('myapp')
46
+ app.stubs(:env_whitelist => '*')
47
+ app.setup_env!
48
+ assert_equal({ 'PATH' => '/some/where:/no/where', 'FOO_KEY' => 'FooFoo', 'MY_DIR' => '/no/where' }, @env)
49
+ end
50
+ end
51
+
52
+ def test_setup_env_additions
53
+ in_server_dir do |server_dir|
54
+ app = server_dir.app('myapp')
55
+ app.stubs(:env => { 'SOME_KEY' => 'secret' })
56
+ app.setup_env!
57
+ assert_equal({ 'PATH' => '/some/where:/no/where', 'SOME_KEY' => 'secret' }, @env)
58
+ end
59
+ end
60
+
61
+ def test_setup_env_sets_up_gem_bundle
62
+ in_server_dir do |server_dir|
63
+ app = server_dir.app('myapp')
64
+ File.stubs(:exist?).with(app.gem_file).returns(true)
65
+ File.stubs(:directory?).with(app.bundle_path).returns(true)
66
+ Bundler.expects(:setup)
67
+ app.setup_env!
68
+ assert_not_nil @env['BUNDLE_PATH']
69
+ assert_not_nil @env['GEM_HOME']
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ require 'helper'
2
+
3
+ class TestAppserver < Test::Unit::TestCase
4
+
5
+ def appserver (*args)
6
+ cmd = File.expand_path('../../../bin/appserver', __FILE__)
7
+ bind = TOPLEVEL_BINDING.dup
8
+ bind.eval("Object.send(:remove_const, :ARGV); ARGV = #{args.inspect}", __FILE__, __LINE__)
9
+ bind.eval(File.read(cmd), cmd)
10
+ end
11
+
12
+ def test_command
13
+ Appserver::Command.expects(:run!).with('doit', ['anarg', 'anotherarg'], {})
14
+ appserver('doit', 'anarg', 'anotherarg')
15
+ end
16
+
17
+ def test_dir_option
18
+ Appserver::Command.expects(:run!).with('doit', [], { :dir => '/path/to/anywhere' })
19
+ appserver('--dir', '/path/to/anywhere', 'doit')
20
+ end
21
+
22
+ def test_force_option
23
+ Appserver::Command.expects(:run!).with('doit', [], { :force => true })
24
+ appserver('--force', 'doit')
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ require 'helper'
2
+
3
+ class TestCommand < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @app = stub('App', :branch => 'thebranch')
7
+ @repository = stub('Repository', :app => @app)
8
+ @server_dir = stub('ServerDir', :repository => @repository)
9
+ Appserver::ServerDir.stubs(:discover => @server_dir)
10
+ # FIXME: This is currently needed to silence appserver output during tests :(
11
+ Appserver::Command.any_instance.stubs(:puts)
12
+ end
13
+
14
+ def test_unknown_command
15
+ assert_raise Appserver::UnknownCommandError do
16
+ Appserver::Command.run!('foo', [])
17
+ end
18
+ end
19
+
20
+ def test_dir_option
21
+ Dir.expects(:chdir).with('thedir')
22
+ @server_dir.stubs(:write_configs)
23
+ Appserver::Command.run!('update', [], :dir => 'thedir')
24
+ end
25
+
26
+ def test_init
27
+ Appserver::ServerDir.expects(:init).with('thedir', {})
28
+ Dir.expects(:chdir).with('thedir')
29
+ @server_dir.expects(:write_configs)
30
+ Appserver::Command.run!('init', ['thedir'])
31
+ end
32
+
33
+ def test_command_discovers
34
+ Appserver::ServerDir.expects(:discover).returns(@server_dir)
35
+ @server_dir.expects(:write_configs)
36
+ Appserver::Command.run!('update', [])
37
+ end
38
+
39
+ def test_update
40
+ @server_dir.expects(:write_configs)
41
+ Appserver::Command.run!('update', [])
42
+ end
43
+
44
+ def test_deploy_manually
45
+ @server_dir.expects(:repository).with('repo.git').returns(@repository)
46
+ @repository.expects(:install_hook)
47
+ @repository.expects(:deploy).with('thebranch')
48
+ Appserver::Command.run!('deploy', ['repo.git'])
49
+ end
50
+
51
+ def test_deploy_on_update
52
+ @server_dir.expects(:repository).with('repo.git').returns(@repository)
53
+ @repository.expects(:install_hook)
54
+ @repository.expects(:deploy).with('0123456789abcdef')
55
+ Appserver::Command.run!('deploy', ['repo.git', '/refs/heads/thebranch', '0123456789abcdef'])
56
+ end
57
+
58
+ def test_deploy_on_update_of_different_branch_does_nothing
59
+ @server_dir.expects(:repository).with('repo.git').returns(@repository)
60
+ @repository.expects(:install_hook)
61
+ @repository.expects(:deploy).never
62
+ Appserver::Command.run!('deploy', ['repo.git', '/refs/heads/xyz', '0123456789abcdef'])
63
+ end
64
+ end
@@ -0,0 +1,116 @@
1
+ require 'helper'
2
+
3
+ class TestConfigurator < Test::Unit::TestCase
4
+
5
+ DEFAULTS = {
6
+ :alpha => 'aaa',
7
+ :beta => 'bbb',
8
+ :gamma => 'ccc',
9
+ :delta => 'ddd',
10
+ }
11
+
12
+ CONFIG_TEXT = <<-EOF
13
+ alpha 'anna'
14
+ beta 'bob'
15
+ context 'weird' do
16
+ beta 'betty'
17
+ gamma 'georg'
18
+ end
19
+ EOF
20
+
21
+ class ConfigTarget < Struct.new(:alpha, :beta, :gamma, :delta)
22
+ SETTINGS_DEFAULTS = DEFAULTS
23
+ SETTINGS_EXPAND = []
24
+ end
25
+
26
+ def configurator (config_text, *args)
27
+ config_file = '/tmp/some/path/test.conf.rb'
28
+ File.stubs(:read).with(config_file).returns(config_text)
29
+ Appserver::Configurator.new(config_file, *args)
30
+ end
31
+
32
+ def setup
33
+ @config = configurator(CONFIG_TEXT)
34
+ end
35
+
36
+ def test_default_setting
37
+ assert_no_match /delta/, CONFIG_TEXT
38
+ @config.apply!(target = ConfigTarget.new)
39
+ assert_equal DEFAULTS[:delta], target.delta
40
+ @config.apply!(target = ConfigTarget.new, 'weird')
41
+ assert_equal DEFAULTS[:delta], target.delta
42
+ @config.apply!(target = ConfigTarget.new, 'away')
43
+ assert_equal DEFAULTS[:delta], target.delta
44
+ end
45
+
46
+ def test_global_setting_is_global
47
+ @config.apply!(target = ConfigTarget.new)
48
+ assert_equal 'anna', target.alpha
49
+ @config.apply!(target = ConfigTarget.new, 'weird')
50
+ assert_equal 'anna', target.alpha
51
+ @config.apply!(target = ConfigTarget.new, 'away')
52
+ assert_equal 'anna', target.alpha
53
+ end
54
+
55
+ def test_context_setting_overrides_global_setting
56
+ @config.apply!(target = ConfigTarget.new)
57
+ assert_equal 'bob', target.beta
58
+ @config.apply!(target = ConfigTarget.new, 'weird')
59
+ assert_equal 'betty', target.beta
60
+ @config.apply!(target = ConfigTarget.new, 'away')
61
+ assert_equal 'bob', target.beta
62
+ end
63
+
64
+ def test_context_setting_overrides_default
65
+ @config.apply!(target = ConfigTarget.new)
66
+ assert_equal DEFAULTS[:gamma], target.gamma
67
+ @config.apply!(target = ConfigTarget.new, 'weird')
68
+ assert_equal 'georg', target.gamma
69
+ @config.apply!(target = ConfigTarget.new, 'away')
70
+ assert_equal DEFAULTS[:gamma], target.gamma
71
+ end
72
+
73
+ class ConfigTargetWithExpand < ConfigTarget
74
+ SETTINGS_EXPAND = [ :beta, :gamma ]
75
+ def path; '/tmp/some/path'; end
76
+ end
77
+
78
+ def test_settings_expand
79
+ @config.apply!(target = ConfigTargetWithExpand.new)
80
+ assert_equal 'anna', target.alpha
81
+ assert_equal '/tmp/some/path/bob', target.beta
82
+ assert_equal '/tmp/some/path/ccc', target.gamma
83
+ assert_equal 'ddd', target.delta
84
+ end
85
+
86
+ def test_any_setting_is_possible_by_default
87
+ config = configurator("alpha 'anna'; beta 'bob'")
88
+ config.apply!(target = ConfigTarget.new)
89
+ assert_equal 'anna', target.alpha
90
+ assert_equal 'bob', target.beta
91
+ end
92
+
93
+ def test_allowed_global_settings
94
+ assert_nothing_raised do
95
+ configurator("alpha 'anna'; beta 'bob'", [ :alpha, :beta ])
96
+ end
97
+ end
98
+
99
+ def test_allowed_global_settings_fail_on_invalid_setting
100
+ assert_raise NoMethodError do
101
+ configurator("alpha 'anna'; beta 'bob'", [ :alpha ])
102
+ end
103
+ end
104
+
105
+ def test_allowed_context_settings
106
+ assert_nothing_raised do
107
+ configurator("context 'foo' do; alpha 'anna'; beta 'bob'; end", nil, [ :alpha, :beta ])
108
+ end
109
+ end
110
+
111
+ def test_allowed_context_settings_fail_on_invalid_setting
112
+ assert_raise NoMethodError do
113
+ configurator("context 'foo' do; alpha 'anna'; beta 'bob'; end", nil, [ :alpha ])
114
+ end
115
+ end
116
+ end