engineyard-serverside 2.0.0.pre3 → 2.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ require 'engineyard-serverside/server'
2
+ require 'forwardable'
3
+ require 'set'
4
+
5
+ module EY
6
+ module Serverside
7
+ class Servers
8
+
9
+ class DuplicateHostname < StandardError
10
+ def initialize(hostname)
11
+ super "EY::Serverside::Server '#{hostname}' duplicated!"
12
+ end
13
+ end
14
+
15
+ # Array compatibility
16
+ extend Forwardable
17
+ include Enumerable
18
+ def_delegators :@servers, :each, :size, :empty?
19
+ def select(*a, &b) self.class.new @servers.select(*a,&b) end
20
+ def reject(*a, &b) self.class.new @servers.reject(*a,&b) end
21
+ def to_a() @servers end
22
+ def ==(other) other.respond_to?(:to_a) && other.to_a == to_a end
23
+
24
+
25
+ def self.from_hashes(server_hashes)
26
+ servers = server_hashes.inject({}) do |memo, server_hash|
27
+ server = Server.from_hash(server_hash)
28
+ raise DuplicateHostname.new(server.hostname) if memo.key?(server.hostname)
29
+ memo[server.hostname] = server
30
+ memo
31
+ end
32
+ new(servers.values)
33
+ end
34
+
35
+ def initialize(servers)
36
+ @servers = servers
37
+ @cache = {}
38
+ end
39
+
40
+ def localhost
41
+ @servers.find {|server| server.local? }
42
+ end
43
+
44
+ def remote
45
+ reject { |server| server.local? }
46
+ end
47
+
48
+ # We look up the same set of servers over and over.
49
+ # Cache them so we don't have to find them every time
50
+ # Accepts a block (because it's confusing when you send a block to this
51
+ # method and it doesn't yield and it's better than raising)
52
+ def roles(*select_roles, &block)
53
+ if block_given?
54
+ return yield roles(*select_roles)
55
+ end
56
+
57
+ roles_set = Set.new select_roles.flatten.compact.map{|r| r.to_sym}
58
+ if roles_set.empty? || roles_set.include?(:all)
59
+ self
60
+ else
61
+ @cache[roles_set] ||= select { |server| server.matches_roles?(roles_set) }
62
+ end
63
+ end
64
+
65
+ # Run a command on this set of servers.
66
+ def run(cmd, &blk)
67
+ run_on_servers('sh -l -c', cmd, &blk)
68
+ end
69
+
70
+ # Run a sudo command on this set of servers.
71
+ def sudo(cmd, &blk)
72
+ run_on_servers('sudo sh -l -c', cmd, &blk)
73
+ end
74
+
75
+ private
76
+
77
+ def run_on_servers(prefix, cmd, &block)
78
+ commands = map do |server|
79
+ exec_cmd = server.command_on_server(prefix, cmd, &block)
80
+ proc { shell.logged_system(exec_cmd) }
81
+ end
82
+
83
+ futures = EY::Serverside::Future.call(commands)
84
+
85
+ unless EY::Serverside::Future.success?(futures)
86
+ failures = futures.select {|f| f.error? }.map {|f| f.inspect}.join("\n")
87
+ raise EY::Serverside::RemoteFailure.new(failures)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -3,10 +3,11 @@ require 'engineyard-serverside/shell/helpers'
3
3
  module EY
4
4
  module Serverside
5
5
  class Task
6
- attr_reader :config, :shell
6
+ attr_reader :servers, :config, :shell
7
7
  alias :c :config
8
8
 
9
- def initialize(conf, shell = nil)
9
+ def initialize(servers, conf, shell)
10
+ @servers = servers
10
11
  @config = conf
11
12
  @shell = shell
12
13
  @roles = :all
@@ -69,7 +70,7 @@ module EY
69
70
  private
70
71
 
71
72
  def run_on_roles(prefix, cmd, &block)
72
- servers = EY::Serverside::Server.from_roles(@roles)
73
+ servers = @servers.roles(@roles)
73
74
 
74
75
  commands = servers.map do |server|
75
76
  exec_cmd = server.command_on_server(prefix, cmd, &block)
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module Serverside
3
- VERSION = '2.0.0.pre3'
3
+ VERSION = '2.0.0.pre4'
4
4
  end
5
5
  end
@@ -7,11 +7,11 @@ describe "Deploying a simple application" do
7
7
  end
