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.
- 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
|
|