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.
- data/lib/engineyard-serverside.rb +3 -1
- data/lib/engineyard-serverside/cli.rb +73 -38
- data/lib/engineyard-serverside/configuration.rb +38 -12
- data/lib/engineyard-serverside/deploy.rb +63 -51
- data/lib/engineyard-serverside/deploy_hook.rb +21 -18
- data/lib/engineyard-serverside/deprecation.rb +9 -17
- data/lib/engineyard-serverside/lockfile_parser.rb +1 -1
- data/lib/engineyard-serverside/rails_asset_support.rb +5 -5
- data/lib/engineyard-serverside/server.rb +8 -11
- data/lib/engineyard-serverside/shell.rb +101 -0
- data/lib/engineyard-serverside/shell/formatter.rb +70 -0
- data/lib/engineyard-serverside/shell/helpers.rb +29 -0
- data/lib/engineyard-serverside/strategies/git.rb +12 -15
- data/lib/engineyard-serverside/task.rb +28 -5
- data/lib/engineyard-serverside/version.rb +1 -1
- data/lib/vendor/open4/lib/open4.rb +432 -0
- data/spec/basic_deploy_spec.rb +9 -9
- data/spec/bundler_deploy_spec.rb +1 -1
- data/spec/custom_deploy_spec.rb +45 -4
- data/spec/deploy_hook_spec.rb +77 -78
- data/spec/deprecation_spec.rb +4 -26
- data/spec/git_strategy_spec.rb +6 -2
- data/spec/nodejs_deploy_spec.rb +2 -2
- data/spec/services_deploy_spec.rb +11 -10
- data/spec/shell_spec.rb +48 -0
- data/spec/spec_helper.rb +48 -25
- data/spec/sqlite3_deploy_spec.rb +1 -2
- data/spec/support/integration.rb +1 -13
- metadata +57 -97
- data/lib/engineyard-serverside/logged_output.rb +0 -91
- data/lib/vendor/systemu/LICENSE +0 -3
- data/lib/vendor/systemu/lib/systemu.rb +0 -363
- data/lib/vendor/systemu/systemu.gemspec +0 -45
- data/spec/fixtures/gitrepo/bar +0 -0
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
62
|
+
shell.logged_system(Escape.shell_command(["sh", "-l", "-c", cmd]))
|
52
63
|
end
|
53
64
|
|
54
65
|
def sudo(cmd)
|
55
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
79
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
|