engineyard-serverside 2.0.0.pre4 → 2.0.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,7 +34,25 @@ module EY
34
34
  EY::Serverside::Deploy.new(servers, config, shell).send(default_task)
35
35
  end
36
36
 
37
+ account_app_env_options
38
+ config_option
39
+ instances_options
40
+ verbose_option
41
+ desc "enable_maintenance", "Enable maintenance page (disables web access)"
42
+ def enable_maintenance
43
+ servers, config, shell = init_and_propagate(options, 'enable_maintenance')
44
+ EY::Serverside::Maintenance.new(servers, config, shell).manually_enable
45
+ end
37
46
 
47
+ account_app_env_options
48
+ config_option
49
+ instances_options
50
+ verbose_option
51
+ desc "disable_maintenance", "Disable maintenance page (enables web access)"
52
+ def disable_maintenance
53
+ servers, config, shell = init_and_propagate(options, 'disable_maintenance')
54
+ EY::Serverside::Maintenance.new(servers, config, shell).manually_disable
55
+ end
38
56
 
39
57
  method_option :release_path, :type => :string,
40
58
  :desc => "Value for #release_path in hooks (mostly for internal coordination)",
@@ -95,7 +113,6 @@ module EY
95
113
  desc "restart", "Restart app servers, conditionally enabling maintenance page"
96
114
  def restart
97
115
  servers, config, shell = init_and_propagate(options, 'restart')
98
-
99
116
  EY::Serverside::Deploy.new(servers, config, shell).restart_with_maintenance_page
100
117
  end
101
118
 
@@ -3,6 +3,7 @@ require 'base64'
3
3
  require 'fileutils'
4
4
  require 'json'
5
5
  require 'engineyard-serverside/rails_asset_support'
6
+ require 'engineyard-serverside/maintenance'
6
7
 
7
8
  module EY
8
9
  module Serverside
@@ -33,7 +34,7 @@ module EY
33
34
  check_for_ey_config
34
35
  symlink_configs
35
36
  setup_sqlite3_if_necessary
36
- conditionally_enable_maintenance_page
37
+ enable_maintenance_page
37
38
  run_with_callbacks(:migrate)
38
39
  run_with_callbacks(:compile_assets) # defined in RailsAssetSupport
39
40
  callback(:before_symlink)
@@ -44,7 +45,7 @@ module EY
44
45
 
45
46
  callback(:after_symlink)
46
47
  run_with_callbacks(:restart)
47
- conditionally_disable_maintenance_page
48
+ disable_maintenance_page
48
49
 
49
50
  cleanup_old_releases
50
51
  shell.status "Finished deploy at #{Time.now.asctime}"
@@ -117,79 +118,17 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
117
118
 
118
119
  def restart_with_maintenance_page
119
120
  require_custom_tasks
120
- with_maintenance_page { restart }
121
+ enable_maintenance_page
122
+ restart
123
+ disable_maintenance_page
121
124
  end
122
125
 
123
126
  def enable_maintenance_page
