engineyard-serverside 1.5.29.pre.timestamps2 → 1.5.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,8 +26,6 @@ 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
-
31
29
 
32
30
  module EY
33
31
  module Serverside
@@ -51,12 +51,14 @@ module EY
51
51
  desc "deploy", "Deploy code from /data/<app>"
52
52
  def deploy(default_task=:deploy)
53
53
  config = EY::Serverside::Deploy::Configuration.new(options)
54
- shell = init_shell(config.verbose, "#{config.app}-deploy")
55
54
  load_servers(config)
56
55
 
57
- propagate(shell)
56
+ EY::Serverside::LoggedOutput.verbose = options[:verbose]
57
+ EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-deploy.log")
58
58
 
59
- EY::Serverside::Deploy.new(config, shell).send(default_task)
59
+ invoke :propagate
60
+
61
+ EY::Serverside::Deploy.new(config).send(default_task)
60
62
  end
61
63
 
62
64
  method_option :app, :type => :string,
@@ -118,6 +120,9 @@ module EY
118
120
  :aliases => ["-v"]
119
121
  desc "integrate", "Integrate other instances into this cluster"
120
122
  def integrate
123
+ EY::Serverside::LoggedOutput.verbose = options[:verbose]
124
+ EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-integrate.log")
125
+
121
126
  app_dir = Pathname.new "/data/#{options[:app]}"
122
127
  current_app_dir = app_dir + "current"
123
128
 
@@ -129,23 +134,22 @@ module EY
129
134
  integrate_options[:branch] = (current_app_dir + 'REVISION').read.strip
130
135
 
131
136
  config = EY::Serverside::Deploy::Configuration.new(integrate_options)
132
- shell = init_shell(config.verbose, "#{config.app}-integrate")
133
137
 
134
138
  load_servers(config)
135
139
 
136
- propagate(shell)
140
+ invoke :propagate
137
141
 
138
142
  EY::Serverside::Server.all.each do |server|
139
- server.sync_directory(app_dir) { |cmd| shell.logged_system(cmd) }
143
+ server.sync_directory app_dir
140
144
  # we're just about to recreate this, so it has to be gone
141
145
  # first. otherwise, non-idempotent deploy hooks could screw
142
146
  # things up, and since we don't control deploy hooks, we must
143
147
  # assume the worst.
144
- run_on_server(server,"rm -rf #{current_app_dir}")
148
+ server.run("rm -rf #{current_app_dir}")
145
149
  end
146
150
 
147
151
  # deploy local-ref to other instances into /data/$app/local-current
148
- EY::Serverside::Deploy.new(config, shell).cached_deploy
152
+ EY::Serverside::Deploy.new(config).cached_deploy
149
153
  end
150
154
 
151
155
  method_option :app, :type => :string,
@@ -173,13 +177,15 @@ module EY
173
177
  :aliases => ["-v"]
174
178
  desc "restart", "Restart app servers, conditionally enabling maintenance page"
175
179
  def restart
180
+ EY::Serverside::LoggedOutput.verbose = options[:verbose]
181
+ EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-restart.log")
182
+
176
183
  config = EY::Serverside::Deploy::Configuration.new(options)
177
- shell = init_shell(config.verbose, "#{config.app}-restart")
178
184
  load_servers(config)
179
185
 
180
- propagate(shell)
186
+ invoke :propagate
181
187
 
182
- EY::Serverside::Deploy.new(config, shell).restart_with_maintenance_page
188
+ EY::Serverside::Deploy.new(config).restart_with_maintenance_page
183
189
  end
184
190
 
185
191
  desc "install_bundler [VERSION]", "Make sure VERSION of bundler is installed (in system ruby)"
@@ -197,10 +203,8 @@ module EY
197
203
  end
198
204
  end
199
205
 
200
- private
201
-
202
- # Put the same engineyard-serverside on all the servers (Used to be public but is unused as an actual CLI command now)
203
- def propagate(shell)
206
+ desc "propagate", "Propagate the engineyard-serverside gem to the other instances in the cluster. This will install exactly version #{EY::Serverside::VERSION}."
207
+ def propagate
204
208
  config = EY::Serverside::Deploy::Configuration.new
205
209
  gem_filename = "engineyard-serverside-#{EY::Serverside::VERSION}.gem"
206
210
  local_gem_file = File.join(Gem.dir, 'cache', gem_filename)
@@ -215,37 +219,28 @@ module EY
215
219
  # 0.5.11, and mistakenly thinking 0.5.1 is there
