Class: EY::Serverside::DeployBase
Direct Known Subclasses
Deploy
Instance Attribute Summary
Attributes inherited from Task
#config, #servers, #shell
Instance Method Summary
collapse
#app_builds_own_assets?, #app_disables_assets?, #app_has_asset_task?, #app_needs_assets?, #bundled_rails_version, #compile_assets, #keep_existing_assets
Methods inherited from Task
#c, #initialize, #load_ey_yml, #paths, #require_custom_tasks, #roles, #run, #sudo
Instance Method Details
210
211
212
213
214
215
|
# File 'lib/engineyard-serverside/deploy.rb', line 210
def bundle
roles :app_master, :app, :solo, :util do
check_ruby_bundler
check_node_npm
end
end
|
#cached_deploy ⇒ Object
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
# File 'lib/engineyard-serverside/deploy.rb', line 20
def cached_deploy
shell.status "Deploying app from cached copy at #{Time.now.asctime}"
require_custom_tasks
load_ey_yml
push_code
shell.status "Starting full deploy"
copy_repository_cache
check_repository
with_failed_release_cleanup do
create_revision_file
run_with_callbacks(:bundle)
setup_services
check_for_ey_config
symlink_configs
setup_sqlite3_if_necessary
run_with_callbacks(:compile_assets)
enable_maintenance_page
run_with_callbacks(:migrate)
callback(:before_symlink)
symlink
end
callback(:after_symlink)
run_with_callbacks(:restart)
disable_maintenance_page
cleanup_old_releases
gc_repository_cache
shell.status "Finished deploy at #{Time.now.asctime}"
rescue Exception
shell.status "Finished failing to deploy at #{Time.now.asctime}"
puts_deploy_failure
raise
end
|
#callback(what) ⇒ Object
383
384
385
386
387
388
389
390
391
392
393
394
395
|
# File 'lib/engineyard-serverside/deploy.rb', line 383
def callback(what)
@callbacks_reached ||= true
if paths.deploy_hook(what).exist?
shell.status "Running deploy hook: deploy/#{what}.rb"
run Escape.shell_command(base_callback_command_for(what)) do |server, cmd|
per_instance_args = []
per_instance_args << '--current-roles' << server.roles.to_a.join(' ')
per_instance_args << '--current-name' << server.name.to_s if server.name
per_instance_args << '--config' << config.to_json
cmd << " " << Escape.shell_command(per_instance_args)
end
end
end
|
#check_for_ey_config ⇒ Object
83
84
85
86
87
88
89
90
|
# File 'lib/engineyard-serverside/deploy.rb', line 83
def check_for_ey_config
if gemfile? && lockfile
configured_services = parse_configured_services
if !configured_services.empty? && !lockfile.has_ey_config?
shell.warning "Gemfile.lock does not contain ey_config. Add it to get EY::Config access to: #{configured_services.keys.join(', ')}."
end
end
end
|
#check_repository ⇒ Object
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
# File 'lib/engineyard-serverside/deploy.rb', line 99
def check_repository
if gemfile?
shell.status "Gemfile found."
if lockfile
shell.status "Gemfile.lock found."
if !config.ignore_database_adapter_warning? && has_database? && !lockfile.any_database_adapter?
shell.warning "Gemfile.lock does not contain a recognized database adapter.\nA database-adapter gem such as pg, mysql2, mysql, or do_mysql was expected.\nThis can prevent applications that use PostgreSQL or MySQL from booting.\n\nTo fix, add any needed adapter to your Gemfile, bundle, commit, and redeploy.\nApplications not using PostgreSQL or MySQL can safely ignore this warning by\nadding ignore_database_adapter_warning: true to the application's ey.yml file\nunder this environment's name and adding the file to your repository.\n WARN\n end\n else\n shell.warning <<-WARN\nGemfile.lock is missing!\nYou can get different versions of gems in production than what you tested with.\nYou can get different versions of gems on every deployment even if your Gemfile hasn't changed.\nDeploying will take longer.\n\nTo fix this problem, commit your Gemfile.lock to your repository and redeploy.\n WARN\n end\n else\n shell.status \"No Gemfile. Deploying without bundler support.\"\n end\nend\n"
|
#clean_environment ⇒ Object
GIT_SSH needs to be defined in the environment for customers with private bundler repos in their Gemfile.
177
178
179
|
# File 'lib/engineyard-serverside/deploy.rb', line 177
def clean_environment
%Q[export GIT_SSH="#{ssh_executable}" && export LANG="en_US.UTF-8" && unset RUBYOPT BUNDLE_PATH BUNDLE_FROZEN BUNDLE_WITHOUT BUNDLE_BIN BUNDLE_GEMFILE]
end
|
#clean_release_directory(dir, count = 3) ⇒ Object
Remove all but the most-recent count releases from the specified
release directory.
IMPORTANT: This expects the release directory naming convention to be
something with a sensible lexical order. Violate that at your peril.
227
228
229
230
231
232
233
|
# File 'lib/engineyard-serverside/deploy.rb', line 227
def clean_release_directory(dir, count = 3)
@cleanup_failed = true
ordinal = count.succ.to_s
shell.status "Cleaning release directory: #{dir}"
sudo "ls -r #{dir} | tail -n +#{ordinal} | xargs -I@ rm -rf #{dir}/@"
@cleanup_failed = false
end
|
#cleanup_old_releases ⇒ Object
218
219
220
221
|
# File 'lib/engineyard-serverside/deploy.rb', line 218
def cleanup_old_releases
clean_release_directory(paths.releases)
clean_release_directory(paths.releases_failed)
end
|
#copy_repository_cache ⇒ Object
272
273
274
275
276
277
278
279
|
# File 'lib/engineyard-serverside/deploy.rb', line 272
def copy_repository_cache
shell.status "Copying to #{paths.active_release}"
exclusions = Array(config.copy_exclude).map { |e| %|--exclude="#{e}"| }.join(' ')
run("mkdir -p #{paths.active_release} #{paths.releases_failed} #{paths.shared_config} && rsync -aq #{exclusions} #{paths.repository_cache}/ #{paths.active_release}")
shell.status "Ensuring proper ownership."
sudo("chown -R #{config.user}:#{config.group} #{paths.active_release} #{paths.releases_failed}")
end
|
#create_revision_file ⇒ Object
281
282
283
|
# File 'lib/engineyard-serverside/deploy.rb', line 281
def create_revision_file
run create_revision_file_command
end
|
#create_revision_file_command ⇒ Object
67
68
69
|
# File 'lib/engineyard-serverside/deploy.rb', line 67
def create_revision_file_command
strategy.create_revision_file_command(paths.active_release)
end
|
14
15
16
17
18
|
# File 'lib/engineyard-serverside/deploy.rb', line 14
def deploy
shell.status "Starting deploy at #{shell.start_time.asctime}"
update_repository_cache
cached_deploy
end
|
#disable_maintenance_page ⇒ Object
143
144
145
|
# File 'lib/engineyard-serverside/deploy.rb', line 143
def disable_maintenance_page
maintenance.conditionally_disable
end
|
#enable_maintenance_page ⇒ Object
139
140
141
|
# File 'lib/engineyard-serverside/deploy.rb', line 139
def enable_maintenance_page
maintenance.conditionally_enable
end
|
#gc_repository_cache ⇒ Object
63
64
65
|
# File 'lib/engineyard-serverside/deploy.rb', line 63
def gc_repository_cache
strategy.gc_repository_cache
end
|
#generate_ssh_wrapper ⇒ Object
196
197
198
199
200
201
202
203
204
205
206
207
|
# File 'lib/engineyard-serverside/deploy.rb', line 196
def generate_ssh_wrapper
path = paths.ssh_wrapper
"mkdir -p \#{path.dirname}\n[[ -x \#{path} ]] || cat > \#{path} <<'SSH'\n#!/bin/sh\nunset SSH_AUTH_SOCK\nssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=\#{paths.deploy_key} -o IdentitiesOnly=yes $*\nSSH\nchmod 0700 \#{path}\n SCRIPT\nend\n"
|
#has_database? ⇒ Boolean
The nodatabase.yml file is dropped by server configuration when there is
no database in the cluster.
94
95
96
97
|
# File 'lib/engineyard-serverside/deploy.rb', line 94
def has_database?
paths.shared_config.join('database.yml').exist? &&
!paths.shared_config.join('nodatabase.yml').exist?
end
|
261
262
263
264
265
266
267
268
269
|
# File 'lib/engineyard-serverside/deploy.rb', line 261
def migrate
return unless config.migrate?
@migrations_reached = true
cmd = "cd #{paths.active_release} && PATH=#{paths.binstubs}:$PATH #{config.framework_envs} #{config.migration_command}"
roles :app_master, :solo do
shell.status "Migrating: #{cmd}"
run(cmd)
end
end
|
75
76
77
78
79
80
81
|
# File 'lib/engineyard-serverside/deploy.rb', line 75
def parse_configured_services
result = YAML.load_file "#{paths.shared_config}/ey_services_config_deploy.yml"
return {} unless result.is_a?(Hash)
result
rescue
{}
end
|
#push_code ⇒ Object
154
155
156
157
158
159
160
|
# File 'lib/engineyard-serverside/deploy.rb', line 154
def push_code
shell.status "Pushing code to all servers"
servers.remote.run_on_each do |server|
cmd = server.sync_directory_command(paths.repository_cache)
shell.logged_system(cmd)
end
end
|
163
164
165
166
167
168
169
170
|
# File 'lib/engineyard-serverside/deploy.rb', line 163
def restart
@restart_failed = true
shell.status "Restarting app servers"
roles :app_master, :app, :solo do
run(restart_command)
end
@restart_failed = false
end
|
#restart_command ⇒ Object
172
173
174
|
# File 'lib/engineyard-serverside/deploy.rb', line 172
def restart_command
%{LANG="en_US.UTF-8" /engineyard/bin/app_#{config.app} deploy}
end
|
#restart_with_maintenance_page ⇒ Object
131
132
133
134
135
136
137
|
# File 'lib/engineyard-serverside/deploy.rb', line 131
def restart_with_maintenance_page
require_custom_tasks
load_ey_yml
enable_maintenance_page
restart
disable_maintenance_page
end
|
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
# File 'lib/engineyard-serverside/deploy.rb', line 236
def rollback
if config.rollback_paths!
begin
rolled_back_release = paths.latest_release
shell.status "Rolling back to previous release: #{short_log_message(config.active_revision)}"
run_with_callbacks(:symlink)
sudo "rm -rf #{rolled_back_release}"
bundle
shell.status "Restarting with previous release."
enable_maintenance_page
run_with_callbacks(:restart)
disable_maintenance_page
shell.status "Finished rollback at #{Time.now.asctime}"
rescue Exception
shell.status "Failed to rollback at #{Time.now.asctime}"
puts_deploy_failure
raise
end
else
shell.fatal "Already at oldest release, nothing to roll back to."
exit(1)
end
end
|
#run_with_callbacks(task) ⇒ Object
147
148
149
150
151
|
# File 'lib/engineyard-serverside/deploy.rb', line 147
def run_with_callbacks(task)
callback("before_#{task}")
send(task)
callback("after_#{task}")
end
|
#services_command_check ⇒ Object
285
286
287
|
# File 'lib/engineyard-serverside/deploy.rb', line 285
def services_command_check
"which /usr/local/ey_resin/ruby/bin/ey-services-setup >/dev/null 2>&1"
end
|
#services_setup_command ⇒ Object
289
290
291
|
# File 'lib/engineyard-serverside/deploy.rb', line 289
def services_setup_command
"/usr/local/ey_resin/ruby/bin/ey-services-setup #{config.app}"
end
|
#setup_services ⇒ Object
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
|
# File 'lib/engineyard-serverside/deploy.rb', line 293
def setup_services
shell.status "Setting up external services."
previously_configured_services = parse_configured_services
begin
sudo(services_command_check)
rescue StandardError => e
shell.info "Could not setup services. Upgrade your environment to get services configuration."
return
end
begin
sudo(services_setup_command)
rescue StandardError => e
unless previously_configured_services.empty?
shell.warning "External services configuration not updated. Using previous version.\nDeploy again if your services configuration appears incomplete or out of date.\n\#{e}\n WARNING\n end\n end\nend\n"
|
#setup_sqlite3_if_necessary ⇒ Object
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
# File 'lib/engineyard-serverside/deploy.rb', line 317
def setup_sqlite3_if_necessary
if gemfile? && lockfile && lockfile.uses_sqlite3?
[
["Create databases directory if needed", "mkdir -p #{paths.shared}/databases"],
["Creating SQLite database if needed", "touch #{paths.shared}/databases/#{config.framework_env}.sqlite3"],
["Create config directory if needed", "mkdir -p #{paths.active_release_config}"],
["Generating SQLite config", "cat > \#{paths.shared_config}/database.sqlite3.yml<<'YML'\n\#{config.framework_env}:\n adapter: sqlite3\n database: \#{paths.shared}/databases/\#{config.framework_env}.sqlite3\n pool: 5\n timeout: 5000\nYML\n"],
["Symlink database.yml", "ln -nfs #{paths.shared_config}/database.sqlite3.yml #{paths.active_release_config}/database.yml"],
].each do |what, cmd|
shell.status "#{what}"
run(cmd)
end
owner = [config.user, config.group].join(':')
shell.status "Setting ownership to #{owner}"
sudo "chown -R #{owner} #{paths.active_release}"
end
end
|
#short_log_message(revision) ⇒ Object
71
72
73
|
# File 'lib/engineyard-serverside/deploy.rb', line 71
def short_log_message(revision)
strategy.short_log_message(revision)
end
|
#ssh_executable ⇒ Object
create ssh wrapper on all servers
182
183
184
185
186
187
188
189
|
# File 'lib/engineyard-serverside/deploy.rb', line 182
def ssh_executable
@ssh_executable ||= begin
roles :app_master, :app, :solo, :util do
run(generate_ssh_wrapper)
end
paths.ssh_wrapper
end
end
|
373
374
375
376
377
378
379
380
381
|
# File 'lib/engineyard-serverside/deploy.rb', line 373
def symlink
shell.status "Symlinking code."
run "rm -f #{paths.current} && ln -nfs #{paths.active_release} #{paths.current} && find #{paths.current} -not -user #{config.user} -or -not -group #{config.group} -exec chown #{config.user}:#{config.group} {} +"
@symlink_changed = true
rescue Exception
sudo "rm -f #{paths.current} && ln -nfs #{paths.previous_release(paths.active_release)} #{paths.current} && chown -R #{config.user}:#{config.group} #{paths.current}"
@symlink_changed = false
raise
end
|
#symlink_configs ⇒ Object
344
345
346
347
348
349
350
351
352
353
|
# File 'lib/engineyard-serverside/deploy.rb', line 344
def symlink_configs
shell.status "Preparing shared resources for release."
symlink_tasks.each do |what, cmd|
shell.substatus what
run(cmd)
end
owner = [config.user, config.group].join(':')
shell.status "Setting ownership to #{owner}"
sudo "chown -R #{owner} #{paths.active_release}"
end
|
#symlink_tasks ⇒ Object
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
|
# File 'lib/engineyard-serverside/deploy.rb', line 355
def symlink_tasks
[
["Set group write permissions", "chmod -R g+w #{paths.active_release}"],
["Remove symlinked shared directories", "rm -rf #{paths.active_log} #{paths.public_system} #{paths.active_release}/tmp/pids"],
["Create tmp directory", "mkdir -p #{paths.active_release}/tmp"],
["Create public directory", "mkdir -p #{paths.public}"],
["Create config directory", "mkdir -p #{paths.active_release_config}"],
["Symlink shared log directory", "ln -nfs #{paths.shared_log} #{paths.active_log}"],
["Symlink public system directory", "ln -nfs #{paths.shared_system} #{paths.public_system}"],
["Symlink shared pids directory", "ln -nfs #{paths.shared}/pids #{paths.active_release}/tmp/pids"],
["Symlink other shared config files", "find #{paths.shared_config} -type f -not -name 'database.yml' -exec ln -s {} #{paths.active_release_config} \\;"],
["Symlink database.yml if needed", "if [ -f \"#{paths.shared_config}/database.yml\" ]; then ln -nfs #{paths.shared_config}/database.yml #{paths.active_release_config}/database.yml; fi"],
["Symlink newrelic.yml if needed", "if [ -f \"#{paths.shared_config}/newrelic.yml\" ]; then ln -nfs #{paths.shared_config}/newrelic.yml #{paths.active_release_config}/newrelic.yml; fi"],
["Symlink mongrel_cluster.yml if needed", "if [ -f \"#{paths.shared_config}/mongrel_cluster.yml\" ]; then ln -nfs #{paths.shared_config}/mongrel_cluster.yml #{paths.active_release_config}/mongrel_cluster.yml; fi"],
]
end
|
#update_repository_cache ⇒ Object
59
60
61
|
# File 'lib/engineyard-serverside/deploy.rb', line 59
def update_repository_cache
strategy.update_repository_cache
end
|