124
- maintenance_page_candidates = [
125
- "public/maintenance.html.custom",
126
- "public/maintenance.html.tmp",
127
- "public/maintenance.html",
128
- "public/system/maintenance.html.default",
129
- ].map do |file|
130
- File.join(c.latest_release, file)
131
- end
132
-
133
- # this one is guaranteed to exist
134
- maintenance_page_candidates << File.expand_path("default_maintenance_page.html", File.dirname(__FILE__))
135
-
136
- # put in the maintenance page
137
- maintenance_file = maintenance_page_candidates.detect do |file|
138
- File.exists?(file)
139
- end
140
-
141
- shell.status "Enabling maintenance page."
142
- @maintenance_up = true
143
- roles :app_master, :app, :solo do
144
- run Escape.shell_command(['mkdir', '-p', File.dirname(c.maintenance_page_enabled_path)])
145
- run Escape.shell_command(['cp', maintenance_file, c.maintenance_page_enabled_path])
146
- end
147
- end
148
-
149
- def conditionally_enable_maintenance_page
150
- if c.enable_maintenance_page?
151
- enable_maintenance_page
152
- else
153
- explain_not_enabling_maintenance_page
154
- end
155
- end
156
-
157
- def explain_not_enabling_maintenance_page
158
- if c.migrate?
159
- if !c.maintenance_on_migrate? && !c.maintenance_on_restart?
160
- shell.status "Skipping maintenance page. (maintenance_on_migrate is false in ey.yml)"
161
- shell.notice "[Caution] No maintenance migrations must be non-destructive!"
162
- shell.notice "Requests may be served during a partially migrated state."
163
- end
164
- else
165
- if c.required_downtime_stack? && !c.maintenance_on_restart?
166
- shell.status "Skipping maintenance page. (maintenance_on_restart is false in ey.yml, overriding recommended default)"
167
- unless File.exist?(c.maintenance_page_enabled_path)
168
- shell.warning <<-WARN
169
- No maintenance page! Brief downtime may be possible during restart.
170
- This application stack does not support no-downtime restarts.
171
- WARN
172
- end
173
- elsif !c.required_downtime_stack?
174
- shell.status "Skipping maintenance page. (no-downtime restarts supported)"
175
- end
176
- end
127
+ maintenance.conditionally_enable
177
128
  end
178
129
 
179
130
  def disable_maintenance_page
180
- shell.status "Removing maintenance page."
181
- @maintenance_up = false
182
- roles :app_master, :app, :solo do
183
- run "rm -f #{c.maintenance_page_enabled_path}"
184
- end
185
- end
186
-
187
- def conditionally_disable_maintenance_page
188
- if c.disable_maintenance_page?
189
- disable_maintenance_page
190
- elsif File.exists?(c.maintenance_page_enabled_path)
191
- shell.notice "[Attention] Maintenance page is still up.\nYou must remove it manually using `ey web enable`."
192
- end
131
+ maintenance.conditionally_disable
193
132
  end
194
133
 
195
134
  def run_with_callbacks(task)
@@ -296,7 +235,9 @@ chmod 0700 #{path}
296
235
  sudo "rm -rf #{rolled_back_release}"
297
236
  bundle
298
237
  shell.status "Restarting with previous release."
299
- with_maintenance_page { run_with_callbacks(:restart) }
238
+ enable_maintenance_page
239
+ run_with_callbacks(:restart)
240
+ disable_maintenance_page
300
241
  shell.status "Finished rollback at #{Time.now.asctime}"
301
242
  rescue Exception
302
243
  shell.status "Failed to rollback at #{Time.now.asctime}"
@@ -483,7 +424,7 @@ WRAP
483
424
  def puts_deploy_failure
484
425
  if @cleanup_failed
485
426
  shell.notice "[Relax] Your site is running new code, but clean up of old deploys failed."
486
- elsif @maintenance_up
427
+ elsif maintenance.up?
487
428
  message = "[Attention] Maintenance page still up, consider the following before removing:\n"
488
429
  message << " * Deploy hooks ran. This might cause problems for reverting to old code.\n" if @callbacks_reached
489
430
  message << " * Migrations ran. This might cause problems for reverting to old code.\n" if @migrations_reached
@@ -501,10 +442,8 @@ WRAP
501
442
  end
502
443
  end
503
444
 
504
- def with_maintenance_page
505
- conditionally_enable_maintenance_page
506
- yield if block_given?
507
- conditionally_disable_maintenance_page
445
+ def maintenance
446
+ @maintenance ||= Maintenance.new(servers, config, shell)
508
447
  end
509
448
 
510
449
  def with_failed_release_cleanup