216
220
  has_gem_cmd = "#{gem_binary} list engineyard-serverside | grep \"engineyard-serverside\" | egrep -q '#{egrep_escaped_version}[,)]'"
217
221
 
218
- if !run_on_server(server,has_gem_cmd) # doesn't have this exact version
219
- shell.status "Installing engineyard-serverside on #{server.hostname}"
222
+ if !server.run(has_gem_cmd) # doesn't have this exact version
223
+ puts "~> Installing engineyard-serverside on #{server.hostname}"
220
224
 
221
- shell.logged_system(Escape.shell_command([
225
+ system(Escape.shell_command([
222
226
  'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
223
227
  "-o", "StrictHostKeyChecking=no",
224
228
  local_gem_file,
225
229
  "#{config.user}@#{server.hostname}:#{remote_gem_file}",
226
230
  ]))
227
- run_on_server(server,"sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
231
+ server.run("sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
228
232
  end
229
233
  end
230
234
 
231
235
  EY::Serverside::Future.success?(futures)
232
236
  end
233
237
 
234
- def init_shell(verbose, log_basename)
235
- EY::Serverside::Shell.new(
236
- :verbose => verbose,
237
- :log_path => File.join(ENV['HOME'], "#{log_basename}.log")
238
- )
239
- end
238
+ private
240
239
 
241
240
  def load_servers(config)
242
241
  EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
243
242
  end
244
243
 
245
- def run_on_server(server, command)
246
- server.run(command) { |cmd| shell.logged_system(cmd) }
247
- end
248
-
249
244
  def assemble_instance_hashes(config)
250
245
  options[:instances].collect { |hostname|
251
246
  { :hostname => hostname,
@@ -55,10 +55,6 @@ module EY
55
55
  EY::Serverside.node
56
56
  end
57
57
 
58
- def verbose
59
- configuration['verbose']
60
- end
61
-
62
58
  def app
63
59
  configuration['app'].to_s
64
60
  end
@@ -7,21 +7,22 @@ require 'engineyard-serverside/rails_asset_support'
7
7
  module EY
8
8
  module Serverside
9
9
  class DeployBase < Task
10
+ include LoggedOutput
10
11
  include ::EY::Serverside::RailsAssetSupport
11
12
 
12
13
  # default task
13
14
  def deploy
14
- shell.status "Starting deploy at #{shell.start_time.asctime}"
15
+ debug "Starting deploy at #{Time.now.asctime}"
15
16
  update_repository_cache
16
17
  cached_deploy
17
18
  end
18
19
 
19
20
  def cached_deploy
20
- shell.status "Deploying app from cached copy at #{Time.now.asctime}"
21
+ debug "Deploying app from cached copy at #{Time.now.asctime}"
21
22
  require_custom_tasks
22
23
  push_code
23
24
 
24
- shell.status "Starting full deploy"
25
+ info "~> Starting full deploy"
25
26
  copy_repository_cache
26
27
  check_repository
27
28
 
@@ -46,9 +47,9 @@ module EY
46
47
  disable_maintenance_page
47
48
 
48
49
  cleanup_old_releases
49
- shell.status "Finished deploy at #{Time.now.asctime}"
50
+ debug "Finished deploy at #{Time.now.asctime}"
50
51
  rescue Exception
51
- shell.status "Finished failing to deploy at #{Time.now.asctime}"
52
+ debug "Finished failing to deploy at #{Time.now.asctime}"
52
53
  puts_deploy_failure
53
54
  raise
54
55
  end
@@ -72,9 +73,9 @@ module EY
72
73
 
73
74
  def check_repository
74
75
  if gemfile?
75
- shell.status "Gemfile found."
76
+ info "~> Gemfile found."
76
77
  if lockfile
77
- shell.status "Gemfile.lock found."
78
+ info "~> Gemfile.lock found."
78
79
  unless lockfile.any_database_adapter?
79
80
  warning <<-WARN
80
81
  Gemfile.lock does not contain a recognized database adapter.
@@ -96,7 +97,7 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
96
97
  WARN
97
98
  end
98
99
  else
99
- shell.status "No Gemfile. Deploying without bundler support."
100
+ info "~> No Gemfile. Deploying without bundler support."
100
101
  end
101
102
  end
102
103
 
@@ -162,9 +163,9 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
162
163
 
163
164
  # task
164
165
  def push_code
165
- shell.status "Pushing code to all servers"
166
+ info "~> Pushing code to all servers"
166
167
  futures = EY::Serverside::Future.call(EY::Serverside::Server.all) do |server|
167
- server.sync_directory(config.repository_cache) { |cmd| shell.logged_system(cmd) }
168
+ server.sync_directory(config.repository_cache)
168
169
  end
169
170
  EY::Serverside::Future.success?(futures)
170
171
  end
@@ -172,7 +173,7 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
172
173
  # task
173
174
  def restart
174
175
  @restart_failed = true
175
- shell.status "Restarting app servers"
176
+ info "~> Restarting app servers"
176
177
  roles :app_master, :app, :solo do
177
178
  run(restart_command)
178
179
  end
@@ -242,7 +243,7 @@ WRAP
242
243
  def clean_release_directory(dir, count = 3)
243
244
  @cleanup_failed = true
244
245
  ordinal = count.succ.to_s
245
- shell.status "Cleaning release directory: #{dir}"
246
+ info "~> Cleaning release directory: #{dir}"
246
247
  sudo "ls -r #{dir} | tail -n +#{ordinal} | xargs -I@ rm -rf #{dir}/@"
247
248
  @cleanup_failed = false
248
249
  end
@@ -254,15 +255,15 @@ WRAP
254
255
  c.release_path = c.previous_release(rolled_back_release)
255
256
 
256
257
  revision = File.read(File.join(c.release_path, 'REVISION')).strip
257
- shell.status "Rolling back to previous release: #{short_log_message(revision)}"
258
+ info "~> Rolling back to previous release: #{short_log_message(revision)}"
258
259
 
259
260
  run_with_callbacks(:symlink)
260
261
  sudo "rm -rf #{rolled_back_release}"
261
262
  bundle
262
- shell.status "Restarting with previous release."
263
+ info "~> Restarting with previous release."
263
264
  with_maintenance_page { run_with_callbacks(:restart) }
264
265
  else
265
- shell.status "Already at oldest release, nothing to roll back to."
266
+ info "~> Already at oldest release, nothing to roll back to."
266
267
  exit(1)
267
268
  end
268
269
  end
@@ -273,17 +274,17 @@ WRAP
273
274
  @migrations_reached = true
274
275
  roles :app_master, :solo do
275
276
  cmd = "cd #{c.release_path} && PATH=#{c.binstubs_path}:$PATH #{c.framework_envs} #{c.migration_command}"
276
- shell.status "Migrating: #{cmd}"
277
+ info "~> Migrating: #{cmd}"
277
278
  run(cmd)
278
279
  end
279
280
  end
280
281
 
281
282
  # task
282
283
  def copy_repository_cache
283
- shell.status "Copying to #{c.release_path}"
284
+ info "~> Copying to #{c.release_path}"
284
285
  run("mkdir -p #{c.release_path} #{c.failed_release_dir} && rsync -aq #{c.exclusions} #{c.repository_cache}/ #{c.release_path}")
285
286
 
286
- shell.status "Ensuring proper ownership."
287
+ info "~> Ensuring proper ownership."
287
288
  sudo("chown -R #{c.user}:#{c.group} #{c.deploy_to}")
288
289
  end
289
290
 
@@ -299,12 +300,8 @@ WRAP
299
300
  "/usr/local/ey_resin/ruby/bin/ey-services-setup #{config.app}"
300
301
  end
301
302
 
302
- def node_package_manager_command_check
303
- "which npm"
304
- end
305
-
306
303
  def setup_services
307
- shell.status "Setting up external services."
304
+ info "~> Setting up external services."
308
305
  previously_configured_services = parse_configured_services
309
306
  begin
310
307
  sudo(services_command_check)
@@ -340,24 +337,24 @@ YML
340
337
  WRAP
341
338
  ["Symlink database.yml", "ln -nfs #{c.shared_path}/config/database.sqlite3.yml #{c.release_path}/config/database.yml"],
342
339
  ].each do |what, cmd|
343
- shell.status "#{what}"
340
+ info "~> #{what}"
344
341
  run(cmd)
345
342
  end
346
343
 
347
344
  owner = [c.user, c.group].join(':')
348
- shell.status "Setting ownership to #{owner}"
345
+ info "~> Setting ownership to #{owner}"
349
346
  sudo "chown -R #{owner} #{c.release_path}"
350
347
  end
351
348
  end
352
349
 
353
350
  def symlink_configs(release_to_link=c.release_path)
354
- shell.status "Preparing shared resources for release."
351
+ info "~> Preparing shared resources for release."
355
352
  symlink_tasks(release_to_link).each do |what, cmd|
356
- shell.status "#{what}"
353
+ info "~> #{what}"
357
354
  run(cmd)
358
355
  end
359
356
  owner = [c.user, c.group].join(':')
360
- shell.status "Setting ownership to #{owner}"
357
+ info "~> Setting ownership to #{owner}"
361
358
  sudo "chown -R #{owner} #{release_to_link}"
362
359
  end
363
360
 
@@ -380,7 +377,7 @@ WRAP
380
377
 
381
378
  # task
382
379
  def symlink(release_to_link=c.release_path)
383
- shell.status "Symlinking code."
380
+ info "~> Symlinking code."
384
381
  run "rm -f #{c.current_path} && ln -nfs #{release_to_link} #{c.current_path} && chown -R #{c.user}:#{c.group} #{c.current_path}"
385
382
  @symlink_changed = true
386
383
  rescue Exception
@@ -428,9 +425,9 @@ WRAP
428
425
 
429
426
  def puts_deploy_failure
430
427
  if @cleanup_failed
431
- shell.status "[Relax] Your site is running new code, but clean up of old deploys failed."
428
+ info "~> [Relax] Your site is running new code, but clean up of old deploys failed."
432
429
  elsif @maintenance_up
433
- shell.status "[Attention] Maintenance page still up, consider the following before removing:"
430
+ info "~> [Attention] Maintenance page still up, consider the following before removing:"
434
431
  info " * Deploy hooks ran. This might cause problems for reverting to old code." if @callbacks_reached
435
432
  info " * Migrations ran. This might cause problems for reverting to old code." if @migrations_reached
436
433
  if @symlink_changed
@@ -440,9 +437,9 @@ WRAP
440
437
  end
441
438
  info " * Application servers failed to restart." if @restart_failed
442
439
  info ""
443
- shell.status "Need help? File a ticket for support."
440
+ info "~> Need help? File a ticket for support."
444
441
  else
445
- shell.status "[Relax] Your site is still running old code and nothing destructive has occurred."
442
+ info "~> [Relax] Your site is still running old code and nothing destructive has occurred."
446
443
  end
447
444
  end
448
445
 
@@ -455,7 +452,7 @@ WRAP
455
452
  def with_failed_release_cleanup
456
453
  yield
457
454
  rescue Exception
458
- shell.status "Release #{c.release_path} failed, saving release to #{c.failed_release_dir}."
455
+ info "~> Release #{c.release_path} failed, saving release to #{c.failed_release_dir}."
459
456
  sudo "mv #{c.release_path} #{c.failed_release_dir}"
460
457
  raise
461
458
  end
@@ -488,7 +485,7 @@ WRAP
488
485
 
489
486
  def check_ruby_bundler
490
487
  if gemfile?
491
- shell.status "Bundling gems..."
488
+ info "~> Bundling gems..."
492
489
 
493
490
  clean_bundle_on_system_version_change
494
491
 
@@ -519,18 +516,14 @@ WRAP
519
516
 
520
517
  def check_node_npm
521
518
  if File.exist?("#{c.release_path}/package.json")
522
- unless run(node_package_manager_command_check)
523
- abort "*** [Error] package.json detected, but npm was not installed"
524
- else
525
- shell.status "package.json detected, installing npm packages"
526
- run "cd #{c.release_path} && npm install"
527
- end
519
+ info "~> package.json detected, installing npm packages"
520
+ run "cd #{c.release_path} && npm install"
528
521
  end
529
522
  end
530
523
  end # DeployBase
531
524
 
532
525
  class Deploy < DeployBase
533
- def self.new(config, shell=nil)
526
+ def self.new(config)
534
527
  # include the correct fetch strategy
535
528
  include EY::Serverside::Strategies.const_get(config.strategy)::Helpers
536
529
  super
@@ -1,8 +1,8 @@
1
1
  module EY
2
2
  module Serverside
3
3
  class DeployHook < Task
4
- def initialize(options, shell = nil)
5
- super(EY::Serverside::Deploy::Configuration.new(options), shell)
4
+ def initialize(options)
5
+ super(EY::Serverside::Deploy::Configuration.new(options))
6
6
  end
7
7
 
8
8
  def callback_context
@@ -3,14 +3,24 @@ module EY
3
3
  def self.deprecation_warning(msg)
4
4
  $stderr.puts "DEPRECATION WARNING: #{msg}"
5
5
  end
6
+ end
6
7
 
7
- def self.const_missing(const)
8
- if const == :LoggedOutput
9
- EY::Serverside.deprecation_warning("EY::Serverside::LoggedOutput has been deprecated. Use EY::Serverside::Shell::Helpers instead.")
10
- EY::Serverside::Shell::Helpers
11
- else
12
- super
13
- end
8
+ def self.const_missing(const)
9
+ if EY::Serverside.const_defined?(const)
10
+ EY::Serverside.deprecation_warning("EY::#{const} has been deprecated. use EY::Serverside::#{const} instead")
11
+ EY::Serverside.class_eval(const.to_s)
12
+ else
13
+ super
14
14
  end
15
15
  end
16
+
17
+ def self.node
18
+ EY::Serverside.deprecation_warning("EY.node has been deprecated. use EY::Serverside.node instead")
19
+ EY::Serverside.node
20
+ end
21
+
22
+ def self.dna_json
23
+ EY::Serverside.deprecation_warning("EY.dna_json has been deprecated. use EY::Serverside.dna_json instead")
24
+ EY::Serverside.dna_json
25
+ end
16
26
  end
@@ -24,7 +24,7 @@ module EY
24
24
  @contents.index(/^\s+#{type}\s\([^\)]+\)$/)
25
25
  end
26
26
 
27
- any_jruby_adapter = %w[mysql postgresql].any? do |type|
27
+ any_jruby_adapter = %w[mysql postgresql postgres].any? do |type|
28
28
  @contents.index(/^\s+jdbc-#{type}\s\([^\)]+\)$/) || @contents.index(/^\s+activerecord-jdbc#{type}-adapter\s\([^\)]+\)$/)
29
29
  end
30
30
 
@@ -0,0 +1,90 @@
1
+ require 'open4'
2
+
3
+ module EY
4
+ module Serverside
5
+ module LoggedOutput
6
+
7
+ class Tee
8
+ def initialize(*streams)
9
+ @streams = streams.flatten
10
+ end
11
+
12
+ def <<(output)
13
+ @streams.each do |s|
14
+ s << output
15
+ s.flush
16
+ end
17
+ self
18
+ end
19
+ end # Tee
20
+
21
+ @@logfile = File.join(ENV['HOME'], 'ey.log')
22
+ def self.logfile=(filename)
23
+ File.unlink filename if File.exist?(filename) # start fresh
24
+ @@logfile = filename
25
+ end
26
+
27
+ def self.logfile
28
+ @@logfile
29
+ end
30
+
31
+ @@verbose = false
32
+ def self.verbose=(v)
33
+ @@verbose = !!v
34
+ end
35
+
36
+ def self.verbose?
37
+ @@verbose
38
+ end
39
+
40
+ def verbose?
41
+ EY::Serverside::LoggedOutput.verbose?
42
+ end
43
+
44
+ def warning(msg)
45
+ info "WARNING: #{msg}\n".gsub(/^/,'!> ')
46
+ end
47
+
48
+ def info(msg)
49
+ with_logfile do |log|
50
+ Tee.new($stdout, log) << ("#{with_timestamp(msg)}\n")
51
+ end
52
+ end
53
+
54
+ def debug(msg)
55
+ with_logfile do |log|
56
+ log << "#{with_timestamp(msg)}\n"
57
+ end
58
+ end
59
+
60
+ def logged_system(cmd)
61
+ with_logfile do |log|
62
+ out = verbose? ? Tee.new($stdout, log) : log
63
+ err = Tee.new($stderr, log) # we always want to see errors
64
+
65
+ out << with_timestamp(":: running #{cmd}\n")
66
+
67
+ # :quiet means don't raise an error on nonzero exit status
68
+ status = Open4.spawn cmd, 0 => '', 1 => out, 2 => err, :quiet => true
69
+ status.exitstatus == 0
70
+ end
71
+ end
72
+
73
+ private
74
+ def with_logfile
75
+ File.open(logfile, 'a') {|f| yield f }
76
+ end
77
+
78
+ def logfile
79
+ EY::Serverside::LoggedOutput.logfile
80
+ end
81
+
82
+ def with_timestamp(msg)
83
+ return msg unless respond_to?(:starting_time)
84
+ time_passed = Time.now.to_i - starting_time.to_i
85
+ timestamp = "+%2dm %02ds " % time_passed.divmod(60)
86
+ msg.gsub(/^/, timestamp)
87
+ end
88
+ end
89
+ end
90
+ end