engineyard-serverside 1.5.35.pre.2 → 1.6.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/lib/engineyard-serverside.rb +3 -1
  2. data/lib/engineyard-serverside/cli.rb +73 -38
  3. data/lib/engineyard-serverside/configuration.rb +38 -12
  4. data/lib/engineyard-serverside/deploy.rb +63 -51
  5. data/lib/engineyard-serverside/deploy_hook.rb +21 -18
  6. data/lib/engineyard-serverside/deprecation.rb +9 -17
  7. data/lib/engineyard-serverside/lockfile_parser.rb +1 -1
  8. data/lib/engineyard-serverside/rails_asset_support.rb +5 -5
  9. data/lib/engineyard-serverside/server.rb +8 -11
  10. data/lib/engineyard-serverside/shell.rb +101 -0
  11. data/lib/engineyard-serverside/shell/formatter.rb +70 -0
  12. data/lib/engineyard-serverside/shell/helpers.rb +29 -0
  13. data/lib/engineyard-serverside/strategies/git.rb +12 -15
  14. data/lib/engineyard-serverside/task.rb +28 -5
  15. data/lib/engineyard-serverside/version.rb +1 -1
  16. data/lib/vendor/open4/lib/open4.rb +432 -0
  17. data/spec/basic_deploy_spec.rb +9 -9
  18. data/spec/bundler_deploy_spec.rb +1 -1
  19. data/spec/custom_deploy_spec.rb +45 -4
  20. data/spec/deploy_hook_spec.rb +77 -78
  21. data/spec/deprecation_spec.rb +4 -26
  22. data/spec/git_strategy_spec.rb +6 -2
  23. data/spec/nodejs_deploy_spec.rb +2 -2
  24. data/spec/services_deploy_spec.rb +11 -10
  25. data/spec/shell_spec.rb +48 -0
  26. data/spec/spec_helper.rb +48 -25
  27. data/spec/sqlite3_deploy_spec.rb +1 -2
  28. data/spec/support/integration.rb +1 -13
  29. metadata +57 -97
  30. data/lib/engineyard-serverside/logged_output.rb +0 -91
  31. data/lib/vendor/systemu/LICENSE +0 -3
  32. data/lib/vendor/systemu/lib/systemu.rb +0 -363
  33. data/lib/vendor/systemu/systemu.gemspec +0 -45
  34. data/spec/fixtures/gitrepo/bar +0 -0
  35. data/spec/logged_output_spec.rb +0 -55
@@ -1,40 +1,51 @@
1
+ require 'engineyard-serverside/shell/helpers'
2
+
1
3
  module EY
2
4
  module Serverside
3
5
  class DeployHook < Task
4
- def initialize(options)
5
- super(EY::Serverside::Deploy::Configuration.new(options))
6
- end
7
-
8
6
  def callback_context
9
- @context ||= CallbackContext.new(config)
7
+ @context ||= CallbackContext.new(config, shell)
10
8
  end
11
9
 
12
10
  def run(hook)
13
11
  hook_path = "#{c.release_path}/deploy/#{hook}.rb"
14
12
  if File.exist?(hook_path)
15
13
  Dir.chdir(c.release_path) do
16
- puts "~> running deploy hook: deploy/#{hook}.rb"
14
+ shell.status "Running deploy hook: deploy/#{hook}.rb"
17
15
  if desc = syntax_error(hook_path)
18
16
  hook_name = File.basename(hook_path)
19
17
  abort "*** [Error] Invalid Ruby syntax in hook: #{hook_name} ***\n*** #{desc.chomp} ***"
20
18
  else
21
- callback_context.instance_eval(IO.read(hook_path))
19
+ eval_hook(IO.read(hook_path))
22
20
  end
23
21
  end
24
22
  end
25
23
  end
26
24
 
25
+ def eval_hook(code)
26
+ callback_context.instance_eval(code)
27
+ end
28
+
27
29
  def syntax_error(file)
28
30
  output = `ruby -c #{file} 2>&1`
29
31
  output unless output =~ /Syntax OK/
30
32
  end
31
33
 
32
34
  class CallbackContext
33
- def initialize(config)
35
+ include EY::Serverside::Shell::Helpers
36
+
37
+ attr_reader :shell
38
+
39
+ def initialize(config, shell)
34
40
  @configuration = config
41
+ @shell = shell
35
42
  @node = node
36
43
  end
37
44
 
45
+ def config
46
+ @configuration
47
+ end
48
+
38
49
  def method_missing(meth, *args, &blk)
39
50
  if @configuration.respond_to?(meth)
40
51
  @configuration.send(meth, *args, &blk)
@@ -48,19 +59,11 @@ module EY
48
59
  end
49
60
 