@@ -0,0 +1,112 @@
1
+ module EY
2
+ module Serverside
3
+ class Maintenance
4
+
5
+ def initialize(servers, config, shell)
6
+ @servers, @config, @shell = servers, config, shell
7
+ end
8
+
9
+ def exist?
10
+ enabled_maintenance_page_pathname.exist?
11
+ end
12
+
13
+ def up?
14
+ @up
15
+ end
16
+
17
+ def manually_enable
18
+ if paths.deployed?
19
+ enable
20
+ else
21
+ shell.fatal "Cannot enabled maintenance page. Application #{config.app_name} has never been deployed."
22
+ false
23
+ end
24
+ end
25
+
26
+ def manually_disable
27
+ if paths.deployed?
28
+ disable
29
+ else
30
+ shell.fatal "Cannot enabled maintenance page. Application #{config.app_name} has never been deployed."
31
+ false
32
+ end
33
+ end
34
+
35
+ def conditionally_enable
36
+ if config.enable_maintenance_page?
37
+ enable
38
+ else
39
+ explain_not_enabling
40
+ end
41
+ end
42
+
43
+ def conditionally_disable
44
+ if config.disable_maintenance_page?
45
+ disable
46
+ elsif exist?
47
+ shell.notice "[Attention] Maintenance page is still up.\nYou must remove it manually using `ey web enable`."
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ attr_reader :config, :shell
54
+
55
+ def enable
56
+ shell.status "Enabling maintenance page."
57
+ @up = true
58
+ run "mkdir -p #{maintenance_page_dirname}"
59
+ run "cp #{source_path} #{enabled_maintenance_page_pathname}"
60
+ end
61
+
62
+ def disable
63
+ shell.status "Removing maintenance page."
64
+ @up = false
65
+ run "rm -f #{enabled_maintenance_page_pathname}"
66
+ end
67
+
68
+ def run(cmd)
69
+ @servers.roles(:app_master, :app, :solo).run(shell, cmd)
70
+ end
71
+
72
+ def paths
73
+ config.paths
74
+ end
75
+
76
+ def source_path
77
+ paths.maintenance_page_candidates.detect {|path| path.exist? }
78
+ end
79
+
80
+ def enabled_maintenance_page_pathname
81
+ paths.enabled_maintenance_page
82
+ end
83
+
84
+ def maintenance_page_dirname
85
+ enabled_maintenance_page_pathname.dirname
86
+ end
87
+
88
+ def explain_not_enabling
89
+ if config.migrate?
90
+ if !config.maintenance_on_migrate? && !config.maintenance_on_restart?
91
+ shell.status "Skipping maintenance page. (maintenance_on_migrate is false in ey.yml)"
92
+ shell.notice "[Caution] No maintenance migrations must be non-destructive!"
93
+ shell.notice "Requests may be served during a partially migrated state."
94
+ end
95
+ else
96
+ if config.required_downtime_stack? && !config.maintenance_on_restart?
97
+ shell.status "Skipping maintenance page. (maintenance_on_restart is false in ey.yml, overriding recommended default)"
98
+ unless exist?
99
+ shell.warning <<-WARN
100
+ No maintenance page! Brief downtime may be possible during restart.
101
+ This application stack does not support no-downtime restarts.
102
+ WARN
103
+ end
104
+ elsif !config.required_downtime_stack?
105
+ shell.status "Skipping maintenance page. (no-downtime restarts supported)"
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -27,10 +27,23 @@ module EY
27
27
  def ssh_identity_file() paths.ssh_identity.to_s end
28
28
  end
29
29
 
30
+ # Maintenance page candidates in order of search preference.
31
+ MAINTENANCE_CANDIDATES = [
32
+ "public/maintenance.html.custom",
33
+ "public/maintenance.html.tmp",
34
+ "public/maintenance.html",
35
+ "public/system/maintenance.html.default",
36
+ ]
37
+
38
+ # This one is guaranteed to exist.
39
+ DEFAULT_MAINTENANCE_PAGE = Pathname.new("default_maintenance_page.html").expand_path(File.dirname(__FILE__))
40
+
41
+ # Define methods that get us paths
30
42
  def self.def_path(name, parts)
31
43
  define_method(name.to_sym) { path(*parts) }
32
44
  end
33
45
 
46
+ # Load a path given a root and more parts
34
47
  def path(root, *parts)
35
48
  send(root).join(*parts)
36
49
  end
@@ -93,8 +106,24 @@ module EY
93
106
  all_releases.last
94
107
  end
95
108
 
