engineyard-serverside 1.6.5 → 1.7.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/lib/engineyard-serverside.rb +2 -0
  2. data/lib/engineyard-serverside/cli.rb +83 -48
  3. data/lib/engineyard-serverside/configuration.rb +85 -18
  4. data/lib/engineyard-serverside/deploy.rb +105 -91
  5. data/lib/engineyard-serverside/deploy_hook.rb +22 -20
  6. data/lib/engineyard-serverside/deprecation.rb +9 -17
  7. data/lib/engineyard-serverside/future.rb +10 -4
  8. data/lib/engineyard-serverside/futures/celluloid.rb +3 -13
  9. data/lib/engineyard-serverside/futures/dataflow.rb +8 -13
  10. data/lib/engineyard-serverside/lockfile_parser.rb +1 -1
  11. data/lib/engineyard-serverside/rails_asset_support.rb +26 -10
  12. data/lib/engineyard-serverside/server.rb +17 -12
  13. data/lib/engineyard-serverside/shell.rb +98 -0
  14. data/lib/engineyard-serverside/shell/formatter.rb +71 -0
  15. data/lib/engineyard-serverside/shell/helpers.rb +29 -0
  16. data/lib/engineyard-serverside/strategies/git.rb +33 -63
  17. data/lib/engineyard-serverside/task.rb +34 -13
  18. data/lib/engineyard-serverside/version.rb +1 -1
  19. data/spec/basic_deploy_spec.rb +15 -50
  20. data/spec/bundler_deploy_spec.rb +3 -44
  21. data/spec/configuration_spec.rb +72 -0
  22. data/spec/custom_deploy_spec.rb +3 -4
  23. data/spec/deploy_hook_spec.rb +210 -162
  24. data/spec/deprecation_spec.rb +4 -26
  25. data/spec/ey_yml_customized_deploy_spec.rb +68 -0
  26. data/spec/fixtures/repos/assets_disabled/Gemfile +6 -0
  27. data/spec/fixtures/repos/assets_disabled/Gemfile.lock +90 -0
  28. data/spec/fixtures/repos/assets_disabled/README +1 -0
  29. data/spec/fixtures/repos/assets_disabled/Rakefile +5 -0
  30. data/spec/fixtures/repos/assets_disabled/config/application.rb +5 -0
  31. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile +6 -0
  32. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile.lock +90 -0
  33. data/spec/fixtures/repos/assets_disabled_in_ey_yml/README +1 -0
  34. data/spec/fixtures/repos/assets_disabled_in_ey_yml/Rakefile +5 -0
  35. data/spec/fixtures/repos/assets_disabled_in_ey_yml/config/application.rb +5 -0
  36. data/spec/fixtures/repos/assets_disabled_in_ey_yml/config/ey.yml +4 -0
  37. data/spec/fixtures/repos/assets_enabled/Gemfile +6 -0
  38. data/spec/fixtures/repos/assets_enabled/Gemfile.lock +90 -0
  39. data/spec/fixtures/repos/assets_enabled/README +1 -0
  40. data/spec/fixtures/repos/assets_enabled/Rakefile +5 -0
  41. data/spec/fixtures/repos/assets_enabled/config/application.rb +5 -0
  42. data/spec/fixtures/repos/assets_enabled_in_ey_yml/README +1 -0
  43. data/spec/fixtures/repos/assets_enabled_in_ey_yml/Rakefile +5 -0
  44. data/spec/fixtures/repos/assets_enabled_in_ey_yml/config/ey.yml +4 -0
  45. data/spec/fixtures/repos/assets_in_hook/Gemfile +6 -0
  46. data/spec/fixtures/repos/assets_in_hook/Gemfile.lock +90 -0
  47. data/spec/fixtures/repos/assets_in_hook/README +2 -0
  48. data/spec/fixtures/repos/assets_in_hook/Rakefile +5 -0
  49. data/spec/fixtures/repos/assets_in_hook/config/application.rb +5 -0
  50. data/spec/fixtures/repos/assets_in_hook/deploy/before_migrate.rb +1 -0
  51. data/spec/fixtures/repos/default/Gemfile +5 -0
  52. data/spec/fixtures/repos/default/Gemfile.lock +14 -0
  53. data/spec/fixtures/repos/default/README +5 -0
  54. data/spec/fixtures/repos/ey_yml/Gemfile +4 -0
  55. data/spec/fixtures/repos/ey_yml/Gemfile.lock +12 -0
  56. data/spec/fixtures/repos/ey_yml/README +1 -0
  57. data/spec/fixtures/repos/ey_yml/config/ey.yml +12 -0
  58. data/spec/fixtures/repos/ey_yml/deploy/before_migrate.rb +6 -0
  59. data/spec/fixtures/repos/ey_yml_alt/Gemfile +4 -0
  60. data/spec/fixtures/repos/ey_yml_alt/Gemfile.lock +12 -0
  61. data/spec/fixtures/repos/ey_yml_alt/README +1 -0
  62. data/spec/fixtures/repos/ey_yml_alt/deploy/before_migrate.rb +6 -0
  63. data/spec/fixtures/repos/ey_yml_alt/ey.yml +12 -0
  64. data/spec/fixtures/repos/hook_fails/README +1 -0
  65. data/spec/fixtures/repos/hook_fails/deploy/before_migrate.rb +1 -0
  66. data/spec/fixtures/repos/hooks/README +1 -0
  67. data/spec/fixtures/repos/hooks/deploy/after_bundle.rb +1 -0
  68. data/spec/fixtures/repos/hooks/deploy/after_compile_assets.rb +1 -0
  69. data/spec/fixtures/repos/hooks/deploy/after_migrate.rb +1 -0
  70. data/spec/fixtures/repos/hooks/deploy/after_restart.rb +1 -0
  71. data/spec/fixtures/repos/hooks/deploy/after_symlink.rb +1 -0
  72. data/spec/fixtures/repos/hooks/deploy/before_bundle.rb +1 -0
  73. data/spec/fixtures/repos/hooks/deploy/before_compile_assets.rb +1 -0
  74. data/spec/fixtures/repos/hooks/deploy/before_migrate.rb +1 -0
  75. data/spec/fixtures/repos/hooks/deploy/before_restart.rb +1 -0
  76. data/spec/fixtures/repos/hooks/deploy/before_symlink.rb +1 -0
  77. data/spec/fixtures/repos/no_ey_config/Gemfile +4 -0
  78. data/spec/fixtures/repos/no_ey_config/Gemfile.lock +12 -0
  79. data/spec/fixtures/repos/no_ey_config/README +1 -0
  80. data/spec/fixtures/repos/no_gemfile_lock/Gemfile +5 -0
  81. data/spec/fixtures/repos/no_gemfile_lock/README +1 -0
  82. data/spec/fixtures/repos/nodejs/README +1 -0
  83. data/spec/fixtures/repos/nodejs/package.json +7 -0
  84. data/spec/fixtures/repos/not_bundled/README +1 -0
  85. data/spec/fixtures/{gemfiles/1.0.21-rails-31-with-sqlite → repos/sqlite3/Gemfile} +0 -0
  86. data/spec/fixtures/{lockfiles/1.0.21-rails-31-with-sqlite → repos/sqlite3/Gemfile.lock} +0 -0
  87. data/spec/fixtures/repos/sqlite3/README +1 -0
  88. data/spec/git_strategy_spec.rb +11 -2
  89. data/spec/lockfile_parser_spec.rb +8 -3
  90. data/spec/nodejs_deploy_spec.rb +1 -26
  91. data/spec/rails31_deploy_spec.rb +23 -31
  92. data/spec/services_deploy_spec.rb +41 -100
  93. data/spec/shell_spec.rb +50 -0
  94. data/spec/spec_helper.rb +80 -66
  95. data/spec/sqlite3_deploy_spec.rb +10 -16
  96. data/spec/support/integration.rb +45 -139
  97. metadata +233 -78
  98. data/lib/engineyard-serverside/logged_output.rb +0 -91
  99. data/spec/logged_output_spec.rb +0 -55