50
61
  def run(cmd)
51
- system(Escape.shell_command(["sh", "-l", "-c", cmd]))
62
+ shell.logged_system(Escape.shell_command(["sh", "-l", "-c", cmd]))
52
63
  end
53
64
 
54
65
  def sudo(cmd)
55
- system(Escape.shell_command(["sudo", "sh", "-l", "-c", cmd]))
56
- end
57
-
58
- def info(*args)
59
- $stderr.puts *args
60
- end
61
-
62
- def debug(*args)
63
- $stdout.puts *args
66
+ shell.logged_system(Escape.shell_command(["sudo", "sh", "-l", "-c", cmd]))
64
67
  end
65
68
 
66
69
  # convenience functions for running on certain instance types
@@ -1,26 +1,18 @@
1
+ require 'engineyard-serverside/shell/helpers'
2
+
1
3
  module EY
2
4
  module Serverside
3
5
  def self.deprecation_warning(msg)
4
6
  $stderr.puts "DEPRECATION WARNING: #{msg}"
5
7
  end
6
- end
7
8
 
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
9
+ def self.const_missing(const)
10
+ if const == :LoggedOutput
11
+ EY::Serverside.deprecation_warning("EY::Serverside::LoggedOutput has been deprecated. Use EY::Serverside::Shell::Helpers instead.")
12
+ EY::Serverside::Shell::Helpers
13
+ else
14
+ super
15
+ end
14
16
  end
15
17
  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
26
18
  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 postgres].any? do |type|