109
+ def deployed?
110
+ !!latest_release
111
+ end
112
+
113
+ def maintenance_page_candidates
114
+ if latest_release
115
+ candidates = MAINTENANCE_CANDIDATES.map do |file|
116
+ path(:latest_release, file)
117
+ end
118
+ else
119
+ candidates = []
120
+ end
121
+ candidates << DEFAULT_MAINTENANCE_PAGE
122
+ candidates
123
+ end
124
+
96
125
  def rollback
97
- if previous_release
126
+ if deployed? && previous_release
98
127
  self.class.new(@opts.dup.merge(:active_release => previous_release))
99
128
  else
100
129
  nil
@@ -63,18 +63,18 @@ module EY
63
63
  end
64
64
 
65
65
  # Run a command on this set of servers.
66
- def run(cmd, &blk)
67
- run_on_servers('sh -l -c', cmd, &blk)
66
+ def run(shell, cmd, &blk)
67
+ run_on_servers(shell, 'sh -l -c', cmd, &blk)
68
68
  end
69
69
 
70
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)
71
+ def sudo(shell, cmd, &blk)
72
+ run_on_servers(shell, 'sudo sh -l -c', cmd, &blk)
73
73
  end
74
74
 
75
75
  private
76
76
 
77
- def run_on_servers(prefix, cmd, &block)
77
+ def run_on_servers(shell, prefix, cmd, &block)
78
78
  commands = map do |server|
79
79
  exec_cmd = server.command_on_server(prefix, cmd, &block)
80
80
  proc { shell.logged_system(exec_cmd) }
@@ -59,31 +59,14 @@ module EY
59
59
  end
60
60
  end
61
61
 
62
- def run(cmd, &blk)
63
- run_on_roles('sh -l -c', cmd, &blk)
62
+ def run(cmd, &block)
63
+ servers.roles(@roles).run(shell, cmd, &block)
64
64
  end
65
65
 
66
- def sudo(cmd, &blk)
67
- run_on_roles('sudo sh -l -c', cmd, &blk)
66
+ def sudo(cmd, &block)
67
+ servers.roles(@roles).sudo(shell, cmd, &block)
68
68
  end
69
69
 
70
- private
71
-
72
- def run_on_roles(prefix, cmd, &block)
73
- servers = @servers.roles(@roles)
74
-
75
- commands = servers.map do |server|
76
- exec_cmd = server.command_on_server(prefix, cmd, &block)
77
- proc { shell.logged_system(exec_cmd) }
78
- end
79
-
80
- futures = EY::Serverside::Future.call(commands)
81
-
82
- unless EY::Serverside::Future.success?(futures)
83
- failures = futures.select {|f| f.error? }.map {|f| f.inspect}.join("\n")
84
- raise EY::Serverside::RemoteFailure.new(failures)
85
- end
86
- end
87
70
  end
88
71
  end
89
72
  end
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module Serverside
3
- VERSION = '2.0.0.pre4'
3
+ VERSION = '2.0.0.pre5'
4
4
  end
5
5
  end
@@ -18,19 +18,19 @@ describe "the EY::Serverside::Deploy API" do
18
18
  @call_order = []
19
19
  end
20
20
 
21
- def push_code() @call_order << 'push_code' end
22
- def copy_repository_cache() @call_order << 'copy_repository_cache' end
23
- def create_revision_file() @call_order << 'create_revision_file' end
24
- def bundle() @call_order << 'bundle' end
25
- def setup_services() @call_order << 'setup_services' end
26
- def symlink_configs() @call_order << 'symlink_configs' end
27
- def migrate() @call_order << 'migrate' end
28
- def compile_assets() @call_order << 'compile_assets' end
29
- def symlink() @call_order << 'symlink' end
30
- def restart() @call_order << 'restart' end
31
- def cleanup_old_releases() @call_order << 'cleanup_old_releases' end
32
- def conditionally_enable_maintenance_page() @call_order << 'conditionally_enable_maintenance_page' end
33
- def disable_maintenance_page() @call_order << 'disable_maintenance_page' end
21
+ def push_code() @call_order << 'push_code' end
22
+ def copy_repository_cache() @call_order << 'copy_repository_cache' end
23
+ def create_revision_file() @call_order << 'create_revision_file' end
24
+ def bundle() @call_order << 'bundle' end
25
+ def setup_services() @call_order << 'setup_services' end
26
+ def symlink_configs() @call_order << 'symlink_configs' end
27
+ def migrate() @call_order << 'migrate' end
28
+ def compile_assets() @call_order << 'compile_assets' end
29
+ def symlink() @call_order << 'symlink' end
30
+ def restart() @call_order << 'restart' end
31
+ def cleanup_old_releases() @call_order << 'cleanup_old_releases' end
32
+ def enable_maintenance_page() @call_order << 'enable_maintenance_page' end
33
+ def disable_maintenance_page() @call_order << 'disable_maintenance_page' end
34
34
  end