@@ -26,6 +26,8 @@ require 'engineyard-serverside/cli'
26
26
  require 'engineyard-serverside/configuration'
27
27
  require 'engineyard-serverside/deprecation'
28
28
  require 'engineyard-serverside/future'
29
+ require 'engineyard-serverside/shell'
30
+
29
31
 
30
32
  module EY
31
33
  module Serverside
@@ -1,5 +1,8 @@
1
1
  require 'thor'
2
2
  require 'pathname'
3
+ require 'engineyard-serverside/deploy'
4
+ require 'engineyard-serverside/shell'
5
+ require 'engineyard-serverside/server'
3
6
 
4
7
  module EY
5
8
  module Serverside
@@ -22,6 +25,14 @@ module EY
22
25
  :desc => "Application to deploy",
23
26
  :aliases => ["-a"]
24
27
 
28
+ method_option :environment_name,:type => :string,
29
+ :required => true,
30
+ :desc => "Environment name"
31
+
32
+ method_option :account_name, :type => :string,
33
+ :required => true,
34
+ :desc => "Account name"
35
+
25
36
  method_option :framework_env, :type => :string,
26
37
  :desc => "Ruby web framework environment",
27
38
  :aliases => ["-e"]
@@ -44,21 +55,13 @@ module EY
44
55
  :desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
45
56
 
46
57
  method_option :verbose, :type => :boolean,
47
- :default => false,
48
58
  :desc => "Verbose output",
49
59
  :aliases => ["-v"]
50
60
 
51
61
  desc "deploy", "Deploy code from /data/<app>"
52
62
  def deploy(default_task=:deploy)
53
- config = EY::Serverside::Deploy::Configuration.new(options)
54
- load_servers(config)
55
-
56
- EY::Serverside::LoggedOutput.verbose = options[:verbose]
57
- EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-deploy.log")
58
-
59
- invoke :propagate
60
-
61
- EY::Serverside::Deploy.new(config).send(default_task)
63
+ config, shell = init_and_propagate(options, 'deploy')
64
+ EY::Serverside::Deploy.new(config, shell).send(default_task)
62
65
  end
63
66
 
64
67
  method_option :app, :type => :string,
@@ -66,6 +69,14 @@ module EY
66
69
  :desc => "Which application's hooks to run",
67
70
  :aliases => ["-a"]
68
71
 
72
+ method_option :environment_name, :type => :string,
73
+ :required => true,
74
+ :desc => "Environment name"
75
+
76
+ method_option :account_name, :type => :string,
77
+ :required => true,
78
+ :desc => "Account name"
79
+
69
80
  method_option :release_path, :type => :string,
70
81
  :desc => "Value for #release_path in hooks (mostly for internal coordination)",
71
82
  :aliases => ["-r"]
@@ -84,9 +95,14 @@ module EY
84
95
  method_option :current_name, :type => :string,
85
96
  :desc => "Value for #current_name in hooks"
86
97
 
98
+ method_option :verbose, :type => :boolean,
99
+ :desc => "Verbose output",
100
+ :aliases => ["-v"]
101
+
87
102
  desc "hook [NAME]", "Run a particular deploy hook"