27
+ any_jruby_adapter = %w[mysql postgresql].any? do |type|
28
28
  @contents.index(/^\s+jdbc-#{type}\s\([^\)]+\)$/) || @contents.index(/^\s+activerecord-jdbc#{type}-adapter\s\([^\)]+\)$/)
29
29
  end
30
30
 
@@ -9,7 +9,7 @@ module EY
9
9
  keep_existing_assets
10
10
  cmd = "cd #{c.release_path} && PATH=#{c.binstubs_path}:$PATH #{c.framework_envs} rake assets:precompile || true"
11
11
  if rails_version
12
- info "~> Precompiling assets for rails v#{rails_version}"
12
+ shell.status "Precompiling assets for rails v#{rails_version}"
13
13
  else
14
14
  warning "Precompiling assets even though Rails was not bundled."
15
15
  end
@@ -22,24 +22,24 @@ module EY
22
22
  return unless File.readable?(app_rb_path) # Not a Rails app in the first place.
23
23
 
24
24
  if File.directory?(File.join(c.release_path, 'app', 'assets'))
25
- info "~> app/assets/ found. Attempting Rails asset pre-compilation."
25
+ shell.status "app/assets/ found. Attempting Rails asset pre-compilation."
26
26
  else
27
27
  return false
28
28
  end
29
29
 
30
30
  if app_builds_own_assets?
31
- info "~> public/assets already exists, skipping pre-compilation."
31
+ shell.status "public/assets already exists, skipping pre-compilation."
32
32
  return
33
33
  end
34
34
  if app_disables_assets?(app_rb_path)
35
- info "~> application.rb has disabled asset compilation. Skipping."
35
+ shell.status "application.rb has disabled asset compilation. Skipping."
36
36
  return
37
37
  end
38
38
  # This check is very expensive, and has been deemed not worth the time.
39
39
  # Leaving this here in case someone comes up with a faster way.
40
40
  =begin
41
41
  unless app_has_asset_task?
42
- info "~> No 'assets:precompile' Rake task found. Skipping."
42
+ shell.status "No 'assets:precompile' Rake task found. Skipping."
43
43
  return
44
44
  end
45
45
  =end
@@ -1,11 +1,8 @@
1
1
  require 'open-uri'
2
- require 'engineyard-serverside/logged_output'
3
2
 
4
3
  module EY
5
4
  module Serverside
6
5
  class Server < Struct.new(:hostname, :roles, :name, :user)
7
- include LoggedOutput
8
-
9
6
  class DuplicateHostname < StandardError
10
7
  def initialize(hostname)
11
8
  super "There is already an EY::Serverside::Server with hostname '#{hostname}'"
@@ -75,20 +72,20 @@ module EY
75
72
 
76
73
  def sync_directory(directory)
77
74
  return if local?
78
- run "mkdir -p #{directory}"
79
- logged_system(%|rsync --delete -aq -e "#{ssh_command}" #{directory}/ #{user}@#{hostname}:#{directory}|)
75
+ yield remote_command("mkdir -p #{directory}")
76
+ yield Escap.shell_command(%w[rsync --delete -aq -e] + [ssh_command, "#{directory}/", "#{user}@#{hostname}:#{directory}"])
80
77
  end
81
78
 
82
79
  def run(command)
83
- if local?
84
- logged_system(command)
85
- else
86
- logged_system("#{ssh_command} #{user}@#{hostname} #{command}")
87
- end
80
+ yield local? ? command : remote_command(command)
81
+ end
82
+
83
+ def remote_command(command)
84
+ ssh_command + Escape.shell_command(["#{user}@#{hostname}", command])
88
85
  end
89
86
 
90
87
  def ssh_command
91
- "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no"
88
+ "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o PasswordAuthentication=no "
92
89
  end
93
90
 
94
91
  end
@@ -0,0 +1,101 @@
1
+ require 'logger'
2
+ require 'pathname'
3
+ require 'open4'
4
+ require 'engineyard-serverside/shell/formatter'
5
+
6
+ module EY
7
+ module Serverside
8
+ class Shell
9
+ class YieldIO
10
+ def initialize(&block)
11
+ @block = block
12
+ end
13
+ def <<(str)
14
+ @block.call str
15
+ end
16
+ end
17
+
18
+ attr_reader :logger
19
+
20
+ def initialize(options)
21
+ @start_time = options[:start_time]
22
+ @verbose = options[:verbose]
23
+
24
+
25
+ @stdout = options[:stdout] || $stdout
26
+ @stderr = options[:stderr] || $stderr
27
+
28
+ log_pathname = Pathname.new(options[:log_path])
29
+ log_pathname.unlink if log_pathname.exist? # start fresh
30
+ @logger = Logger.new(log_pathname.to_s)
31
+ @logger.level = Logger::DEBUG # Always log to the file at debug, formatter hides debug for non-verbose
32
+ @logger.formatter = EY::Serverside::Shell::Formatter.new(@stdout, @stderr, start_time, @verbose)
33
+ end
34
+
35
+ def start_time
36
+ @start_time ||= Time.now
37
+ end
38
+
39
+ # a nice info outputter that prepends spermy operators for some reason.
40
+ def status(msg)
41
+ info msg.gsub(/^/, '~> ')
42
+ end
43
+
44
+ def substatus(msg)
45
+ debug msg.gsub(/^/, ' ~ ')
46
+ end
47
+
48
+ # a debug outputter that displays a command being run
49
+ # Formatis like this:
50
+ # $ cmd blah do \
51
+ # > something more
52
+ # > end
53
+ def show_command(cmd)
54
+ debug cmd.gsub(/^/, ' > ').sub(/>/, '$')
55
+ end
56
+
57
+ def command_stdout(msg)
58
+ debug msg.gsub(/^/,' ')
59
+ end
60
+
61
+ def command_stderr(msg)
62
+ unknown msg.gsub(/^/,' ')
63
+ end
64
+
65
+ def fatal(msg) logger.fatal msg end
66
+ def error(msg) logger.error msg end
67
+ def warning(msg) logger.warn msg end
68
+ def info(msg) logger.info msg end
69
+ def debug(msg) logger.debug msg end
70
+ def unknown(msg) logger.unknown msg end
71
+
72
+ # Return an IO that outputs to stdout or not according to the verbosity settings
73
+ # debug is hidden in non-verbose mode
74
+ def out
75
+ YieldIO.new { |msg| command_stdout(msg) }
76
+ end
77
+
78
+ # Return an IO that outputs to stderr
79
+ # unknown always shows, but without a severity title
80
+ def err
81
+ YieldIO.new { |msg| command_stderr(msg) }
82
+ end
83
+
84
+ def logged_system(cmd)
85
+ show_command(cmd)
86
+ spawn_process(cmd, out, err)
87
+ end
88
+
89
+ protected
90
+
91
+ # spawn_process is a separate utility method so tests can override just the meat
92
+ # of the process spawning and not couple the tests too tightly with the implementation.
93
+ # we do this because Open4 LOVES to segfault in CI. FUN!
94
+ def spawn_process(cmd, cmd_stdout, cmd_stderr)
95
+ # :quiet means don't raise an error on nonzero exit status
96
+ result = Open4.spawn cmd, 0 => '', 1 => cmd_stdout, 2 => cmd_stderr, :quiet => true
97
+ result.exitstatus == 0
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,70 @@
1
+ module EY
2
+ module Serverside
3
+ class Shell
4
+ class Formatter
5
+ def initialize(stdout, stderr, start_time, verbose)
6
+ @stdout, @stderr = stdout, stderr
7
+ @start = start_time.to_i
8
+ @verbose = verbose
9
+ end
10
+
11
+ def call(severity, time, _, message)
12
+ msg = build_message(severity, timestamp(time), message)
13
+ put_to_io(severity, msg)
14
+ msg
15
+ end
16
+
17
+ def build_message(severity, stamp, message)
18
+ if %w[WARN ERROR FATAL].include?(severity)
19
+ prepend("#{stamp}!> ", "#{severity_name(severity)}#{message}")
20
+ elsif severity == "INFO"
21
+ prepend(stamp, message)
22
+ else
23
+ prepend(' ' * stamp.size, message)
24
+ end
25
+ end
26
+
27
+ def prepend(pre, str)
28
+ str.gsub(/^/, pre).sub(/\n?\z/m,"\n")
29
+ end
30
+
31
+ def put_to_io(severity, msg)
32
+ if severity == "DEBUG"
33
+ if @verbose
34
+ @stdout << msg
35
+ end
36
+ elsif severity == "INFO"
37
+ # Need to differentiate info messages more when we're running in verbose mode
38
+ @stdout << (@verbose ? "\n\e[1m#{msg}\e[0m" : msg)
39
+ @stdout.flush
40
+ else
41
+ @stderr << msg
42
+ @stderr.flush
43
+ end
44
+ end
45
+
46
+ def timestamp(datetime)
47
+ diff = datetime.to_i - @start
48
+ diff = 0 if diff < 0
49
+ div, mod = diff.divmod(60)
50
+ if div.zero?
51
+ "+ %02ds " % mod
52
+ else
53
+ "+%2dm %02ds " % [div,mod]
54
+ end
55
+ end
56
+
57
+ def severity_name(severity)
58
+ if %w[INFO DEBUG ANY].include?(severity)
59
+ ""
60
+ elsif severity =='WARN'
61
+ "WARNING: "
62
+ else
63
+ "#{severity}: "
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,29 @@
1
+ module EY
2
+ module Serverside
3
+ class Shell
4
+ # Compatibility with old LoggedOutput where the module was included into the class.
5
+ module Helpers
6
+ def verbose?
7
+ shell.verbose?
8
+ end
9
+
10
+ def warning(*a)
11
+ shell.warning(*a)
12
+ end
13
+
14
+ def info(*a)
15
+ shell.info(*a)
16
+ end
17
+
18
+ def debug(*a)
19
+ shell.debug(*a)
20
+ end
21
+
22
+ def logged_system(*a)
23
+ shell.logged_system(*a)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -1,5 +1,3 @@
1
- require 'engineyard-serverside/logged_output'
2
-
3
1
  module EY
4
2
  module Serverside
5
3
  module Strategies
@@ -28,7 +26,7 @@ module EY
28
26
  #
29
27
  # Rollback doesn't know about the repository location (nor
30
28
  # should it need to), but it would like to use #short_log_message.
31
- klass.new(
29
+ klass.new(shell,
32
30
  :repository_cache => c[:repository_cache],
33
31
  :app => c[:app],
34
32
  :repo => c[:repo],
@@ -37,11 +35,10 @@ module EY
37
35
  end
38
36
  end
39
37
 
40
- include LoggedOutput
41
-
42
- attr_reader :opts
38
+ attr_reader :shell, :opts
43
39
 
44
- def initialize(opts)
40
+ def initialize(shell, opts)
41
+ @shell = shell
45
42
  @opts = opts
46
43
  end
47
44
 
@@ -51,21 +48,21 @@ module EY
51
48
 
52
49
  def fetch
53
50
  if usable_repository?
54
- logged_system("#{git} fetch -q origin 2>&1")
51
+ shell.logged_system("#{git} fetch -q origin 2>&1")
55
52
  else
56
53
  FileUtils.rm_rf(opts[:repository_cache])
57
- logged_system("git clone -q #{opts[:repo]} #{opts[:repository_cache]} 2>&1")
54
+ shell.logged_system("git clone -q #{opts[:repo]} #{opts[:repository_cache]} 2>&1")
58
55
  end
59
56
  end
60
57
 
61
58
  def checkout
62
- info "~> Deploying revision #{short_log_message(to_checkout)}"
59
+ shell.status "Deploying revision #{short_log_message(to_checkout)}"
63
60
  in_git_work_tree do
64
- (logged_system("git checkout -q '#{to_checkout}'") ||
65
- logged_system("git reset -q --hard '#{to_checkout}'")) &&
66
- logged_system("git submodule sync") &&
67
- logged_system("git submodule update --init") &&
68
- logged_system("git clean -dfq")
61
+ (shell.logged_system("git checkout -q '#{to_checkout}'") ||
62
+ shell.logged_system("git reset -q --hard '#{to_checkout}'")) &&
63
+ shell.logged_system("git submodule sync") &&
64
+ shell.logged_system("git submodule update --init") &&
65
+ shell.logged_system("git clean -dfq")
69
66
  end
70
67
  end
71
68