35
35
 
36
36
  config = EY::Serverside::Deploy::Configuration.new({
@@ -40,6 +40,17 @@ describe "the EY::Serverside::Deploy API" do
40
40
 
41
41
  td = TestDeploy.new(test_servers, config, test_shell)
42
42
  td.deploy
43
+
44
+ ############################# IMPORTANT ####################################
45
+ #
46
+ # Call order is referenced in the engineyard gem eydeploy.rb documentation.
47
+ #
48
+ # https://support.cloud.engineyard.com/entries/20996661-customize-your-deployment
49
+ #
50
+ # Changing call order or removing methods may adversely affect customers
51
+ # that are using eydeploy.rb and relying on this documentation.
52
+ #
53
+ ############################################################################
43
54
  td.call_order.should == %w(
44
55
  push_code
45
56
  copy_repository_cache
@@ -47,7 +58,7 @@ describe "the EY::Serverside::Deploy API" do
47
58
  bundle
48
59
  setup_services
49
60
  symlink_configs
50
- conditionally_enable_maintenance_page
61
+ enable_maintenance_page
51
62
  migrate
52
63
  compile_assets
53
64
  symlink
data/spec/restart_spec.rb CHANGED
@@ -20,12 +20,12 @@ 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
- config = EY::Serverside::Deploy::Configuration.new('app' => 'app_name')
23
+ config = EY::Serverside::Deploy::Configuration.new('deploy_to' => deploy_dir, 'app' => 'app_name')
24
24
  deployer = TestRestartWithMaintenancePage.new(test_servers, config, test_shell)
25
25
  deployer.restart_with_maintenance_page
26
26
  deployer.call_order.should == %w(
27
27
  require_custom_tasks
28
- conditionally_enable_maintenance_page
28
+ enable_maintenance_page
29
29
  restart
30
30
  disable_maintenance_page
31
31
  )
@@ -35,7 +35,7 @@ end
35
35
  describe "glassfish stack" do
36
36
 
37
37
  it "requires a maintenance page" do
38
- config = EY::Serverside::Deploy::Configuration.new(:stack => 'glassfish')
38
+ config = EY::Serverside::Deploy::Configuration.new('deploy_to' => deploy_dir, 'app' => 'app_name', 'stack' => 'glassfish')
39
39
  deployer = TestRestartDeploy.new(test_servers, config, test_shell)
40
40
  deployer.restart_with_maintenance_page
41
41
  deployer.call_order.should include('enable_maintenance_page')
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engineyard-serverside
3
3
  version: !ruby/object:Gem::Version
4
- hash: 883885311
4
+ hash: 3919216497
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 2
8
8
  - 0
9
9
  - 0
10
10
  - pre
11
- - 4
12
- version: 2.0.0.pre4
11
+ - 5
12
+ version: 2.0.0.pre5
13
13
  platform: ruby
14
14
  authors:
15
15
  - EY Cloud Team
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2012-06-13 00:00:00 Z
20
+ date: 2012-06-14 00:00:00 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: rspec
@@ -115,6 +115,7 @@ files:
115
115
  - lib/engineyard-serverside/futures/celluloid.rb
116
116
  - lib/engineyard-serverside/futures/dataflow.rb
117
117
  - lib/engineyard-serverside/lockfile_parser.rb
118
+ - lib/engineyard-serverside/maintenance.rb
118
119
  - lib/engineyard-serverside/paths.rb
119
120
  - lib/engineyard-serverside/rails_asset_support.rb
120
121
  - lib/engineyard-serverside/server.rb