engineyard-serverside 2.1.4 → 2.2.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 +0 -1
- data/lib/engineyard-serverside/cli.rb +32 -13
- data/lib/engineyard-serverside/configuration.rb +1 -1
- data/lib/engineyard-serverside/dependency_manager/composer.rb +2 -2
- data/lib/engineyard-serverside/deploy.rb +3 -4
- data/lib/engineyard-serverside/maintenance.rb +1 -1
- data/lib/engineyard-serverside/rails_assets.rb +8 -3
- data/lib/engineyard-serverside/server.rb +12 -0
- data/lib/engineyard-serverside/servers.rb +32 -27
- data/lib/engineyard-serverside/shell.rb +2 -14
- data/lib/engineyard-serverside/shell/command_result.rb +3 -7
- data/lib/engineyard-serverside/spawner.rb +187 -0
- data/lib/engineyard-serverside/strategies/git.rb +1 -1
- data/lib/engineyard-serverside/task.rb +2 -2
- data/lib/engineyard-serverside/version.rb +1 -1
- data/spec/bundler_deploy_spec.rb +5 -3
- data/spec/deploy_hook_spec.rb +8 -10
- data/spec/git_strategy_spec.rb +1 -1
- data/spec/nodejs_deploy_spec.rb +11 -10
- data/spec/php_deploy_spec.rb +40 -48
- data/spec/server_spec.rb +5 -5
- data/spec/spec_helper.rb +122 -8
- data/spec/support/integration.rb +0 -1
- metadata +10 -24
- data/lib/engineyard-serverside/propagator.rb +0 -75
- 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/propagator_spec.rb +0 -95
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'pathname'
|
3
|
+
require 'engineyard-serverside/about'
|
3
4
|
require 'engineyard-serverside/deploy'
|
4
5
|
require 'engineyard-serverside/shell'
|
5
6
|
require 'engineyard-serverside/servers'
|
@@ -8,6 +9,7 @@ require 'engineyard-serverside/cli_helpers'
|
|
8
9
|
module EY
|
9
10
|
module Serverside
|
10
11
|
class CLI < Thor
|
12
|
+
|
11
13
|
extend CLIHelpers
|
12
14
|
|
13
15
|
method_option :migrate, :type => :string,
|
@@ -99,15 +101,12 @@ module EY
|
|
99
101
|
|
100
102
|
# We have to rsync the entire app dir, so we need all the permissions to be correct!
|
101
103
|
chown_command = "find #{app_dir} -not -user #{config.user} -or -not -group #{config.group} -exec chown #{config.user}:#{config.group} {} +"
|
102
|
-
shell.logged_system
|
103
|
-
|
104
|
-
servers.
|
105
|
-
|
106
|
-
|
107
|
-
#
|
108
|
-
# things up, and since we don't control deploy hooks, we must
|
109
|
-
# assume the worst.
|
110
|
-
shell.logged_system server.command_on_server('sh -l -c', "rm -rf #{current_app_dir}")
|
104
|
+
shell.logged_system("sudo sh -l -c '#{chown_command}'", servers.detect {|s| s.local?})
|
105
|
+
|
106
|
+
servers.run_for_each! do |server|
|
107
|
+
sync = server.sync_directory_command(app_dir)
|
108
|
+
clean = server.command_on_server('sh -l -c', "rm -rf #{current_app_dir}")
|
109
|
+
"(#{sync}) && (#{clean})"
|
111
110
|
end
|
112
111
|
|
113
112
|
# deploy local-ref to other instances into /data/$app/local-current
|
@@ -130,8 +129,8 @@ module EY
|
|
130
129
|
|
131
130
|
def init_and_propagate(*args)
|
132
131
|
init(*args) do |config, shell|
|
133
|
-
servers = load_servers(config)
|
134
|
-
|
132
|
+
servers = load_servers(config, shell)
|
133
|
+
propagate(servers, shell)
|
135
134
|
yield servers, config, shell
|
136
135
|
end
|
137
136
|
end
|
@@ -154,8 +153,28 @@ module EY
|
|
154
153
|
end
|
155
154
|
end
|
156
155
|
|
157
|
-
def
|
158
|
-
|
156
|
+
def propagate(servers, shell)
|
157
|
+
shell.status "Verifying and propagating #{About.name_with_version} to all servers."
|
158
|
+
|
159
|
+
gem_binary = File.join(Gem.default_bindir, 'gem')
|
160
|
+
remote_gem_file = File.join(Dir.tmpdir, About.gem_filename)
|
161
|
+
|
162
|
+
# the [,)] is to stop us from looking for e.g. 0.5.1, seeing
|
163
|
+
# 0.5.11, and mistakenly thinking 0.5.1 is there
|
164
|
+
check_command = %{#{gem_binary} list #{About.gem_name} | grep "#{About.gem_name}" | egrep -q "#{About.version.gsub(/\./, '\.')}[,)]"}
|
165
|
+
install_command = "#{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'"
|
166
|
+
|
167
|
+
servers.remote.run_for_each! do |server|
|
168
|
+
check = server.command_on_server('sh -l -c', check_command)
|
169
|
+
scp = server.scp_command(About.gem_file, remote_gem_file)
|
170
|
+
install = server.command_on_server('sudo sh -l -c', install_command)
|
171
|
+
|
172
|
+
"(#{check}) || ((#{scp}) && (#{install}))"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def load_servers(config, shell)
|
177
|
+
EY::Serverside::Servers.from_hashes(assemble_instance_hashes(config), shell)
|
159
178
|
end
|
160
179
|
|
161
180
|
def assemble_instance_hashes(config)
|
@@ -60,7 +60,7 @@ module EY
|
|
60
60
|
def_option :precompile_assets, 'detect'
|
61
61
|
def_option :precompile_assets_task, 'assets:precompile'
|
62
62
|
def_option :asset_strategy, 'shifting'
|
63
|
-
def_option :asset_dependencies, %w[app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb]
|
63
|
+
def_option :asset_dependencies, %w[app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb config/application.rb]
|
64
64
|
def_option :stack, nil
|
65
65
|
def_option :strategy, 'Git'
|
66
66
|
def_option :branch, 'master'
|
@@ -42,11 +42,11 @@ To fix this problem, commit your composer.lock to the repository and redeploy.
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def composer_install
|
45
|
-
|
45
|
+
run "composer install --no-interaction --working-dir #{paths.active_release}"
|
46
46
|
end
|
47
47
|
|
48
48
|
def composer_selfupdate
|
49
|
-
|
49
|
+
run "composer self-update"
|
50
50
|
end
|
51
51
|
|
52
52
|
def composer_available?
|
@@ -106,9 +106,8 @@ module EY
|
|
106
106
|
# task
|
107
107
|
def push_code
|
108
108
|
shell.status "Pushing code to all servers"
|
109
|
-
servers.remote.
|
110
|
-
|
111
|
-
shell.logged_system(cmd)
|
109
|
+
servers.remote.run_for_each do |server|
|
110
|
+
server.sync_directory_command(paths.repository_cache)
|
112
111
|
end
|
113
112
|
end
|
114
113
|
|
@@ -149,7 +148,7 @@ module EY
|
|
149
148
|
path = paths.ssh_wrapper
|
150
149
|
<<-SCRIPT
|
151
150
|
mkdir -p #{path.dirname}
|
152
|
-
[
|
151
|
+
[ -x #{path} ] || cat > #{path} <<'SSH'
|
153
152
|
#!/bin/sh
|
154
153
|
unset SSH_AUTH_SOCK
|
155
154
|
ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{paths.deploy_key} -o IdentitiesOnly=yes $*
|
@@ -86,10 +86,15 @@ module EY
|
|
86
86
|
run_precompile_assets_task
|
87
87
|
|
88
88
|
shell.warning <<-WARN
|
89
|
-
|
89
|
+
Assets were detected and precompiled for this application, but asset precompile
|
90
|
+
failures may be silently ignored in the future without updating config/ey.yml.
|
90
91
|
|
91
|
-
ACTION REQUIRED: Add
|
92
|
-
|
92
|
+
ACTION REQUIRED: Add this line to config/ey.yml to ensure assets are compiled
|
93
|
+
every deploy and deploys are halted if there is an asset compilation failure.
|
94
|
+
|
95
|
+
precompile_assets: true # precompile assets when assets are changed.
|
96
|
+
|
97
|
+
This warning will continue to show until you update and commit config/ey.yml.
|
93
98
|
WARN
|
94
99
|
rescue EY::Serverside::RemoteFailure => e
|
95
100
|
# If we are implicitly precompiling, we want to fail non-destructively
|
@@ -42,6 +42,18 @@ module EY
|
|
42
42
|
].join(' && ')
|
43
43
|
end
|
44
44
|
|
45
|
+
def scp_command(local_file, remote_file)
|
46
|
+
Escape.shell_command([
|
47
|
+
'scp',
|
48
|
+
'-i', "#{ENV['HOME']}/.ssh/internal",
|
49
|
+
"-o", "StrictHostKeyChecking=no",
|
50
|
+
"-o", "UserKnownHostsFile=#{self.class.known_hosts_file.path}",
|
51
|
+
"-o", "PasswordAuthentication=no",
|
52
|
+
local_file,
|
53
|
+
"#{authority}:#{remote_file}",
|
54
|
+
])
|
55
|
+
end
|
56
|
+
|
45
57
|
def command_on_server(prefix, cmd, &block)
|
46
58
|
command = block ? block.call(self, cmd.dup) : cmd
|
47
59
|
command = "#{prefix} #{Escape.shell_command([command])}"
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'engineyard-serverside/server'
|
2
2
|
require 'forwardable'
|
3
3
|
require 'set'
|
4
|
+
require 'engineyard-serverside/spawner'
|
4
5
|
|
5
6
|
module EY
|
6
7
|
module Serverside
|
@@ -16,24 +17,25 @@ module EY
|
|
16
17
|
extend Forwardable
|
17
18
|
include Enumerable
|
18
19
|
def_delegators :@servers, :each, :size, :empty?
|
19
|
-
def select(*a, &b) self.class.new @servers.select(*a,&b) end
|
20
|
-
def reject(*a, &b) self.class.new @servers.reject(*a,&b) end
|
20
|
+
def select(*a, &b) self.class.new @servers.select(*a,&b), @shell end
|
21
|
+
def reject(*a, &b) self.class.new @servers.reject(*a,&b), @shell end
|
21
22
|
def to_a() @servers end
|
22
23
|
def ==(other) other.respond_to?(:to_a) && other.to_a == to_a end
|
23
24
|
|
24
25
|
|
25
|
-
def self.from_hashes(server_hashes)
|
26
|
+
def self.from_hashes(server_hashes, shell)
|
26
27
|
servers = server_hashes.inject({}) do |memo, server_hash|
|
27
28
|
server = Server.from_hash(server_hash)
|
28
29
|
raise DuplicateHostname.new(server.hostname) if memo.key?(server.hostname)
|
29
30
|
memo[server.hostname] = server
|
30
31
|
memo
|
31
32
|
end
|
32
|
-
new(servers.values)
|
33
|
+
new(servers.values, shell)
|
33
34
|
end
|
34
35
|
|
35
|
-
def initialize(servers)
|
36
|
+
def initialize(servers, shell)
|
36
37
|
@servers = servers
|
38
|
+
@shell = shell
|
37
39
|
@cache = {}
|
38
40
|
end
|
39
41
|
|
@@ -62,40 +64,43 @@ module EY
|
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
67
|
+
def run_on_each(cmd, &block)
|
68
|
+
run_for_each do |server|
|
69
|
+
server.command_on_server('sh -l -c', cmd, &block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
65
73
|
# Run a command on this set of servers.
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
shell.logged_system(exec_cmd, server)
|
74
|
+
def run_on_each!(cmd, &block)
|
75
|
+
run_for_each! do |server|
|
76
|
+
server.command_on_server('sh -l -c', cmd, &block)
|
70
77
|
end
|
71
78
|
end
|
79
|
+
alias run run_on_each!
|
72
80
|
|
73
81
|
# Run a sudo command on this set of servers.
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
shell.logged_system(exec_cmd, server)
|
82
|
+
def sudo_on_each(cmd, &block)
|
83
|
+
run_for_each do |server|
|
84
|
+
server.command_on_server('sudo sh -l -c', cmd, &block)
|
78
85
|
end
|
79
86
|
end
|
80
87
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
# Run a sudo command on this set of servers.
|
89
|
+
def sudo_on_each!(cmd, &block)
|
90
|
+
run_for_each! do |server|
|
91
|
+
server.command_on_server('sudo sh -l -c', cmd, &block)
|
92
|
+
end
|
86
93
|
end
|
94
|
+
alias sudo sudo_on_each!
|
87
95
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
96
|
+
def run_for_each(&block)
|
97
|
+
spawner = Spawner.new
|
98
|
+
each { |server| spawner.add(block.call(server), @shell, server) }
|
99
|
+
spawner.run
|
91
100
|
end
|
92
101
|
|
93
|
-
|
94
|
-
|
95
|
-
# and ensures that all the command results were successful.
|
96
|
-
def run_on_each(shell, &block)
|
97
|
-
results = map_in_parallel(&block)
|
98
|
-
failures = results.reject {|result| result.success? }
|
102
|
+
def run_for_each!(&block)
|
103
|
+
failures = run_for_each(&block).reject {|result| result.success? }
|
99
104
|
|
100
105
|
if failures.any?
|
101
106
|
commands = failures.map { |f| f.command }.uniq
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'pathname'
|
3
|
-
require '
|
3
|
+
require 'open3'
|
4
4
|
require 'engineyard-serverside/shell/formatter'
|
5
5
|
require 'engineyard-serverside/shell/command_result'
|
6
6
|
require 'engineyard-serverside/shell/yieldio'
|
@@ -64,21 +64,9 @@ module EY
|
|
64
64
|
def command_err(msg) unknown msg.gsub(/^/,' ') end
|
65
65
|
|
66
66
|
def logged_system(cmd, server = nil)
|
67
|
-
|
68
|
-
output = ""
|
69
|
-
outio = YieldIO.new { |msg| output << msg; command_out(msg) }
|
70
|
-
errio = YieldIO.new { |msg| output << msg; command_err(msg) }
|
71
|
-
result = spawn_process(cmd, outio, errio)
|
72
|
-
CommandResult.new(cmd, result.exitstatus, output, server)
|
67
|
+
EY::Serverside::Spawner.run(cmd, self, server)
|
73
68
|
end
|
74
69
|
|
75
|
-
protected
|
76
|
-
|
77
|
-
# This is the meat of process spawning. It's nice to keep it separate even
|
78
|
-
# though it's simple because we've had to modify it frequently.
|
79
|
-
def spawn_process(cmd, outio, errio)
|
80
|
-
systemu cmd, 'stdout' => outio, 'stderr' => errio
|
81
|
-
end
|
82
70
|
end
|
83
71
|
end
|
84
72
|
end
|
@@ -1,17 +1,13 @@
|
|
1
1
|
module EY
|
2
2
|
module Serverside
|
3
3
|
class Shell
|
4
|
-
class CommandResult < Struct.new(:command, :
|
5
|
-
|
6
|
-
exitstatus.to_i == 0
|
7
|
-
end
|
4
|
+
class CommandResult < Struct.new(:command, :success, :output, :server)
|
5
|
+
alias success? success
|
8
6
|
|
9
7
|
def inspect
|
10
8
|
<<-EOM
|
11
|
-
$ #{command}
|
9
|
+
$ #{success? ? "(success)" : "(failed)"} #{command}
|
12
10
|
#{output}
|
13
|
-
|
14
|
-
($?: #{exitstatus})
|
15
11
|
EOM
|
16
12
|
end
|
17
13
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'engineyard-serverside/spawner'
|
3
|
+
|
4
|
+
module EY
|
5
|
+
module Serverside
|
6
|
+
class Spawner
|
7
|
+
def self.run(cmd, shell, server = nil)
|
8
|
+
s = new
|
9
|
+
s.add(cmd, shell, server)
|
10
|
+
s.run.first
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@poll_period = 0.5
|
15
|
+
@children = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(cmd, shell, server = nil)
|
19
|
+
@children << Child.new(cmd, shell, server)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
@child_by_fd = {}
|
24
|
+
@child_by_pid = {}
|
25
|
+
|
26
|
+
@children.each do |child|
|
27
|
+
pid, stdout_fd, stderr_fd = child.spawn
|
28
|
+
@child_by_pid[pid] = child
|
29
|
+
@child_by_fd[stdout_fd] = child
|
30
|
+
@child_by_fd[stderr_fd] = child
|
31
|
+
end
|
32
|
+
|
33
|
+
while @child_by_pid.any?
|
34
|
+
process
|
35
|
+
wait
|
36
|
+
end
|
37
|
+
|
38
|
+
@children.map { |child| child.result }
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def process
|
44
|
+
read_fds = @child_by_pid.values.map {|child| child.ios }.flatten.compact
|
45
|
+
ra, _, _ = IO.select(read_fds, [], [], @poll_period)
|
46
|
+
process_readable(ra) if ra
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_readable(ra)
|
50
|
+
ra.each do |fd|
|
51
|
+
child = @child_by_fd[fd]
|
52
|
+
if !child
|
53
|
+
raise "Select returned unknown fd: #{fd.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
if buf = fd.sysread(4096)
|
58
|
+
child.append_to_buffer(fd, buf)
|
59
|
+
else
|
60
|
+
raise "sysread() returned nil"
|
61
|
+
end
|
62
|
+
rescue SystemCallError, EOFError => e
|
63
|
+
@child_by_fd.delete(fd)
|
64
|
+
child.close(fd)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def wait
|
70
|
+
possible_children = true
|
71
|
+
just_reaped = []
|
72
|
+
while possible_children
|
73
|
+
begin
|
74
|
+
pid, status = Process::waitpid2(-1, Process::WNOHANG)
|
75
|
+
if pid.nil?
|
76
|
+
possible_children = false
|
77
|
+
else
|
78
|
+
child = @child_by_pid.delete(pid)
|
79
|
+
child.finished status
|
80
|
+
just_reaped << child
|
81
|
+
end
|
82
|
+
rescue Errno::ECHILD
|
83
|
+
possible_children = false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
# We may have waited on a child before reading all its output. Collect those missing bits. No blocking.
|
87
|
+
if just_reaped.any?
|
88
|
+
read_fds = just_reaped.map {|child| child.ios }.flatten.compact
|
89
|
+
ra, _, _ = IO.select(read_fds, nil, nil, 0)
|
90
|
+
process_readable(ra) if ra
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Child
|
95
|
+
attr_reader :stdout_fd, :stderr_fd
|
96
|
+
|
97
|
+
def initialize(command, shell, server = nil)
|
98
|
+
@command = command
|
99
|
+
@shell = shell
|
100
|
+
@server = server
|
101
|
+
@output = ""
|
102
|
+
end
|
103
|
+
|
104
|
+
def spawn
|
105
|
+
@shell.command_show @command
|
106
|
+
#stdin, @stdout_fd, @stderr_fd, @waitthr = Open3.popen3(@cmd)
|
107
|
+
#stdin.close
|
108
|
+
|
109
|
+
stdin_rd, stdin_wr = IO.pipe
|
110
|
+
@stdout_fd, stdout_wr = IO.pipe
|
111
|
+
@stderr_fd, stderr_wr = IO.pipe
|
112
|
+
|
113
|
+
@pid = fork do
|
114
|
+
stdin_wr.close
|
115
|
+
@stdout_fd.close
|
116
|
+
@stderr_fd.close
|
117
|
+
STDIN.reopen(stdin_rd)
|
118
|
+
STDOUT.reopen(stdout_wr)
|
119
|
+
STDERR.reopen(stderr_wr)
|
120
|
+
Kernel.exec(@command)
|
121
|
+
raise "Exec failed!"
|
122
|
+
end
|
123
|
+
|
124
|
+
stdin_rd.close
|
125
|
+
stdin_wr.close
|
126
|
+
stdout_wr.close
|
127
|
+
stderr_wr.close
|
128
|
+
|
129
|
+
[@pid, @stdout_fd, @stderr_fd]
|
130
|
+
end
|
131
|
+
|
132
|
+
def ios
|
133
|
+
[@stdout_fd, @stderr_fd].compact
|
134
|
+
end
|
135
|
+
|
136
|
+
def finished(status)
|
137
|
+
@status = status
|
138
|
+
end
|
139
|
+
|
140
|
+
def result
|
141
|
+
if @status
|
142
|
+
Result.new(@command, @status.success?, @output, @server)
|
143
|
+
else
|
144
|
+
raise "No result from unfinished child process"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def close(fd)
|
149
|
+
case fd
|
150
|
+
when @stdout_fd then @stdout_fd = nil
|
151
|
+
when @stderr_fd then @stderr_fd = nil
|
152
|
+
end
|
153
|
+
fd.close rescue true
|
154
|
+
end
|
155
|
+
|
156
|
+
def append_to_buffer(fd,data)
|
157
|
+
case fd
|
158
|
+
when @stdout_fd
|
159
|
+
@shell.command_out data
|
160
|
+
@output << data
|
161
|
+
when @stderr_fd
|
162
|
+
@shell.command_err data
|
163
|
+
@output << data
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class Result
|
169
|
+
attr_reader :command, :success, :output, :server
|
170
|
+
def initialize(command, success, output, server = nil)
|
171
|
+
@command = command
|
172
|
+
@success = success
|
173
|
+
@output = output
|
174
|
+
@server = server
|
175
|
+
end
|
176
|
+
|
177
|
+
alias success? success
|
178
|
+
def inspect
|
179
|
+
<<-EOM
|
180
|
+
$ #{success? ? "(success)" : "(failed)"} #{command}
|
181
|
+
#{output}
|
182
|
+
EOM
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|