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