8
8
 
9
9
  it "creates a REVISION file" do
10
- @deploy_dir.join('current', 'REVISION').should exist
10
+ deploy_dir.join('current', 'REVISION').should exist
11
11
  end
12
12
 
13
13
  it "restarts the app servers" do
14
- restart = @deploy_dir.join('current', 'restart')
14
+ restart = deploy_dir.join('current', 'restart')
15
15
  restart.should exist
16
16
  restart.read.chomp.should == %|LANG="en_US.UTF-8" /engineyard/bin/app_rails31 deploy|
17
17
  end
@@ -34,19 +34,19 @@ describe "Deploying an application that uses Bundler" do
34
34
  end
35
35
 
36
36
  it "has the binstubs in the path when migrating" do
37
- File.read(File.join(@deploy_dir, 'path-when-migrating')).should include('ey_bundler_binstubs')
37
+ File.read(File.join(deploy_dir, 'path-when-migrating')).should include('ey_bundler_binstubs')
38
38
  end
39
39
 
40
40
  it "creates a ruby version file" do
41
- File.exist?(File.join(@deploy_dir, 'shared', 'bundled_gems', 'RUBY_VERSION')).should be_true
41
+ File.exist?(File.join(deploy_dir, 'shared', 'bundled_gems', 'RUBY_VERSION')).should be_true
42
42
  end
43
43
 
44
44
  it "creates a system version file" do
45
- File.exist?(File.join(@deploy_dir, 'shared', 'bundled_gems', 'SYSTEM_VERSION')).should be_true
45
+ File.exist?(File.join(deploy_dir, 'shared', 'bundled_gems', 'SYSTEM_VERSION')).should be_true
46
46
  end
47
47
 
48
48
  it "generates bundler binstubs" do
49
- File.exist?(File.join(@deploy_dir, 'current', 'ey_bundler_binstubs', 'rake')).should be_true
49
+ File.exist?(File.join(deploy_dir, 'current', 'ey_bundler_binstubs', 'rake')).should be_true
50
50
  end
51
51
  end
52
52
 
@@ -68,11 +68,11 @@ describe "Deploying an application that uses Bundler" do
68
68
  end
69
69
 
70
70
  it "creates a ruby version file" do
71
- File.exist?(File.join(@deploy_dir, 'shared', 'bundled_gems', 'RUBY_VERSION')).should be_true
71
+ File.exist?(File.join(deploy_dir, 'shared', 'bundled_gems', 'RUBY_VERSION')).should be_true
72
72
  end
73
73
 
74
74
  it "creates a system version file" do
75
- File.exist?(File.join(@deploy_dir, 'shared', 'bundled_gems', 'SYSTEM_VERSION')).should be_true
75
+ File.exist?(File.join(deploy_dir, 'shared', 'bundled_gems', 'SYSTEM_VERSION')).should be_true
76
76
  end
77
77
 
78
78
  it "sets GIT_SSH environment variable" do
@@ -6,6 +6,7 @@ describe EY::Serverside::Deploy::Configuration do
6
6
  @tempdir = `mktemp -d -t ey_yml_spec.XXXXX`.strip
7
7
  @config = EY::Serverside::Deploy::Configuration.new({
8
8
  'repository_cache' => @tempdir,
9
+ 'app' => 'app_name',
9
10
  'environment_name' => 'env_name',
10
11
  'account_name' => 'acc',
11
12
  'migrate' => nil,
@@ -13,7 +14,7 @@ describe EY::Serverside::Deploy::Configuration do
13
14
  'config' => {'custom' => 'custom_from_extra_config'}.to_json
14
15
  })
15
16
 
16
- @deploy = FullTestDeploy.new(@config, test_shell)
17
+ @deploy = FullTestDeploy.new(test_servers, @config, test_shell)
17
18
 