88
103
  def hook(hook_name)
89
- EY::Serverside::DeployHook.new(options).run(hook_name)
104
+ config, shell = init(options, "hook-#{hook_name}")
105
+ EY::Serverside::DeployHook.new(config, shell).run(hook_name)
90
106
  end
91
107
 
92
108
 
@@ -95,6 +111,14 @@ module EY
95
111
  :desc => "Application to deploy",
96
112
  :aliases => ["-a"]
97
113
 
114
+ method_option :environment_name,:type => :string,
115
+ :required => true,
116
+ :desc => "Environment name"
117
+
118
+ method_option :account_name, :type => :string,
119
+ :required => true,
120
+ :desc => "Account name"
121
+
98
122
  method_option :framework_env, :type => :string,
99
123
  :required => true,
100
124
  :desc => "Ruby web framework environment",
@@ -115,13 +139,10 @@ module EY
115
139
  :desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
116
140
 
117
141
  method_option :verbose, :type => :boolean,
118
- :default => false,
119
142
  :desc => "Verbose output",
120
143
  :aliases => ["-v"]
121
144
  desc "integrate", "Integrate other instances into this cluster"
122
145
  def integrate
123
- EY::Serverside::LoggedOutput.verbose = options[:verbose]
124
- EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-integrate.log")
125
146
 
126
147
  app_dir = Pathname.new "/data/#{options[:app]}"
127
148
  current_app_dir = app_dir + "current"
@@ -133,26 +154,19 @@ module EY
133
154
  # we have to deploy the same SHA there as here
134
155
  integrate_options[:branch] = (current_app_dir + 'REVISION').read.strip
135
156
 
136
- config = EY::Serverside::Deploy::Configuration.new(integrate_options)
137
-
138
- # We have to rsync the entire app dir, so we need all the permissions to be correct!
139
- system "sudo sh -l -c 'find #{app_dir} -not -user #{config.user} -or -not -group #{config.group} -exec chown #{config.user}:#{config.group} {} +'"
140
-
141
- load_servers(config)
142
-
143
- invoke :propagate
157
+ config, shell = init_and_propagate(integrate_options, 'integrate')
144
158
 
145
159
  EY::Serverside::Server.all.each do |server|
146
- server.sync_directory app_dir
160
+ shell.logged_system server.sync_directory_command(app_dir)
147
161
  # we're just about to recreate this, so it has to be gone
148
162
  # first. otherwise, non-idempotent deploy hooks could screw
149
163
  # things up, and since we don't control deploy hooks, we must
150
164
  # assume the worst.
151
- server.run("rm -rf #{current_app_dir}")
165
+ run_on_server(server,"rm -rf #{current_app_dir}")
152
166
  end
153
167
 
154
168
  # deploy local-ref to other instances into /data/$app/local-current
155
- EY::Serverside::Deploy.new(config).cached_deploy
169
+ EY::Serverside::Deploy.new(config, shell).cached_deploy
156
170
  end
157
171
 
158
172
  method_option :app, :type => :string,
@@ -160,6 +174,14 @@ module EY
160
174
  :desc => "Application to deploy",
161
175
  :aliases => ["-a"]
162
176
 
177
+ method_option :environment_name,:type => :string,
178
+ :required => true,
179
+ :desc => "Environment name"
180
+
181
+ method_option :account_name, :type => :string,
182
+ :required => true,
183
+ :desc => "Account name"
184
+
163
185
  method_option :stack, :type => :string,
164
186
  :desc => "Web stack (so we can restart it correctly)"
165
187
 
@@ -175,20 +197,13 @@ module EY
175
197
  :desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
176
198
 
177
199
  method_option :verbose, :type => :boolean,
178
- :default => false,
179
200
  :desc => "Verbose output",
180
201
  :aliases => ["-v"]
181
202
  desc "restart", "Restart app servers, conditionally enabling maintenance page"
182
203
  def restart
183
- EY::Serverside::LoggedOutput.verbose = options[:verbose]
184
- EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-restart.log")
204
+ config, shell = init_and_propagate(options, 'restart')
185
205
 
186
- config = EY::Serverside::Deploy::Configuration.new(options)
187
- load_servers(config)
188
-
189
- invoke :propagate
190
-
191
- EY::Serverside::Deploy.new(config).restart_with_maintenance_page
206
+ EY::Serverside::Deploy.new(config, shell).restart_with_maintenance_page
192
207
  end
193
208
 
194
209
  desc "install_bundler [VERSION]", "Make sure VERSION of bundler is installed (in system ruby)"
@@ -206,8 +221,10 @@ module EY
206
221
  end
207
222
  end
208
223
 
209
- desc "propagate", "Propagate the engineyard-serverside gem to the other instances in the cluster. This will install exactly version #{EY::Serverside::VERSION}."
210
- def propagate
224
+ private
225
+
226
+ # Put the same engineyard-serverside on all the servers (Used to be public but is unused as an actual CLI command now)
227
+ def propagate(shell)
211
228
  config = EY::Serverside::Deploy::Configuration.new
212
229
  gem_filename = "engineyard-serverside-#{EY::Serverside::VERSION}.gem"
213
230
  local_gem_file = File.join(Gem.dir, 'cache', gem_filename)
@@ -216,29 +233,47 @@ module EY
216
233
 
217
234
  servers = EY::Serverside::Server.all.find_all { |server| !server.local? }
218
235
 
219
- futures = EY::Serverside::Future.call(servers) do |server|
236
+ commands = servers.each do |server|
220
237
  egrep_escaped_version = EY::Serverside::VERSION.gsub(/\./, '\.')
