engineyard-serverside 2.0.0.pre3 → 2.0.0.pre4

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