18
19
  @yaml_data = {
19
20
  'environments' => {
@@ -33,7 +33,12 @@ describe "the EY::Serverside::Deploy API" do
33
33
  def disable_maintenance_page() @call_order << 'disable_maintenance_page' end
34
34
  end
35
35
 
36
- td = TestDeploy.new(EY::Serverside::Deploy::Configuration.new, test_shell)
36
+ config = EY::Serverside::Deploy::Configuration.new({
37
+ 'app' => 'app_name',
38
+ 'framework_env' => 'staging',
39
+ })
40
+
41
+ td = TestDeploy.new(test_servers, config, test_shell)
37
42
  td.deploy
38
43
  td.call_order.should == %w(
39
44
  push_code
@@ -58,8 +63,8 @@ describe "the EY::Serverside::Deploy API" do
58
63
 
59
64
  before(:each) do
60
65
  @tempdir = `mktemp -d -t custom_deploy_spec.XXXXX`.strip
61
- @config = EY::Serverside::Deploy::Configuration.new('repository_cache' => @tempdir)
62
- @deploy = TestQuietDeploy.new(@config, test_shell)
66
+ @config = EY::Serverside::Deploy::Configuration.new('app' => 'app_name', 'repository_cache' => @tempdir)
67
+ @deploy = TestQuietDeploy.new(test_servers, @config, test_shell)
63
68
  end
64
69
 
65
70
  def write_eydeploy(relative_path, contents = "def got_new_methods() 'from the file on disk' end")
@@ -91,7 +96,7 @@ describe "the EY::Serverside::Deploy API" do
91
96
  def value() 'base' end
92
97
  end
93
98
 
94
- deploy = TestDeploySuper.new(@config, test_shell)
99
+ deploy = TestDeploySuper.new(test_servers, @config, test_shell)
95
100
  deploy.require_custom_tasks.should be_true
96
101
  deploy.value.should == "base + derived"
97
102
  end
@@ -7,16 +7,16 @@ describe "deploy hooks" do
7
7
  end
8
8
 
9
9
  it "runs all the hooks" do
10
- @deploy_dir.join('current', 'before_bundle.ran' ).should exist
11
- @deploy_dir.join('current', 'after_bundle.ran' ).should exist
12
- @deploy_dir.join('current', 'before_migrate.ran').should exist
13
- @deploy_dir.join('current', 'after_migrate.ran' ).should exist
14
- @deploy_dir.join('current', 'before_compile_assets.ran').should exist
15
- @deploy_dir.join('current', 'after_compile_assets.ran' ).should exist
16
- @deploy_dir.join('current', 'before_symlink.ran').should exist
17
- @deploy_dir.join('current', 'after_symlink.ran' ).should exist
18
- @deploy_dir.join('current', 'before_restart.ran').should exist
19
- @deploy_dir.join('current', 'after_restart.ran' ).should exist
10
+ deploy_dir.join('current', 'before_bundle.ran' ).should exist
11
+ deploy_dir.join('current', 'after_bundle.ran' ).should exist
12
+ deploy_dir.join('current', 'before_migrate.ran').should exist
13
+ deploy_dir.join('current', 'after_migrate.ran' ).should exist
14
+ deploy_dir.join('current', 'before_compile_assets.ran').should exist
15
+ deploy_dir.join('current', 'after_compile_assets.ran' ).should exist
16
+ deploy_dir.join('current', 'before_symlink.ran').should exist
17
+ deploy_dir.join('current', 'after_symlink.ran' ).should exist
18
+ deploy_dir.join('current', 'before_restart.ran').should exist
19
+ deploy_dir.join('current', 'after_restart.ran' ).should exist
20
20
  end
21
21
  end
22
22
 
@@ -30,15 +30,19 @@ describe "deploy hooks" do
30
30
 
31
31
  it "retains the failed release" do
32
32
  release_name = File.basename(@config.release_path)
33
- @deploy_dir.join('releases_failed', release_name).should be_directory
33
+ deploy_dir.join('releases_failed', release_name).should be_directory
34
34
  end
35
35
  end
36
36
 
37
37
  context "deploy hook API" do
38
38
 
39
39
  def deploy_hook(options={})
40
- config = EY::Serverside::Deploy::Configuration.new(options)
41
- EY::Serverside::DeployHook.new(config, test_shell)
40
+ config = EY::Serverside::Deploy::Configuration.new({
41
+ 'app' => 'app_name',
42
+ 'framework_env' => 'staging',
43
+ 'current_roles' => ['solo'],
44
+ }.merge(options))
45
+ EY::Serverside::DeployHook.new(config, test_shell, 'fake_test_hook')
42
46
  end
43
47
 
44
48
  context "#run" do
@@ -191,7 +195,7 @@ describe "deploy hooks" do
191
195
 
192
196
  def where_code_runs_with(code)
193
197
  scenarios.select do |role, name|
194
- hook = deploy_hook(:current_roles => role.split(','), :current_name => name)
198
+ hook = deploy_hook('current_roles' => role.split(','), 'current_name' => name)
195
199
  hook.eval_hook("#{code} { 'ran' } == 'ran'")
196
200
  end.map do |scenario|
197
201
  scenario.compact.join("_")
@@ -244,9 +248,21 @@ describe "deploy hooks" do
244
248
  end
245
249
  end
246
250
 
251
+ context "errors in hooks" do
252
+ it "shows the error in a helpful way" do
253
+ lambda {
254
+ deploy_hook.eval_hook('methedo_no_existo')
255
+ }.should raise_error(NameError)
256
+ out = read_output
257
+ out.should =~ %r|FATAL: Exception raised in deploy hook "/data/app_name/releases/\d+/deploy/fake_test_hook.rb".|
258
+ out.should =~ %r|NameError: undefined local variable or method `methedo_no_existo' for|
259
+ out.should =~ %r|Please fix this error before retrying.|
260
+ end
261
+ end
262
+
247
263
  context "is compatible with older deploy hook scripts" do
248
264
  it "#current_role returns the first role" do
249
- deploy_hook(:current_roles => %w(a b)).eval_hook('current_role').should == 'a'
265
+ deploy_hook('current_roles' => %w(a b)).eval_hook('current_role').should == 'a'
250
266
  end
251
267
 
252
268
  it "has info, warning, debug, logged_system, and access to shell" do
@@ -11,7 +11,7 @@ describe "Deploying an app with ey.yml" do
11
11
  end
12
12
 
13
13
  it "does not enable the maintenance page at all" do
14
- @deploy_dir.join('current','maintenance_disabled').should exist
14
+ deploy_dir.join('current','maintenance_disabled').should exist
15
15
  end
16
16
  end
17
17
 
@@ -24,12 +24,12 @@ describe "Deploying an app with ey.yml" do
24
24
  it "excludes copy_excludes from releases" do
25
25
  cmd = @deployer.commands.grep(/rsync -aq/).first
26
26
  cmd.should include('rsync -aq --exclude=".git" --exclude="README"')
27
- @deploy_dir.join('current', '.git').should_not exist
28
- @deploy_dir.join('current', 'README').should_not exist
27
+ deploy_dir.join('current', '.git').should_not exist
28
+ deploy_dir.join('current', 'README').should_not exist
29
29
  end
30
30
 
31
31
  it "loads ey.yml at lower priority than command line options" do
32
- @deploy_dir.join('current', 'REVISION').read.should == "somebranch\n"
32
+ deploy_dir.join('current', 'REVISION').read.should == "somebranch\n"
33
33
  end
34
34
 
35
35
  it "loads bundle_without from the config, which overrides the default" do
@@ -38,23 +38,25 @@ describe "Deploying an app with ey.yml" do
38
38
  end
39
39
 
40
40
  it "does not enable the maintenance page during migrations" do
41
- @deploy_dir.join('current','maintenance_disabled').should exist
42
- @deploy_dir.join('current','maintenance_enabled').should_not exist
41
+ deploy_dir.join('current','maintenance_disabled').should exist
42
+ deploy_dir.join('current','maintenance_enabled').should_not exist
43
43
  end
44
44
 
45
45
  it "does not remove an existing maintenance page" do
46
- @deploy_dir.join('current','maintenance_disabled').delete
46
+ deploy_dir.join('current','maintenance_disabled').delete
47
47
  @deployer.enable_maintenance_page
48
- @deploy_dir.join('shared','system','maintenance.html').should exist
48
+ deploy_dir.join('shared','system','maintenance.html').should exist
49
49
  redeploy_test_application
50
- read_output.should =~ /Maintenance page is still up. You must remove it manually./
51
- @deploy_dir.join('shared','system','maintenance.html').should exist
52
- @deploy_dir.join('current','maintenance_disabled').should_not exist
53
- @deploy_dir.join('current','maintenance_enabled').should exist
50
+ read_output.should =~ /Maintenance page is still up./
51
+ deploy_dir.join('shared','system','maintenance.html').should exist
52
+ deploy_dir.join('current','maintenance_disabled').should_not exist
53
+ deploy_dir.join('current','maintenance_enabled').should exist
54
+ @deployer.disable_maintenance_page
55
+ deploy_dir.join('shared','system','maintenance.html').should_not exist
54
56
  end
55
57
 
56
58
  it "makes custom variables available to hooks" do
57
- @deploy_dir.join('current', 'custom_hook').read.should include("custom_from_ey_yml")
59
+ deploy_dir.join('current', 'custom_hook').read.should include("custom_from_ey_yml")
58
60
  end
59
61
 
60
62
  it "doesn't display the database adapter warning with ignore_database_adapter_warning: true" do
@@ -68,8 +70,8 @@ describe "Deploying an app with ey.yml" do
68
70
  end
69
71
 
70
72
  it "always installs maintenance pages" do
71
- @deploy_dir.join('current','maintenance_enabled').should exist
72
- @deploy_dir.join('current','maintenance_disabled').should_not exist
73
+ deploy_dir.join('current','maintenance_enabled').should exist
74
+ deploy_dir.join('current','maintenance_disabled').should_not exist
73
75
  end
74
76
 
75
77
  it "displays the database adapter warning without ignore_database_adapter_warning" do
@@ -7,7 +7,7 @@ describe "Deploying a Rails 3.1 application" do
7
7
  end
8
8
 
9
9
  it "precompiles assets" do
10
- @deploy_dir.join('current', 'precompiled').should exist
10
+ deploy_dir.join('current', 'precompiled').should exist
11
11
  end
12
12
  end
13
13
 
@@ -17,7 +17,7 @@ describe "Deploying a Rails 3.1 application" do
17
17
  end
18
18
 
19
19
  it "precompiles assets" do
20
- @deploy_dir.join('current', 'precompiled').should exist
20
+ deploy_dir.join('current', 'precompiled').should exist
21
21
  end
22
22
  end
23
23
 
@@ -27,7 +27,7 @@ describe "Deploying a Rails 3.1 application" do
27
27
  end
28
28
 
29
29
  it "does not precompile assets" do
30
- @deploy_dir.join('current', 'precompiled').should_not exist
30
+ deploy_dir.join('current', 'precompiled').should_not exist
31
31
  end
32
32
  end
33
33
 
@@ -37,7 +37,7 @@ describe "Deploying a Rails 3.1 application" do
37
37
  end
38
38
 
39
39
  it "precompiles assets" do
40
- @deploy_dir.join('current', 'precompiled').should_not exist
40
+ deploy_dir.join('current', 'precompiled').should_not exist
41
41
  end
42
42
  end
43
43
 
@@ -47,10 +47,10 @@ describe "Deploying a Rails 3.1 application" do
47
47
  end
48
48
 
49
49
  it "does not replace the public/assets directory" do
50
- @deploy_dir.join('current', 'custom_compiled').should exist
51
- @deploy_dir.join('current', 'precompiled').should_not exist
52
- @deploy_dir.join('current', 'public', 'assets').should be_directory
53
- @deploy_dir.join('current', 'public', 'assets').should_not be_symlink
50
+ deploy_dir.join('current', 'custom_compiled').should exist
51
+ deploy_dir.join('current', 'precompiled').should_not exist
52
+ deploy_dir.join('current', 'public', 'assets').should be_directory
53
+ deploy_dir.join('current', 'public', 'assets').should_not be_symlink
54
54
  end
55
55
  end
56
56
  end
data/spec/restart_spec.rb CHANGED
@@ -20,7 +20,8 @@ describe "EY::Serverside::Deploy#restart_with_maintenance_page" do
20
20
  end
21
21
 
22
22
  it "puts up the maintenance page if necessary, restarts, and takes down the maintenance page" do
23
- deployer = TestRestartWithMaintenancePage.new(EY::Serverside::Deploy::Configuration.new)
23
+ config = EY::Serverside::Deploy::Configuration.new('app' => 'app_name')
24
+ deployer = TestRestartWithMaintenancePage.new(test_servers, config, test_shell)
24
25
  deployer.restart_with_maintenance_page
25
26
  deployer.call_order.should == %w(
26
27
  require_custom_tasks
@@ -35,7 +36,7 @@ describe "glassfish stack" do
35
36
 
36
37
  it "requires a maintenance page" do
37
38
  config = EY::Serverside::Deploy::Configuration.new(:stack => 'glassfish')
38
- deployer = TestRestartDeploy.new(config)
39
+ deployer = TestRestartDeploy.new(test_servers, config, test_shell)
39
40
  deployer.restart_with_maintenance_page
40
41
  deployer.call_order.should include('enable_maintenance_page')
41
42
  end