221
238
  # the [,)] is to stop us from looking for e.g. 0.5.1, seeing
222
239
  # 0.5.11, and mistakenly thinking 0.5.1 is there
223
240
  has_gem_cmd = "#{gem_binary} list engineyard-serverside | grep \"engineyard-serverside\" | egrep -q '#{egrep_escaped_version}[,)]'"
224
241
 
225
- if !server.run(has_gem_cmd) # doesn't have this exact version
226
- puts "~> Installing engineyard-serverside on #{server.hostname}"
227
-
228
- system(Escape.shell_command([
229
- 'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
230
- "-o", "StrictHostKeyChecking=no",
231
- local_gem_file,
232
- "#{config.user}@#{server.hostname}:#{remote_gem_file}",
233
- ]))
234
- server.run("sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
242
+ proc do
243
+ if !run_on_server(server,has_gem_cmd).success? # doesn't have this exact version
244
+ shell.status "Installing engineyard-serverside on #{server.hostname}"
245
+
246
+ shell.logged_system(Escape.shell_command([
247
+ 'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
248
+ "-o", "StrictHostKeyChecking=no",
249
+ local_gem_file,
250
+ "#{config.user}@#{server.hostname}:#{remote_gem_file}",
251
+ ]))
252
+ run_on_server(server,"sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
253
+ end
235
254
  end
236
255
  end
237
256
 
257
+ futures = EY::Serverside::Future.call(commands)
238
258
  EY::Serverside::Future.success?(futures)
239
259
  end
240
260
 
241
- private
261
+ def init_and_propagate(*args)
262
+ config, shell = init(*args)
263
+ load_servers(config)
264
+ propagate(shell)
265
+ [config, shell]
266
+ end
267
+
268
+ def init(options, action)
269
+ config = EY::Serverside::Deploy::Configuration.new(options)
270
+ shell = EY::Serverside::Shell.new(:verbose => config.verbose, :log_path => File.join(ENV['HOME'], "#{config.app}-#{action}.log"))
271
+ [config, shell]
272
+ end
273
+
274
+ def run_on_server(server, command)
275
+ shell.logged_system(server.command_on_server('sh -l -c', command))
276
+ end
242
277
 
243
278
  def load_servers(config)
244
279
  EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'thor'
3
+ require 'pp'
3
4
 
4
5
  module EY
5
6
  module Serverside
@@ -10,41 +11,54 @@ module EY
10
11
  "bundle_without" => "test development",
11
12
  })
12
13
 
13
- attr_reader :configuration
14
- alias :c :configuration
15
-
16
14
  attr_writer :release_path
17
15
 
18
16
  def initialize(options={})
19
- opts = options.dup
20
- @release_path = opts[:release_path]
17
+ opts = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
18
+ @release_path = opts['release_path']
21
19
  config = JSON.parse(opts.delete("config") || "{}")
22
- @configuration = DEFAULT_CONFIG.merge(config).merge(opts)
20
+ @configs = [config, opts] # low to high priority
21
+ end
22
+
23
+ def configuration
24
+ @configuration ||= @configs.inject(DEFAULT_CONFIG) {|low,high| low.merge(high)}
23
25
  end
26
+ alias :c :configuration # FIXME: awful, but someone is probably using it :(
24
27
 
25
28
  # Delegate to the configuration objects
26
29
  def method_missing(meth, *args, &blk)
27
- c.key?(meth.to_s) ? c[meth.to_s] : super
30
+ configuration.key?(meth.to_s) ? configuration[meth.to_s] : super
28
31
  end
29
32
 
30
33
  def respond_to?(meth, include_private=false)
31
- c.key?(meth.to_s) ? true : super
34
+ configuration.key?(meth.to_s) ? true : super
35
+ end
36
+
37
+ def load_ey_yml_data(data, shell)
38
+ environments = data['environments']
39
+ if environments && (env_data = environments[environment_name])
40
+ shell.substatus "ey.yml configuration loaded for environment #{environment_name.inspect}."
41
+ shell.debug "#{environment_name}: #{env_data.pretty_inspect}"
42
+ @configuration = nil # reset cached configuration hash
43
+ @configs.unshift(env_data) # insert just above default configuration
44
+ true
45
+ else
46
+ shell.info "No matching ey.yml configuration found for environment #{environment_name.inspect}."
47
+ shell.debug "ey.yml:\n#{data.pretty_inspect}"
48
+ false
49
+ end
32
50
  end
33
51
 
34
52
  def [](key)
35
53
  if respond_to?(key.to_sym)
36
54
  send(key.to_sym)
37
55
  else
38
- c[key]
56
+ configuration[key]
39
57
  end
40
58
  end
41
59
 
42
60
  def has_key?(key)
43
- if respond_to?(key.to_sym)
44
- true
45
- else
46
- c.has_key?(key)
47
- end
61
+ respond_to?(key.to_sym) || configuration.has_key?(key)
48
62
  end
49
63
 
50
64
  def to_json
@@ -55,9 +69,30 @@ module EY
55
69
  EY::Serverside.node
56
70
  end
57
71
 
72
+ def verbose
73
+ configuration['verbose']
74
+ end
75
+
58
76
  def app
59
77
  configuration['app'].to_s
60
78
  end
79
+ alias app_name app
80
+
81
+ def environment_name
82
+ configuration['environment_name'].to_s
83
+ end
84
+
85
+ def account_name
86
+ configuration['account_name'].to_s
87
+ end
88
+
89
+ def ssh_identity_file
90
+ "~/.ssh/#{c.app}-deploy-key"
91
+ end
92
+
93
+ def strategy_class
94
+ EY::Serverside::Strategies.const_get(strategy)
95
+ end
61
96
 
