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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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