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.
- data/lib/engineyard-serverside/cli.rb +46 -153
- data/lib/engineyard-serverside/cli_helpers.rb +53 -0
- data/lib/engineyard-serverside/configuration.rb +131 -161
- data/lib/engineyard-serverside/deploy.rb +69 -37
- data/lib/engineyard-serverside/deploy_hook.rb +31 -6
- data/lib/engineyard-serverside/paths.rb +106 -0
- data/lib/engineyard-serverside/server.rb +7 -59
- data/lib/engineyard-serverside/servers.rb +93 -0
- data/lib/engineyard-serverside/task.rb +4 -3
- data/lib/engineyard-serverside/version.rb +1 -1
- data/spec/basic_deploy_spec.rb +2 -2
- data/spec/bundler_deploy_spec.rb +6 -6
- data/spec/configuration_spec.rb +2 -1
- data/spec/custom_deploy_spec.rb +9 -4
- data/spec/deploy_hook_spec.rb +31 -15
- data/spec/ey_yml_customized_deploy_spec.rb +17 -15
- data/spec/fixtures/repos/assets_disabled/app/assets/empty +0 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/app/assets/empty +0 -0
- data/spec/fixtures/repos/assets_enabled/app/assets/empty +0 -0
- data/spec/fixtures/repos/assets_in_hook/app/assets/empty +0 -0
- data/spec/rails31_deploy_spec.rb +8 -8
- data/spec/restart_spec.rb +3 -2
- data/spec/rollback_spec.rb +61 -0
- data/spec/server_spec.rb +44 -50
- data/spec/spec_helper.rb +19 -12
- metadata +17 -4
@@ -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
|
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 =
|
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)
|
data/spec/basic_deploy_spec.rb
CHANGED
@@ -7,11 +7,11 @@ describe "Deploying a simple application" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "creates a REVISION file" do
|
10
|
-
|
10
|
+
deploy_dir.join('current', 'REVISION').should exist
|
11
11
|
end
|
12
12
|
|
13
13
|
it "restarts the app servers" do
|
14
|
-
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
|
data/spec/bundler_deploy_spec.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
data/spec/configuration_spec.rb
CHANGED
@@ -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' => {
|
data/spec/custom_deploy_spec.rb
CHANGED
@@ -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
|
-
|
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
|
data/spec/deploy_hook_spec.rb
CHANGED
@@ -7,16 +7,16 @@ describe "deploy hooks" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "runs all the hooks" do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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(
|
41
|
-
|
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(
|
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(
|
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
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
46
|
+
deploy_dir.join('current','maintenance_disabled').delete
|
47
47
|
@deployer.enable_maintenance_page
|
48
|
-
|
48
|
+
deploy_dir.join('shared','system','maintenance.html').should exist
|
49
49
|
redeploy_test_application
|
50
|
-
read_output.should =~ /Maintenance page is still up
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/spec/rails31_deploy_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe "Deploying a Rails 3.1 application" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "precompiles assets" do
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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
|