62
97
  def revision
63
98
  IO.read(File.join(latest_release, 'REVISION'))
@@ -95,6 +130,10 @@ module EY
95
130
  node['instance_role']
96
131
  end
97
132
 
133
+ def current_roles
134
+ configuration['current_roles'] || []
135
+ end
136
+
98
137
  def current_role
99
138
  current_roles.first
100
139
  end
@@ -116,10 +155,6 @@ module EY
116
155
  all_releases[index-1]
117
156
  end
118
157
 
119
- def oldest_release
120
- all_releases.first
121
- end
122
-
123
158
  def all_releases
124
159
  Dir.glob("#{release_dir}/*").sort
125
160
  end
@@ -184,10 +219,42 @@ module EY
184
219
  @release_path ||= File.join(release_dir, Time.now.utc.strftime("%Y%m%d%H%M%S"))
185
220
  end
186
221
 
222
+ def precompile_assets?
223
+ configuration['precompile_assets'] == true
224
+ end
225
+
226
+ def skip_precompile_assets?
227
+ configuration['precompile_assets'] == false
228
+ end
229
+
230
+ def required_downtime_stack?
231
+ %w[ nginx_mongrel glassfish ].include? stack
232
+ end
233
+
234
+ def enable_maintenance_page_on_restart?
235
+ configuration.fetch('maintenance_on_restart', required_downtime_stack?)
236
+ end
237
+
238
+ def enable_maintenance_page_on_migrate?
239
+ configuration.fetch('maintenance_on_migrate', true)
240
+ end
241
+
242
+ def enable_maintenance_page?
243
+ enable_maintenance_page_on_restart? || (migrate? && enable_maintenance_page_on_migrate?)
244
+ end
245
+
246
+ def maintenance_page_enabled_path
247
+ File.join(shared_path, "system", "maintenance.html")
248
+ end
249
+
187
250
  def exclusions
188
251
  copy_exclude.map { |e| %|--exclude="#{e}"| }.join(' ')
189
252
  end
190
253
 
254
+ def ignore_database_adapter_warning?
255
+ configuration.fetch('ignore_database_adapter_warning', false)
256
+ end
257
+
191
258
  end
192
259
  end
193
260
  end
@@ -7,22 +7,22 @@ require 'engineyard-serverside/rails_asset_support'
7
7
  module EY
8
8
  module Serverside
9
9
  class DeployBase < Task
10
- include LoggedOutput
11
10
  include ::EY::Serverside::RailsAssetSupport
12
11
 
13
12
  # default task
14
13
  def deploy
15
- debug "Starting deploy at #{Time.now.asctime}"
14
+ shell.status "Starting deploy at #{shell.start_time.asctime}"
16
15
  update_repository_cache
17
16
  cached_deploy
18
17
  end
19
18
 
20
19
  def cached_deploy
21
- debug "Deploying app from cached copy at #{Time.now.asctime}"
20
+ shell.status "Deploying app from cached copy at #{Time.now.asctime}"
22
21
  require_custom_tasks
22
+ load_ey_yml
23
23
  push_code
24
24
 
25
- info "~> Starting full deploy"
25
+ shell.status "Starting full deploy"
26
26
  copy_repository_cache
27
27
  check_repository
28
28
 
@@ -47,13 +47,25 @@ module EY
47
47
  disable_maintenance_page
48
48
 
49
49
  cleanup_old_releases
50
- debug "Finished deploy at #{Time.now.asctime}"
50
+ shell.status "Finished deploy at #{Time.now.asctime}"
51
51
  rescue Exception
52
- debug "Finished failing to deploy at #{Time.now.asctime}"
52
+ shell.status "Finished failing to deploy at #{Time.now.asctime}"
53
53
  puts_deploy_failure
54
54
  raise
55
55
  end
56
56
 
57
+ def update_repository_cache
58
+ strategy.update_repository_cache
59
+ end
60
+
61
+ def create_revision_file_command
62
+ strategy.create_revision_file_command(config.release_path)
63
+ end
64
+
65
+ def short_log_message(revision)
66
+ strategy.short_log_message(revision)
67
+ end
68
+
57
69
  def parse_configured_services
58
70
  result = YAML.load_file "#{c.shared_path}/config/ey_services_config_deploy.yml"
59
71
  return {} unless result.is_a?(Hash)
@@ -66,18 +78,18 @@ module EY
66
78
  if gemfile? && lockfile
67
79
  configured_services = parse_configured_services
68
80
  if !configured_services.empty? && !lockfile.has_ey_config?
69
- warning "Gemfile.lock does not contain ey_config. Add it to get EY::Config access to: #{configured_services.keys.join(', ')}."
81
+ shell.warning "Gemfile.lock does not contain ey_config. Add it to get EY::Config access to: #{configured_services.keys.join(', ')}."
70
82
  end
71
83
  end
72
84
  end
73
85
 
74
86
  def check_repository
75
87
  if gemfile?
76
- info "~> Gemfile found."
88
+ shell.status "Gemfile found."
77
89
  if lockfile
78
- info "~> Gemfile.lock found."
79
- unless lockfile.any_database_adapter?
80
- warning <<-WARN
90
+ shell.status "Gemfile.lock found."
91
+ if !config.ignore_database_adapter_warning? && !lockfile.any_database_adapter?
92
+ shell.warning <<-WARN
81
93
  Gemfile.lock does not contain a recognized database adapter.
82
94
  A database-adapter gem such as mysql2, mysql, or do_mysql was expected.
83
95
  This can prevent applications that use MySQL or PostreSQL from booting.
@@ -87,7 +99,7 @@ Applications that don't use MySQL or PostgreSQL can safely ignore this warning.
87
99
  WARN
88
100
  end
89
101
  else
90
- warning <<-WARN
102
+ shell.warning <<-WARN
91
103
  Gemfile.lock is missing!
92
104
  You can get different versions of gems in production than what you tested with.
93
105
  You can get different versions of gems on every deployment even if your Gemfile hasn't changed.
@@ -97,15 +109,13 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
97
109
  WARN
98
110
  end
99
111
  else
100
- info "~> No Gemfile. Deploying without bundler support."
112
+ shell.status "No Gemfile. Deploying without bundler support."
101
113
  end
102
114
  end
103
115
 
104
116
  def restart_with_maintenance_page
105
117
  require_custom_tasks
106
- conditionally_enable_maintenance_page
107
- restart
108
- disable_maintenance_page
118
+ with_maintenance_page { restart }
109
119
  end
110
120
 
111
121
  def enable_maintenance_page
@@ -119,10 +129,7 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
119
129
  end
120
130
 
121
131
  # this one is guaranteed to exist
122
- maintenance_page_candidates << File.expand_path(
123
- "default_maintenance_page.html",
124
- File.dirname(__FILE__)
125
- )
132
+ maintenance_page_candidates << File.expand_path("default_maintenance_page.html", File.dirname(__FILE__))
126
133
 
127
134
  # put in the maintenance page
128
135
  maintenance_file = maintenance_page_candidates.detect do |file|
@@ -131,27 +138,21 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
131
138
 
132
139
  @maintenance_up = true
133
140
  roles :app_master, :app, :solo do
134
- maint_page_dir = File.join(c.shared_path, "system")
135
- visible_maint_page = File.join(maint_page_dir, "maintenance.html")
136
- run Escape.shell_command(['mkdir', '-p', maint_page_dir])
137
- run Escape.shell_command(['cp', maintenance_file, visible_maint_page])
141
+ run Escape.shell_command(['mkdir', '-p', File.dirname(c.maintenance_page_enabled_path)])
142
+ run Escape.shell_command(['cp', maintenance_file, c.maintenance_page_enabled_path])
138
143
  end
139
144
  end
140
145
 
141
146
  def conditionally_enable_maintenance_page
142
- if c.migrate? || required_downtime_stack?
147
+ if c.enable_maintenance_page?
143
148
  enable_maintenance_page
144
149
  end
145
150
  end
146
151
 
147
- def required_downtime_stack?
148
- %w[ nginx_mongrel glassfish ].include? c.stack
149
- end
150
-
151
152
  def disable_maintenance_page
152
153
  @maintenance_up = false
153
154
  roles :app_master, :app, :solo do
154
- run "rm -f #{File.join(c.shared_path, "system", "maintenance.html")}"
155
+ run "rm -f #{c.maintenance_page_enabled_path}"
155
156
  end
156
157
  end
157
158
 
@@ -163,17 +164,19 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
163
164
 
164
165
  # task
165
166
  def push_code
166
- info "~> Pushing code to all servers"
167
- futures = EY::Serverside::Future.call(EY::Serverside::Server.all) do |server|
168
- server.sync_directory(config.repository_cache)
167
+ shell.status "Pushing code to all servers"
168
+ commands = EY::Serverside::Server.all.reject { |server| server.local? }.map do |server|
169
+ cmd = server.sync_directory_command(config.repository_cache)
170
+ proc { shell.logged_system(cmd) }
169
171
  end
172
+ futures = EY::Serverside::Future.call(commands)
170
173
  EY::Serverside::Future.success?(futures)
171
174
  end
172
175
 
173
176
  # task
174
177
  def restart
175
178
  @restart_failed = true
176
- info "~> Restarting app servers"
179
+ shell.status "Restarting app servers"
177
180
  roles :app_master, :app, :solo do
178
181
  run(restart_command)
179
182
  end
@@ -189,14 +192,14 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
189
192
  %Q[export GIT_SSH="#{ssh_executable}" && export LANG="en_US.UTF-8" && unset RUBYOPT BUNDLE_PATH BUNDLE_FROZEN BUNDLE_WITHOUT BUNDLE_BIN BUNDLE_GEMFILE]
190
193
  end
191
194
 
192
- # If we don't have a local version of the ssh wrapper script yet,
193
- # create it on all the servers that will need it.
194
- # TODO - This logic likely fails when people change deploy keys.
195
+ # create ssh wrapper on all servers
195
196
  def ssh_executable
196
- roles :app_master, :app, :solo, :util do
197
- run(generate_ssh_wrapper)
198
- end
199
- ssh_wrapper_path
197
+ @ssh_executable ||= begin
198
+ roles :app_master, :app, :solo, :util do
199
+ run(generate_ssh_wrapper)
200
+ end
201
+ ssh_wrapper_path
202
+ end
200
203
  end
201
204
 
202
205
  # We specify 'IdentitiesOnly' to avoid failures on systems with > 5 private keys available.
@@ -206,15 +209,15 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
206
209
  # (Thanks Jim L.)
207
210
  def generate_ssh_wrapper
208
211
  path = ssh_wrapper_path
209
- identity_file = "~/.ssh/#{c.app}-deploy-key"
210
- <<-WRAP
212
+ <<-SCRIPT
213
+ mkdir -p #{File.dirname(path)}
211
214
  [[ -x #{path} ]] || cat > #{path} <<'SSH'
212
215
  #!/bin/sh
213
216
  unset SSH_AUTH_SOCK
214
- ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{identity_file} -o IdentitiesOnly=yes $*
217
+ ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{c.ssh_identity_file} -o IdentitiesOnly=yes $*
215
218
  SSH
216
219
  chmod 0700 #{path}
217
- WRAP
220
+ SCRIPT
218
221
  end
219
222
 
220
223
  def ssh_wrapper_path
@@ -242,7 +245,7 @@ WRAP
242
245
  def clean_release_directory(dir, count = 3)
243
246
  @cleanup_failed = true
244
247
  ordinal = count.succ.to_s
245
- info "~> Cleaning release directory: #{dir}"
248
+ shell.status "Cleaning release directory: #{dir}"
246
249
  sudo "ls -r #{dir} | tail -n +#{ordinal} | xargs -I@ rm -rf #{dir}/@"
247
250
  @cleanup_failed = false
248
251
  end
@@ -254,15 +257,15 @@ WRAP
254
257
  c.release_path = c.previous_release(rolled_back_release)
255
258
 
256
259
  revision = File.read(File.join(c.release_path, 'REVISION')).strip
257
- info "~> Rolling back to previous release: #{short_log_message(revision)}"
260
+ shell.status "Rolling back to previous release: #{short_log_message(revision)}"
258
261
 
259
262
  run_with_callbacks(:symlink)
260
263
  sudo "rm -rf #{rolled_back_release}"
261
264
  bundle
262
- info "~> Restarting with previous release."
265
+ shell.status "Restarting with previous release."
263
266
  with_maintenance_page { run_with_callbacks(:restart) }
264
267
  else
265
- info "~> Already at oldest release, nothing to roll back to."
268
+ shell.status "Already at oldest release, nothing to roll back to."
266
269
  exit(1)
267
270
  end
268
271
  end
@@ -273,17 +276,17 @@ WRAP
273
276
  @migrations_reached = true
274
277
  roles :app_master, :solo do
275
278
  cmd = "cd #{c.release_path} && PATH=#{c.binstubs_path}:$PATH #{c.framework_envs} #{c.migration_command}"
276
- info "~> Migrating: #{cmd}"
279
+ shell.status "Migrating: #{cmd}"
277
280
  run(cmd)
278
281
  end
279
282
  end
280
283
 
281
284
  # task
282
285
  def copy_repository_cache
283
- info "~> Copying to #{c.release_path}"
286
+ shell.status "Copying to #{c.release_path}"
284
287
  run("mkdir -p #{c.release_path} #{c.failed_release_dir} && rsync -aq #{c.exclusions} #{c.repository_cache}/ #{c.release_path}")
285
288
 
286
- info "~> Ensuring proper ownership."
289
+ shell.status "Ensuring proper ownership."
287
290
  sudo("chown -R #{c.user}:#{c.group} #{c.deploy_to}")
288
291
  end
289
292
 
@@ -300,18 +303,18 @@ WRAP
300
303
  end
301
304
 
302
305
  def setup_services
303
- info "~> Setting up external services."
306
+ shell.status "Setting up external services."
304
307
  previously_configured_services = parse_configured_services
305
308
  begin
306
309
  sudo(services_command_check)
307
310
  rescue StandardError => e
308
- info "Could not setup services. Upgrade your environment to get services configuration."
311
+ shell.info "Could not setup services. Upgrade your environment to get services configuration."
309
312
  return
310
313
  end
311
314
  sudo(services_setup_command)
312
315
  rescue StandardError => e
313
316
  unless previously_configured_services.empty?
314
- warning <<-WARNING
317
+ shell.warning <<-WARNING
315
318
  External services configuration not updated. Using previous version.
316
319
  Deploy again if your services configuration appears incomplete or out of date.
317
320
  #{e}
@@ -336,24 +339,24 @@ YML
336
339
  WRAP
337
340
  ["Symlink database.yml", "ln -nfs #{c.shared_path}/config/database.sqlite3.yml #{c.release_path}/config/database.yml"],
338
341
  ].each do |what, cmd|
339
- info "~> #{what}"
342
+ shell.status "#{what}"
340
343
  run(cmd)
341
344
  end
342
345
 
343
346
  owner = [c.user, c.group].join(':')
344
- info "~> Setting ownership to #{owner}"
347
+ shell.status "Setting ownership to #{owner}"
345
348
  sudo "chown -R #{owner} #{c.release_path}"
346
349
  end
347
350
  end
348
351
 
349
352
  def symlink_configs(release_to_link=c.release_path)
350
- info "~> Preparing shared resources for release."
353
+ shell.status "Preparing shared resources for release."
351
354
  symlink_tasks(release_to_link).each do |what, cmd|
352
- info "~> #{what}"
355
+ shell.substatus what
353
356
  run(cmd)
354
357
  end
355
358
  owner = [c.user, c.group].join(':')
356
- info "~> Setting ownership to #{owner}"
359
+ shell.status "Setting ownership to #{owner}"
357
360
  sudo "chown -R #{owner} #{release_to_link}"
358
361
  end
359
362
 
@@ -376,7 +379,7 @@ WRAP
376
379
 
377
380
  # task
378
381
  def symlink(release_to_link=c.release_path)
379
- info "~> Symlinking code."
382
+ shell.status "Symlinking code."
380
383
  run "rm -f #{c.current_path} && ln -nfs #{release_to_link} #{c.current_path} && chown -R #{c.user}:#{c.group} #{c.current_path}"
381
384
  @symlink_changed = true
382
385
  rescue Exception
@@ -388,12 +391,12 @@ WRAP
388
391
  def callback(what)
389
392
  @callbacks_reached ||= true
390
393
  if File.exist?("#{c.release_path}/deploy/#{what}.rb")
394
+ shell.status "Running deploy hook: deploy/#{what}.rb"
391
395
  run Escape.shell_command(base_callback_command_for(what)) do |server, cmd|
392
- per_instance_args = [
393
- '--current-roles', server.roles.join(' '),
394
- '--config', c.to_json,
395
- ]
396
- per_instance_args << '--current-name' << server.name.to_s if server.name
396
+ per_instance_args = []
397
+ per_instance_args << '--current-roles' << server.roles.join(' ')
398
+ per_instance_args << '--current-name' << server.name.to_s if server.name
399
+ per_instance_args << '--config' << c.to_json
397
400
  cmd << " " << Escape.shell_command(per_instance_args)
398
401
  end
399
402
  end
@@ -401,8 +404,20 @@ WRAP
401
404
 
402
405
  protected
403
406
 
404
- def starting_time
405
- @starting_time ||= Time.now
407
+ # Use [] to access attributes instead of calling methods so
408
+ # that we get nils instead of NoMethodError.
409
+ #
410
+ # Rollback doesn't know about the repository location (nor
411
+ # should it need to), but it would like to use #short_log_message.
412
+ def strategy
413
+ ENV['GIT_SSH'] = ssh_executable
414
+ @strategy ||= config.strategy_class.new(
415
+ shell,
416
+ :repository_cache => config[:repository_cache],
417
+ :app => config[:app],
418
+ :repo => config[:repo],
419
+ :ref => config[:branch]
420
+ )
406
421
  end
407
422
 
408
423
  def gemfile?
@@ -410,11 +425,14 @@ WRAP
410
425
  end
411
426
 
412
427
  def base_callback_command_for(what)
413
- [serverside_bin, 'hook', what.to_s,
414
- '--app', config.app,
415
- '--release-path', config.release_path.to_s,
416
- '--framework-env', c.environment.to_s,
417
- ].compact
428
+ cmd = [serverside_bin, 'hook', what.to_s]
429
+ cmd << '--app' << config.app
430
+ cmd << '--environment-name' << config.environment_name
431
+ cmd << '--account-name' << config.account_name
432
+ cmd << '--release-path' << config.release_path.to_s
433
+ cmd << '--framework-env' << config.environment.to_s
434
+ cmd << '--verbose' if config.verbose
435
+ cmd
418
436
  end
419
437
 
420
438
  def serverside_bin
@@ -424,21 +442,22 @@ WRAP
424
442
 
425
443
  def puts_deploy_failure
426
444
  if @cleanup_failed
427
- info "~> [Relax] Your site is running new code, but clean up of old deploys failed."
445
+ shell.notice "[Relax] Your site is running new code, but clean up of old deploys failed."
428
446
  elsif @maintenance_up
429
- info "~> [Attention] Maintenance page still up, consider the following before removing:"
430
- info " * Deploy hooks ran. This might cause problems for reverting to old code." if @callbacks_reached
431
- info " * Migrations ran. This might cause problems for reverting to old code." if @migrations_reached
447
+ message = "[Attention] Maintenance page still up, consider the following before removing:\n"
448
+ message << " * Deploy hooks ran. This might cause problems for reverting to old code.\n" if @callbacks_reached
449
+ message << " * Migrations ran. This might cause problems for reverting to old code.\n" if @migrations_reached
432
450
  if @symlink_changed
433
- info " * Your new code is symlinked as current."
451
+ message << " * Your new code is symlinked as current.\n"
434
452
  else
435
- info " * Your old code is still symlinked as current."
453
+ message << " * Your old code is still symlinked as current.\n"
436
454
  end
437
- info " * Application servers failed to restart." if @restart_failed
438
- info ""
439
- info "~> Need help? File a ticket for support."
455
+ message << " * Application servers failed to restart.\n" if @restart_failed
456
+ message << "\n"
457
+ message << "Need help? File a ticket for support.\n"
458
+ shell.notice message
440
459
  else
441
- info "~> [Relax] Your site is still running old code and nothing destructive has occurred."
460
+ shell.notice "[Relax] Your site is still running old code and nothing destructive has occurred."
442
461
  end
443
462
  end
444
463
 
@@ -451,7 +470,7 @@ WRAP
451
470
  def with_failed_release_cleanup
452
471
  yield
453
472
  rescue Exception
454
- info "~> Release #{c.release_path} failed, saving release to #{c.failed_release_dir}."
473
+ shell.status "Release #{c.release_path} failed, saving release to #{c.failed_release_dir}."
455
474
  sudo "mv #{c.release_path} #{c.failed_release_dir}"
456
475
  raise
457
476
  end
@@ -484,7 +503,7 @@ WRAP
484
503
 
485
504
  def check_ruby_bundler
486
505
  if gemfile?
487
- info "~> Bundling gems..."
506
+ shell.status "Bundling gems..."
488
507
 
489
508
  clean_bundle_on_system_version_change
490
509
 
@@ -515,18 +534,13 @@ WRAP
515
534
 
516
535
  def check_node_npm
517
536
  if File.exist?("#{c.release_path}/package.json")
518
- info "~> package.json detected, installing npm packages"
537
+ shell.info "~> package.json detected, installing npm packages"
519
538
  run "cd #{c.release_path} && npm install"
520
539
  end
521
540
  end
522
541
  end # DeployBase
523
542
 
524
543
  class Deploy < DeployBase
525
- def self.new(config)
526
- # include the correct fetch strategy
527
- include EY::Serverside::Strategies.const_get(config.strategy)::Helpers
528
- super
529
- end
530
544
  end
531
545
  end
532
546
  end