engineyard-serverside 1.6.5 → 1.7.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/engineyard-serverside.rb +2 -0
- data/lib/engineyard-serverside/cli.rb +83 -48
- data/lib/engineyard-serverside/configuration.rb +85 -18
- data/lib/engineyard-serverside/deploy.rb +105 -91
- data/lib/engineyard-serverside/deploy_hook.rb +22 -20
- data/lib/engineyard-serverside/deprecation.rb +9 -17
- data/lib/engineyard-serverside/future.rb +10 -4
- data/lib/engineyard-serverside/futures/celluloid.rb +3 -13
- data/lib/engineyard-serverside/futures/dataflow.rb +8 -13
- data/lib/engineyard-serverside/lockfile_parser.rb +1 -1
- data/lib/engineyard-serverside/rails_asset_support.rb +26 -10
- data/lib/engineyard-serverside/server.rb +17 -12
- data/lib/engineyard-serverside/shell.rb +98 -0
- data/lib/engineyard-serverside/shell/formatter.rb +71 -0
- data/lib/engineyard-serverside/shell/helpers.rb +29 -0
- data/lib/engineyard-serverside/strategies/git.rb +33 -63
- data/lib/engineyard-serverside/task.rb +34 -13
- data/lib/engineyard-serverside/version.rb +1 -1
- data/spec/basic_deploy_spec.rb +15 -50
- data/spec/bundler_deploy_spec.rb +3 -44
- data/spec/configuration_spec.rb +72 -0
- data/spec/custom_deploy_spec.rb +3 -4
- data/spec/deploy_hook_spec.rb +210 -162
- data/spec/deprecation_spec.rb +4 -26
- data/spec/ey_yml_customized_deploy_spec.rb +68 -0
- data/spec/fixtures/repos/assets_disabled/Gemfile +6 -0
- data/spec/fixtures/repos/assets_disabled/Gemfile.lock +90 -0
- data/spec/fixtures/repos/assets_disabled/README +1 -0
- data/spec/fixtures/repos/assets_disabled/Rakefile +5 -0
- data/spec/fixtures/repos/assets_disabled/config/application.rb +5 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile +6 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/Gemfile.lock +90 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/README +1 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/Rakefile +5 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/config/application.rb +5 -0
- data/spec/fixtures/repos/assets_disabled_in_ey_yml/config/ey.yml +4 -0
- data/spec/fixtures/repos/assets_enabled/Gemfile +6 -0
- data/spec/fixtures/repos/assets_enabled/Gemfile.lock +90 -0
- data/spec/fixtures/repos/assets_enabled/README +1 -0
- data/spec/fixtures/repos/assets_enabled/Rakefile +5 -0
- data/spec/fixtures/repos/assets_enabled/config/application.rb +5 -0
- data/spec/fixtures/repos/assets_enabled_in_ey_yml/README +1 -0
- data/spec/fixtures/repos/assets_enabled_in_ey_yml/Rakefile +5 -0
- data/spec/fixtures/repos/assets_enabled_in_ey_yml/config/ey.yml +4 -0
- data/spec/fixtures/repos/assets_in_hook/Gemfile +6 -0
- data/spec/fixtures/repos/assets_in_hook/Gemfile.lock +90 -0
- data/spec/fixtures/repos/assets_in_hook/README +2 -0
- data/spec/fixtures/repos/assets_in_hook/Rakefile +5 -0
- data/spec/fixtures/repos/assets_in_hook/config/application.rb +5 -0
- data/spec/fixtures/repos/assets_in_hook/deploy/before_migrate.rb +1 -0
- data/spec/fixtures/repos/default/Gemfile +5 -0
- data/spec/fixtures/repos/default/Gemfile.lock +14 -0
- data/spec/fixtures/repos/default/README +5 -0
- data/spec/fixtures/repos/ey_yml/Gemfile +4 -0
- data/spec/fixtures/repos/ey_yml/Gemfile.lock +12 -0
- data/spec/fixtures/repos/ey_yml/README +1 -0
- data/spec/fixtures/repos/ey_yml/config/ey.yml +12 -0
- data/spec/fixtures/repos/ey_yml/deploy/before_migrate.rb +6 -0
- data/spec/fixtures/repos/ey_yml_alt/Gemfile +4 -0
- data/spec/fixtures/repos/ey_yml_alt/Gemfile.lock +12 -0
- data/spec/fixtures/repos/ey_yml_alt/README +1 -0
- data/spec/fixtures/repos/ey_yml_alt/deploy/before_migrate.rb +6 -0
- data/spec/fixtures/repos/ey_yml_alt/ey.yml +12 -0
- data/spec/fixtures/repos/hook_fails/README +1 -0
- data/spec/fixtures/repos/hook_fails/deploy/before_migrate.rb +1 -0
- data/spec/fixtures/repos/hooks/README +1 -0
- data/spec/fixtures/repos/hooks/deploy/after_bundle.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/after_compile_assets.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/after_migrate.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/after_restart.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/after_symlink.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/before_bundle.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/before_compile_assets.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/before_migrate.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/before_restart.rb +1 -0
- data/spec/fixtures/repos/hooks/deploy/before_symlink.rb +1 -0
- data/spec/fixtures/repos/no_ey_config/Gemfile +4 -0
- data/spec/fixtures/repos/no_ey_config/Gemfile.lock +12 -0
- data/spec/fixtures/repos/no_ey_config/README +1 -0
- data/spec/fixtures/repos/no_gemfile_lock/Gemfile +5 -0
- data/spec/fixtures/repos/no_gemfile_lock/README +1 -0
- data/spec/fixtures/repos/nodejs/README +1 -0
- data/spec/fixtures/repos/nodejs/package.json +7 -0
- data/spec/fixtures/repos/not_bundled/README +1 -0
- data/spec/fixtures/{gemfiles/1.0.21-rails-31-with-sqlite → repos/sqlite3/Gemfile} +0 -0
- data/spec/fixtures/{lockfiles/1.0.21-rails-31-with-sqlite → repos/sqlite3/Gemfile.lock} +0 -0
- data/spec/fixtures/repos/sqlite3/README +1 -0
- data/spec/git_strategy_spec.rb +11 -2
- data/spec/lockfile_parser_spec.rb +8 -3
- data/spec/nodejs_deploy_spec.rb +1 -26
- data/spec/rails31_deploy_spec.rb +23 -31
- data/spec/services_deploy_spec.rb +41 -100
- data/spec/shell_spec.rb +50 -0
- data/spec/spec_helper.rb +80 -66
- data/spec/sqlite3_deploy_spec.rb +10 -16
- data/spec/support/integration.rb +45 -139
- metadata +233 -78
- data/lib/engineyard-serverside/logged_output.rb +0 -91
- data/spec/logged_output_spec.rb +0 -55
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'pathname'
|
3
|
+
require 'engineyard-serverside/deploy'
|
4
|
+
require 'engineyard-serverside/shell'
|
5
|
+
require 'engineyard-serverside/server'
|
3
6
|
|
4
7
|
module EY
|
5
8
|
module Serverside
|
@@ -22,6 +25,14 @@ module EY
|
|
22
25
|
:desc => "Application to deploy",
|
23
26
|
:aliases => ["-a"]
|
24
27
|
|
28
|
+
method_option :environment_name,:type => :string,
|
29
|
+
:required => true,
|
30
|
+
:desc => "Environment name"
|
31
|
+
|
32
|
+
method_option :account_name, :type => :string,
|
33
|
+
:required => true,
|
34
|
+
:desc => "Account name"
|
35
|
+
|
25
36
|
method_option :framework_env, :type => :string,
|
26
37
|
:desc => "Ruby web framework environment",
|
27
38
|
:aliases => ["-e"]
|
@@ -44,21 +55,13 @@ module EY
|
|
44
55
|
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
45
56
|
|
46
57
|
method_option :verbose, :type => :boolean,
|
47
|
-
:default => false,
|
48
58
|
:desc => "Verbose output",
|
49
59
|
:aliases => ["-v"]
|
50
60
|
|
51
61
|
desc "deploy", "Deploy code from /data/<app>"
|
52
62
|
def deploy(default_task=:deploy)
|
53
|
-
config =
|
54
|
-
|
55
|
-
|
56
|
-
EY::Serverside::LoggedOutput.verbose = options[:verbose]
|
57
|
-
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-deploy.log")
|
58
|
-
|
59
|
-
invoke :propagate
|
60
|
-
|
61
|
-
EY::Serverside::Deploy.new(config).send(default_task)
|
63
|
+
config, shell = init_and_propagate(options, 'deploy')
|
64
|
+
EY::Serverside::Deploy.new(config, shell).send(default_task)
|
62
65
|
end
|
63
66
|
|
64
67
|
method_option :app, :type => :string,
|
@@ -66,6 +69,14 @@ module EY
|
|
66
69
|
:desc => "Which application's hooks to run",
|
67
70
|
:aliases => ["-a"]
|
68
71
|
|
72
|
+
method_option :environment_name, :type => :string,
|
73
|
+
:required => true,
|
74
|
+
:desc => "Environment name"
|
75
|
+
|
76
|
+
method_option :account_name, :type => :string,
|
77
|
+
:required => true,
|
78
|
+
:desc => "Account name"
|
79
|
+
|
69
80
|
method_option :release_path, :type => :string,
|
70
81
|
:desc => "Value for #release_path in hooks (mostly for internal coordination)",
|
71
82
|
:aliases => ["-r"]
|
@@ -84,9 +95,14 @@ module EY
|
|
84
95
|
method_option :current_name, :type => :string,
|
85
96
|
:desc => "Value for #current_name in hooks"
|
86
97
|
|
98
|
+
method_option :verbose, :type => :boolean,
|
99
|
+
:desc => "Verbose output",
|
100
|
+
:aliases => ["-v"]
|
101
|
+
|
87
102
|
desc "hook [NAME]", "Run a particular deploy hook"
|
88
103
|
def hook(hook_name)
|
89
|
-
|
104
|
+
config, shell = init(options, "hook-#{hook_name}")
|
105
|
+
EY::Serverside::DeployHook.new(config, shell).run(hook_name)
|
90
106
|
end
|
91
107
|
|
92
108
|
|
@@ -95,6 +111,14 @@ module EY
|
|
95
111
|
:desc => "Application to deploy",
|
96
112
|
:aliases => ["-a"]
|
97
113
|
|
114
|
+
method_option :environment_name,:type => :string,
|
115
|
+
:required => true,
|
116
|
+
:desc => "Environment name"
|
117
|
+
|
118
|
+
method_option :account_name, :type => :string,
|
119
|
+
:required => true,
|
120
|
+
:desc => "Account name"
|
121
|
+
|
98
122
|
method_option :framework_env, :type => :string,
|
99
123
|
:required => true,
|
100
124
|
:desc => "Ruby web framework environment",
|
@@ -115,13 +139,10 @@ module EY
|
|
115
139
|
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
116
140
|
|
117
141
|
method_option :verbose, :type => :boolean,
|
118
|
-
:default => false,
|
119
142
|
:desc => "Verbose output",
|
120
143
|
:aliases => ["-v"]
|
121
144
|
desc "integrate", "Integrate other instances into this cluster"
|
122
145
|
def integrate
|
123
|
-
EY::Serverside::LoggedOutput.verbose = options[:verbose]
|
124
|
-
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-integrate.log")
|
125
146
|
|
126
147
|
app_dir = Pathname.new "/data/#{options[:app]}"
|
127
148
|
current_app_dir = app_dir + "current"
|
@@ -133,26 +154,19 @@ module EY
|
|
133
154
|
# we have to deploy the same SHA there as here
|
134
155
|
integrate_options[:branch] = (current_app_dir + 'REVISION').read.strip
|
135
156
|
|
136
|
-
config =
|
137
|
-
|
138
|
-
# We have to rsync the entire app dir, so we need all the permissions to be correct!
|
139
|
-
system "sudo sh -l -c 'find #{app_dir} -not -user #{config.user} -or -not -group #{config.group} -exec chown #{config.user}:#{config.group} {} +'"
|
140
|
-
|
141
|
-
load_servers(config)
|
142
|
-
|
143
|
-
invoke :propagate
|
157
|
+
config, shell = init_and_propagate(integrate_options, 'integrate')
|
144
158
|
|
145
159
|
EY::Serverside::Server.all.each do |server|
|
146
|
-
server.
|
160
|
+
shell.logged_system server.sync_directory_command(app_dir)
|
147
161
|
# we're just about to recreate this, so it has to be gone
|
148
162
|
# first. otherwise, non-idempotent deploy hooks could screw
|
149
163
|
# things up, and since we don't control deploy hooks, we must
|
150
164
|
# assume the worst.
|
151
|
-
server
|
165
|
+
run_on_server(server,"rm -rf #{current_app_dir}")
|
152
166
|
end
|
153
167
|
|
154
168
|
# deploy local-ref to other instances into /data/$app/local-current
|
155
|
-
EY::Serverside::Deploy.new(config).cached_deploy
|
169
|
+
EY::Serverside::Deploy.new(config, shell).cached_deploy
|
156
170
|
end
|
157
171
|
|
158
172
|
method_option :app, :type => :string,
|
@@ -160,6 +174,14 @@ module EY
|
|
160
174
|
:desc => "Application to deploy",
|
161
175
|
:aliases => ["-a"]
|
162
176
|
|
177
|
+
method_option :environment_name,:type => :string,
|
178
|
+
:required => true,
|
179
|
+
:desc => "Environment name"
|
180
|
+
|
181
|
+
method_option :account_name, :type => :string,
|
182
|
+
:required => true,
|
183
|
+
:desc => "Account name"
|
184
|
+
|
163
185
|
method_option :stack, :type => :string,
|
164
186
|
:desc => "Web stack (so we can restart it correctly)"
|
165
187
|
|
@@ -175,20 +197,13 @@ module EY
|
|
175
197
|
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
176
198
|
|
177
199
|
method_option :verbose, :type => :boolean,
|
178
|
-
:default => false,
|
179
200
|
:desc => "Verbose output",
|
180
201
|
:aliases => ["-v"]
|
181
202
|
desc "restart", "Restart app servers, conditionally enabling maintenance page"
|
182
203
|
def restart
|
183
|
-
|
184
|
-
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-restart.log")
|
204
|
+
config, shell = init_and_propagate(options, 'restart')
|
185
205
|
|
186
|
-
|
187
|
-
load_servers(config)
|
188
|
-
|
189
|
-
invoke :propagate
|
190
|
-
|
191
|
-
EY::Serverside::Deploy.new(config).restart_with_maintenance_page
|
206
|
+
EY::Serverside::Deploy.new(config, shell).restart_with_maintenance_page
|
192
207
|
end
|
193
208
|
|
194
209
|
desc "install_bundler [VERSION]", "Make sure VERSION of bundler is installed (in system ruby)"
|
@@ -206,8 +221,10 @@ module EY
|
|
206
221
|
end
|
207
222
|
end
|
208
223
|
|
209
|
-
|
210
|
-
|
224
|
+
private
|
225
|
+
|
226
|
+
# Put the same engineyard-serverside on all the servers (Used to be public but is unused as an actual CLI command now)
|
227
|
+
def propagate(shell)
|
211
228
|
config = EY::Serverside::Deploy::Configuration.new
|
212
229
|
gem_filename = "engineyard-serverside-#{EY::Serverside::VERSION}.gem"
|
213
230
|
local_gem_file = File.join(Gem.dir, 'cache', gem_filename)
|
@@ -216,29 +233,47 @@ module EY
|
|
216
233
|
|
217
234
|
servers = EY::Serverside::Server.all.find_all { |server| !server.local? }
|
218
235
|
|
219
|
-
|
236
|
+
commands = servers.each do |server|
|
220
237
|
egrep_escaped_version = EY::Serverside::VERSION.gsub(/\./, '\.')
|
221
238
|
# the [,)] is to stop us from looking for e.g. 0.5.1, seeing
|
222
239
|
# 0.5.11, and mistakenly thinking 0.5.1 is there
|
223
240
|
has_gem_cmd = "#{gem_binary} list engineyard-serverside | grep \"engineyard-serverside\" | egrep -q '#{egrep_escaped_version}[,)]'"
|
224
241
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
242
|
+
proc do
|
243
|
+
if !run_on_server(server,has_gem_cmd).success? # doesn't have this exact version
|
244
|
+
shell.status "Installing engineyard-serverside on #{server.hostname}"
|
245
|
+
|
246
|
+
shell.logged_system(Escape.shell_command([
|
247
|
+
'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
|
248
|
+
"-o", "StrictHostKeyChecking=no",
|
249
|
+
local_gem_file,
|
250
|
+
"#{config.user}@#{server.hostname}:#{remote_gem_file}",
|
251
|
+
]))
|
252
|
+
run_on_server(server,"sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
|
253
|
+
end
|
235
254
|
end
|
236
255
|
end
|
237
256
|
|
257
|
+
futures = EY::Serverside::Future.call(commands)
|
238
258
|
EY::Serverside::Future.success?(futures)
|
239
259
|
end
|
240
260
|
|
241
|
-
|
261
|
+
def init_and_propagate(*args)
|
262
|
+
config, shell = init(*args)
|
263
|
+
load_servers(config)
|
264
|
+
propagate(shell)
|
265
|
+
[config, shell]
|
266
|
+
end
|
267
|
+
|
268
|
+
def init(options, action)
|
269
|
+
config = EY::Serverside::Deploy::Configuration.new(options)
|
270
|
+
shell = EY::Serverside::Shell.new(:verbose => config.verbose, :log_path => File.join(ENV['HOME'], "#{config.app}-#{action}.log"))
|
271
|
+
[config, shell]
|
272
|
+
end
|
273
|
+
|
274
|
+
def run_on_server(server, command)
|
275
|
+
shell.logged_system(server.command_on_server('sh -l -c', command))
|
276
|
+
end
|
242
277
|
|
243
278
|
def load_servers(config)
|
244
279
|
EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'thor'
|
3
|
+
require 'pp'
|
3
4
|
|
4
5
|
module EY
|
5
6
|
module Serverside
|
@@ -10,41 +11,54 @@ module EY
|
|
10
11
|
"bundle_without" => "test development",
|
11
12
|
})
|
12
13
|
|
13
|
-
attr_reader :configuration
|
14
|
-
alias :c :configuration
|
15
|
-
|
16
14
|
attr_writer :release_path
|
17
15
|
|
18
16
|
def initialize(options={})
|
19
|
-
opts = options.
|
20
|
-
@release_path = opts[
|
17
|
+
opts = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
18
|
+
@release_path = opts['release_path']
|
21
19
|
config = JSON.parse(opts.delete("config") || "{}")
|
22
|
-
@
|
20
|
+
@configs = [config, opts] # low to high priority
|
21
|
+
end
|
22
|
+
|
23
|
+
def configuration
|
24
|
+
@configuration ||= @configs.inject(DEFAULT_CONFIG) {|low,high| low.merge(high)}
|
23
25
|
end
|
26
|
+
alias :c :configuration # FIXME: awful, but someone is probably using it :(
|
24
27
|
|
25
28
|
# Delegate to the configuration objects
|
26
29
|
def method_missing(meth, *args, &blk)
|
27
|
-
|
30
|
+
configuration.key?(meth.to_s) ? configuration[meth.to_s] : super
|
28
31
|
end
|
29
32
|
|
30
33
|
def respond_to?(meth, include_private=false)
|
31
|
-
|
34
|
+
configuration.key?(meth.to_s) ? true : super
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_ey_yml_data(data, shell)
|
38
|
+
environments = data['environments']
|
39
|
+
if environments && (env_data = environments[environment_name])
|
40
|
+
shell.substatus "ey.yml configuration loaded for environment #{environment_name.inspect}."
|
41
|
+
shell.debug "#{environment_name}: #{env_data.pretty_inspect}"
|
42
|
+
@configuration = nil # reset cached configuration hash
|
43
|
+
@configs.unshift(env_data) # insert just above default configuration
|
44
|
+
true
|
45
|
+
else
|
46
|
+
shell.info "No matching ey.yml configuration found for environment #{environment_name.inspect}."
|
47
|
+
shell.debug "ey.yml:\n#{data.pretty_inspect}"
|
48
|
+
false
|
49
|
+
end
|
32
50
|
end
|
33
51
|
|
34
52
|
def [](key)
|
35
53
|
if respond_to?(key.to_sym)
|
36
54
|
send(key.to_sym)
|
37
55
|
else
|
38
|
-
|
56
|
+
configuration[key]
|
39
57
|
end
|
40
58
|
end
|
41
59
|
|
42
60
|
def has_key?(key)
|
43
|
-
|
44
|
-
true
|
45
|
-
else
|
46
|
-
c.has_key?(key)
|
47
|
-
end
|
61
|
+
respond_to?(key.to_sym) || configuration.has_key?(key)
|
48
62
|
end
|
49
63
|
|
50
64
|
def to_json
|
@@ -55,9 +69,30 @@ module EY
|
|
55
69
|
EY::Serverside.node
|
56
70
|
end
|
57
71
|
|
72
|
+
def verbose
|
73
|
+
configuration['verbose']
|
74
|
+
end
|
75
|
+
|
58
76
|
def app
|
59
77
|
configuration['app'].to_s
|
60
78
|
end
|
79
|
+
alias app_name app
|
80
|
+
|
81
|
+
def environment_name
|
82
|
+
configuration['environment_name'].to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def account_name
|
86
|
+
configuration['account_name'].to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
def ssh_identity_file
|
90
|
+
"~/.ssh/#{c.app}-deploy-key"
|
91
|
+
end
|
92
|
+
|
93
|
+
def strategy_class
|
94
|
+
EY::Serverside::Strategies.const_get(strategy)
|
95
|
+
end
|
61
96
|
|
62
97
|
def revision
|
63
98
|
IO.read(File.join(latest_release, 'REVISION'))
|
@@ -95,6 +130,10 @@ module EY
|
|
95
130
|
node['instance_role']
|
96
131
|
end
|
97
132
|
|
133
|
+
def current_roles
|
134
|
+
configuration['current_roles'] || []
|
135
|
+
end
|
136
|
+
|
98
137
|
def current_role
|
99
138
|
current_roles.first
|
100
139
|
end
|
@@ -116,10 +155,6 @@ module EY
|
|
116
155
|
all_releases[index-1]
|
117
156
|
end
|
118
157
|
|
119
|
-
def oldest_release
|
120
|
-
all_releases.first
|
121
|
-
end
|
122
|
-
|
123
158
|
def all_releases
|
124
159
|
Dir.glob("#{release_dir}/*").sort
|
125
160
|
end
|
@@ -184,10 +219,42 @@ module EY
|
|
184
219
|
@release_path ||= File.join(release_dir, Time.now.utc.strftime("%Y%m%d%H%M%S"))
|
185
220
|
end
|
186
221
|
|
222
|
+
def precompile_assets?
|
223
|
+
configuration['precompile_assets'] == true
|
224
|
+
end
|
225
|
+
|
226
|
+
def skip_precompile_assets?
|
227
|
+
configuration['precompile_assets'] == false
|
228
|
+
end
|
229
|
+
|
230
|
+
def required_downtime_stack?
|
231
|
+
%w[ nginx_mongrel glassfish ].include? stack
|
232
|
+
end
|
233
|
+
|
234
|
+
def enable_maintenance_page_on_restart?
|
235
|
+
configuration.fetch('maintenance_on_restart', required_downtime_stack?)
|
236
|
+
end
|
237
|
+
|
238
|
+
def enable_maintenance_page_on_migrate?
|
239
|
+
configuration.fetch('maintenance_on_migrate', true)
|
240
|
+
end
|
241
|
+
|
242
|
+
def enable_maintenance_page?
|
243
|
+
enable_maintenance_page_on_restart? || (migrate? && enable_maintenance_page_on_migrate?)
|
244
|
+
end
|
245
|
+
|
246
|
+
def maintenance_page_enabled_path
|
247
|
+
File.join(shared_path, "system", "maintenance.html")
|
248
|
+
end
|
249
|
+
|
187
250
|
def exclusions
|
188
251
|
copy_exclude.map { |e| %|--exclude="#{e}"| }.join(' ')
|
189
252
|
end
|
190
253
|
|
254
|
+
def ignore_database_adapter_warning?
|
255
|
+
configuration.fetch('ignore_database_adapter_warning', false)
|
256
|
+
end
|
257
|
+
|
191
258
|
end
|
192
259
|
end
|
193
260
|
end
|
@@ -7,22 +7,22 @@ require 'engineyard-serverside/rails_asset_support'
|
|
7
7
|
module EY
|
8
8
|
module Serverside
|
9
9
|
class DeployBase < Task
|
10
|
-
include LoggedOutput
|
11
10
|
include ::EY::Serverside::RailsAssetSupport
|
12
11
|
|
13
12
|
# default task
|
14
13
|
def deploy
|
15
|
-
|
14
|
+
shell.status "Starting deploy at #{shell.start_time.asctime}"
|
16
15
|
update_repository_cache
|
17
16
|
cached_deploy
|
18
17
|
end
|
19
18
|
|
20
19
|
def cached_deploy
|
21
|
-
|
20
|
+
shell.status "Deploying app from cached copy at #{Time.now.asctime}"
|
22
21
|
require_custom_tasks
|
22
|
+
load_ey_yml
|
23
23
|
push_code
|
24
24
|
|
25
|
-
|
25
|
+
shell.status "Starting full deploy"
|
26
26
|
copy_repository_cache
|
27
27
|
check_repository
|
28
28
|
|
@@ -47,13 +47,25 @@ module EY
|
|
47
47
|
disable_maintenance_page
|
48
48
|
|
49
49
|
cleanup_old_releases
|
50
|
-
|
50
|
+
shell.status "Finished deploy at #{Time.now.asctime}"
|
51
51
|
rescue Exception
|
52
|
-
|
52
|
+
shell.status "Finished failing to deploy at #{Time.now.asctime}"
|
53
53
|
puts_deploy_failure
|
54
54
|
raise
|
55
55
|
end
|
56
56
|
|
57
|
+
def update_repository_cache
|
58
|
+
strategy.update_repository_cache
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_revision_file_command
|
62
|
+
strategy.create_revision_file_command(config.release_path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def short_log_message(revision)
|
66
|
+
strategy.short_log_message(revision)
|
67
|
+
end
|
68
|
+
|
57
69
|
def parse_configured_services
|
58
70
|
result = YAML.load_file "#{c.shared_path}/config/ey_services_config_deploy.yml"
|
59
71
|
return {} unless result.is_a?(Hash)
|
@@ -66,18 +78,18 @@ module EY
|
|
66
78
|
if gemfile? && lockfile
|
67
79
|
configured_services = parse_configured_services
|
68
80
|
if !configured_services.empty? && !lockfile.has_ey_config?
|
69
|
-
warning "Gemfile.lock does not contain ey_config. Add it to get EY::Config access to: #{configured_services.keys.join(', ')}."
|
81
|
+
shell.warning "Gemfile.lock does not contain ey_config. Add it to get EY::Config access to: #{configured_services.keys.join(', ')}."
|
70
82
|
end
|
71
83
|
end
|
72
84
|
end
|
73
85
|
|
74
86
|
def check_repository
|
75
87
|
if gemfile?
|
76
|
-
|
88
|
+
shell.status "Gemfile found."
|
77
89
|
if lockfile
|
78
|
-
|
79
|
-
|
80
|
-
warning <<-WARN
|
90
|
+
shell.status "Gemfile.lock found."
|
91
|
+
if !config.ignore_database_adapter_warning? && !lockfile.any_database_adapter?
|
92
|
+
shell.warning <<-WARN
|
81
93
|
Gemfile.lock does not contain a recognized database adapter.
|
82
94
|
A database-adapter gem such as mysql2, mysql, or do_mysql was expected.
|
83
95
|
This can prevent applications that use MySQL or PostreSQL from booting.
|
@@ -87,7 +99,7 @@ Applications that don't use MySQL or PostgreSQL can safely ignore this warning.
|
|
87
99
|
WARN
|
88
100
|
end
|
89
101
|
else
|
90
|
-
warning <<-WARN
|
102
|
+
shell.warning <<-WARN
|
91
103
|
Gemfile.lock is missing!
|
92
104
|
You can get different versions of gems in production than what you tested with.
|
93
105
|
You can get different versions of gems on every deployment even if your Gemfile hasn't changed.
|
@@ -97,15 +109,13 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
97
109
|
WARN
|
98
110
|
end
|
99
111
|
else
|
100
|
-
|
112
|
+
shell.status "No Gemfile. Deploying without bundler support."
|
101
113
|
end
|
102
114
|
end
|
103
115
|
|
104
116
|
def restart_with_maintenance_page
|
105
117
|
require_custom_tasks
|
106
|
-
|
107
|
-
restart
|
108
|
-
disable_maintenance_page
|
118
|
+
with_maintenance_page { restart }
|
109
119
|
end
|
110
120
|
|
111
121
|
def enable_maintenance_page
|
@@ -119,10 +129,7 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
119
129
|
end
|
120
130
|
|
121
131
|
# this one is guaranteed to exist
|
122
|
-
maintenance_page_candidates <<
|
123
|
-
"default_maintenance_page.html",
|
124
|
-
File.dirname(__FILE__)
|
125
|
-
)
|
132
|
+
maintenance_page_candidates << File.expand_path("default_maintenance_page.html", File.dirname(__FILE__))
|
126
133
|
|
127
134
|
# put in the maintenance page
|
128
135
|
maintenance_file = maintenance_page_candidates.detect do |file|
|
@@ -131,27 +138,21 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
131
138
|
|
132
139
|
@maintenance_up = true
|
133
140
|
roles :app_master, :app, :solo do
|
134
|
-
|
135
|
-
|
136
|
-
run Escape.shell_command(['mkdir', '-p', maint_page_dir])
|
137
|
-
run Escape.shell_command(['cp', maintenance_file, visible_maint_page])
|
141
|
+
run Escape.shell_command(['mkdir', '-p', File.dirname(c.maintenance_page_enabled_path)])
|
142
|
+
run Escape.shell_command(['cp', maintenance_file, c.maintenance_page_enabled_path])
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
141
146
|
def conditionally_enable_maintenance_page
|
142
|
-
if c.
|
147
|
+
if c.enable_maintenance_page?
|
143
148
|
enable_maintenance_page
|
144
149
|
end
|
145
150
|
end
|
146
151
|
|
147
|
-
def required_downtime_stack?
|
148
|
-
%w[ nginx_mongrel glassfish ].include? c.stack
|
149
|
-
end
|
150
|
-
|
151
152
|
def disable_maintenance_page
|
152
153
|
@maintenance_up = false
|
153
154
|
roles :app_master, :app, :solo do
|
154
|
-
run "rm -f #{
|
155
|
+
run "rm -f #{c.maintenance_page_enabled_path}"
|
155
156
|
end
|
156
157
|
end
|
157
158
|
|
@@ -163,17 +164,19 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
163
164
|
|
164
165
|
# task
|
165
166
|
def push_code
|
166
|
-
|
167
|
-
|
168
|
-
server.
|
167
|
+
shell.status "Pushing code to all servers"
|
168
|
+
commands = EY::Serverside::Server.all.reject { |server| server.local? }.map do |server|
|
169
|
+
cmd = server.sync_directory_command(config.repository_cache)
|
170
|
+
proc { shell.logged_system(cmd) }
|
169
171
|
end
|
172
|
+
futures = EY::Serverside::Future.call(commands)
|
170
173
|
EY::Serverside::Future.success?(futures)
|
171
174
|
end
|
172
175
|
|
173
176
|
# task
|
174
177
|
def restart
|
175
178
|
@restart_failed = true
|
176
|
-
|
179
|
+
shell.status "Restarting app servers"
|
177
180
|
roles :app_master, :app, :solo do
|
178
181
|
run(restart_command)
|
179
182
|
end
|
@@ -189,14 +192,14 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
189
192
|
%Q[export GIT_SSH="#{ssh_executable}" && export LANG="en_US.UTF-8" && unset RUBYOPT BUNDLE_PATH BUNDLE_FROZEN BUNDLE_WITHOUT BUNDLE_BIN BUNDLE_GEMFILE]
|
190
193
|
end
|
191
194
|
|
192
|
-
#
|
193
|
-
# create it on all the servers that will need it.
|
194
|
-
# TODO - This logic likely fails when people change deploy keys.
|
195
|
+
# create ssh wrapper on all servers
|
195
196
|
def ssh_executable
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
197
|
+
@ssh_executable ||= begin
|
198
|
+
roles :app_master, :app, :solo, :util do
|
199
|
+
run(generate_ssh_wrapper)
|
200
|
+
end
|
201
|
+
ssh_wrapper_path
|
202
|
+
end
|
200
203
|
end
|
201
204
|
|
202
205
|
# We specify 'IdentitiesOnly' to avoid failures on systems with > 5 private keys available.
|
@@ -206,15 +209,15 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
|
|
206
209
|
# (Thanks Jim L.)
|
207
210
|
def generate_ssh_wrapper
|
208
211
|
path = ssh_wrapper_path
|
209
|
-
|
210
|
-
|
212
|
+
<<-SCRIPT
|
213
|
+
mkdir -p #{File.dirname(path)}
|
211
214
|
[[ -x #{path} ]] || cat > #{path} <<'SSH'
|
212
215
|
#!/bin/sh
|
213
216
|
unset SSH_AUTH_SOCK
|
214
|
-
ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{
|
217
|
+
ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{c.ssh_identity_file} -o IdentitiesOnly=yes $*
|
215
218
|
SSH
|
216
219
|
chmod 0700 #{path}
|
217
|
-
|
220
|
+
SCRIPT
|
218
221
|
end
|
219
222
|
|
220
223
|
def ssh_wrapper_path
|
@@ -242,7 +245,7 @@ WRAP
|
|
242
245
|
def clean_release_directory(dir, count = 3)
|
243
246
|
@cleanup_failed = true
|
244
247
|
ordinal = count.succ.to_s
|
245
|
-
|
248
|
+
shell.status "Cleaning release directory: #{dir}"
|
246
249
|
sudo "ls -r #{dir} | tail -n +#{ordinal} | xargs -I@ rm -rf #{dir}/@"
|
247
250
|
@cleanup_failed = false
|
248
251
|
end
|
@@ -254,15 +257,15 @@ WRAP
|
|
254
257
|
c.release_path = c.previous_release(rolled_back_release)
|
255
258
|
|
256
259
|
revision = File.read(File.join(c.release_path, 'REVISION')).strip
|
257
|
-
|
260
|
+
shell.status "Rolling back to previous release: #{short_log_message(revision)}"
|
258
261
|
|
259
262
|
run_with_callbacks(:symlink)
|
260
263
|
sudo "rm -rf #{rolled_back_release}"
|
261
264
|
bundle
|
262
|
-
|
265
|
+
shell.status "Restarting with previous release."
|
263
266
|
with_maintenance_page { run_with_callbacks(:restart) }
|
264
267
|
else
|
265
|
-
|
268
|
+
shell.status "Already at oldest release, nothing to roll back to."
|
266
269
|
exit(1)
|
267
270
|
end
|
268
271
|
end
|
@@ -273,17 +276,17 @@ WRAP
|
|
273
276
|
@migrations_reached = true
|
274
277
|
roles :app_master, :solo do
|
275
278
|
cmd = "cd #{c.release_path} && PATH=#{c.binstubs_path}:$PATH #{c.framework_envs} #{c.migration_command}"
|
276
|
-
|
279
|
+
shell.status "Migrating: #{cmd}"
|
277
280
|
run(cmd)
|
278
281
|
end
|
279
282
|
end
|
280
283
|
|
281
284
|
# task
|
282
285
|
def copy_repository_cache
|
283
|
-
|
286
|
+
shell.status "Copying to #{c.release_path}"
|
284
287
|
run("mkdir -p #{c.release_path} #{c.failed_release_dir} && rsync -aq #{c.exclusions} #{c.repository_cache}/ #{c.release_path}")
|
285
288
|
|
286
|
-
|
289
|
+
shell.status "Ensuring proper ownership."
|
287
290
|
sudo("chown -R #{c.user}:#{c.group} #{c.deploy_to}")
|
288
291
|
end
|
289
292
|
|
@@ -300,18 +303,18 @@ WRAP
|
|
300
303
|
end
|
301
304
|
|
302
305
|
def setup_services
|
303
|
-
|
306
|
+
shell.status "Setting up external services."
|
304
307
|
previously_configured_services = parse_configured_services
|
305
308
|
begin
|
306
309
|
sudo(services_command_check)
|
307
310
|
rescue StandardError => e
|
308
|
-
info "Could not setup services. Upgrade your environment to get services configuration."
|
311
|
+
shell.info "Could not setup services. Upgrade your environment to get services configuration."
|
309
312
|
return
|
310
313
|
end
|
311
314
|
sudo(services_setup_command)
|
312
315
|
rescue StandardError => e
|
313
316
|
unless previously_configured_services.empty?
|
314
|
-
warning <<-WARNING
|
317
|
+
shell.warning <<-WARNING
|
315
318
|
External services configuration not updated. Using previous version.
|
316
319
|
Deploy again if your services configuration appears incomplete or out of date.
|
317
320
|
#{e}
|
@@ -336,24 +339,24 @@ YML
|
|
336
339
|
WRAP
|
337
340
|
["Symlink database.yml", "ln -nfs #{c.shared_path}/config/database.sqlite3.yml #{c.release_path}/config/database.yml"],
|
338
341
|
].each do |what, cmd|
|
339
|
-
|
342
|
+
shell.status "#{what}"
|
340
343
|
run(cmd)
|
341
344
|
end
|
342
345
|
|
343
346
|
owner = [c.user, c.group].join(':')
|
344
|
-
|
347
|
+
shell.status "Setting ownership to #{owner}"
|
345
348
|
sudo "chown -R #{owner} #{c.release_path}"
|
346
349
|
end
|
347
350
|
end
|
348
351
|
|
349
352
|
def symlink_configs(release_to_link=c.release_path)
|
350
|
-
|
353
|
+
shell.status "Preparing shared resources for release."
|
351
354
|
symlink_tasks(release_to_link).each do |what, cmd|
|
352
|
-
|
355
|
+
shell.substatus what
|
353
356
|
run(cmd)
|
354
357
|
end
|
355
358
|
owner = [c.user, c.group].join(':')
|
356
|
-
|
359
|
+
shell.status "Setting ownership to #{owner}"
|
357
360
|
sudo "chown -R #{owner} #{release_to_link}"
|
358
361
|
end
|
359
362
|
|
@@ -376,7 +379,7 @@ WRAP
|
|
376
379
|
|
377
380
|
# task
|
378
381
|
def symlink(release_to_link=c.release_path)
|
379
|
-
|
382
|
+
shell.status "Symlinking code."
|
380
383
|
run "rm -f #{c.current_path} && ln -nfs #{release_to_link} #{c.current_path} && chown -R #{c.user}:#{c.group} #{c.current_path}"
|
381
384
|
@symlink_changed = true
|
382
385
|
rescue Exception
|
@@ -388,12 +391,12 @@ WRAP
|
|
388
391
|
def callback(what)
|
389
392
|
@callbacks_reached ||= true
|
390
393
|
if File.exist?("#{c.release_path}/deploy/#{what}.rb")
|
394
|
+
shell.status "Running deploy hook: deploy/#{what}.rb"
|
391
395
|
run Escape.shell_command(base_callback_command_for(what)) do |server, cmd|
|
392
|
-
per_instance_args = [
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
per_instance_args << '--current-name' << server.name.to_s if server.name
|
396
|
+
per_instance_args = []
|
397
|
+
per_instance_args << '--current-roles' << server.roles.join(' ')
|
398
|
+
per_instance_args << '--current-name' << server.name.to_s if server.name
|
399
|
+
per_instance_args << '--config' << c.to_json
|
397
400
|
cmd << " " << Escape.shell_command(per_instance_args)
|
398
401
|
end
|
399
402
|
end
|
@@ -401,8 +404,20 @@ WRAP
|
|
401
404
|
|
402
405
|
protected
|
403
406
|
|
404
|
-
|
405
|
-
|
407
|
+
# Use [] to access attributes instead of calling methods so
|
408
|
+
# that we get nils instead of NoMethodError.
|
409
|
+
#
|
410
|
+
# Rollback doesn't know about the repository location (nor
|
411
|
+
# should it need to), but it would like to use #short_log_message.
|
412
|
+
def strategy
|
413
|
+
ENV['GIT_SSH'] = ssh_executable
|
414
|
+
@strategy ||= config.strategy_class.new(
|
415
|
+
shell,
|
416
|
+
:repository_cache => config[:repository_cache],
|
417
|
+
:app => config[:app],
|
418
|
+
:repo => config[:repo],
|
419
|
+
:ref => config[:branch]
|
420
|
+
)
|
406
421
|
end
|
407
422
|
|
408
423
|
def gemfile?
|
@@ -410,11 +425,14 @@ WRAP
|
|
410
425
|
end
|
411
426
|
|
412
427
|
def base_callback_command_for(what)
|
413
|
-
[serverside_bin, 'hook', what.to_s
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
428
|
+
cmd = [serverside_bin, 'hook', what.to_s]
|
429
|
+
cmd << '--app' << config.app
|
430
|
+
cmd << '--environment-name' << config.environment_name
|
431
|
+
cmd << '--account-name' << config.account_name
|
432
|
+
cmd << '--release-path' << config.release_path.to_s
|
433
|
+
cmd << '--framework-env' << config.environment.to_s
|
434
|
+
cmd << '--verbose' if config.verbose
|
435
|
+
cmd
|
418
436
|
end
|
419
437
|
|
420
438
|
def serverside_bin
|
@@ -424,21 +442,22 @@ WRAP
|
|
424
442
|
|
425
443
|
def puts_deploy_failure
|
426
444
|
if @cleanup_failed
|
427
|
-
|
445
|
+
shell.notice "[Relax] Your site is running new code, but clean up of old deploys failed."
|
428
446
|
elsif @maintenance_up
|
429
|
-
|
430
|
-
|
431
|
-
|
447
|
+
message = "[Attention] Maintenance page still up, consider the following before removing:\n"
|
448
|
+
message << " * Deploy hooks ran. This might cause problems for reverting to old code.\n" if @callbacks_reached
|
449
|
+
message << " * Migrations ran. This might cause problems for reverting to old code.\n" if @migrations_reached
|
432
450
|
if @symlink_changed
|
433
|
-
|
451
|
+
message << " * Your new code is symlinked as current.\n"
|
434
452
|
else
|
435
|
-
|
453
|
+
message << " * Your old code is still symlinked as current.\n"
|
436
454
|
end
|
437
|
-
|
438
|
-
|
439
|
-
|
455
|
+
message << " * Application servers failed to restart.\n" if @restart_failed
|
456
|
+
message << "\n"
|
457
|
+
message << "Need help? File a ticket for support.\n"
|
458
|
+
shell.notice message
|
440
459
|
else
|
441
|
-
|
460
|
+
shell.notice "[Relax] Your site is still running old code and nothing destructive has occurred."
|
442
461
|
end
|
443
462
|
end
|
444
463
|
|
@@ -451,7 +470,7 @@ WRAP
|
|
451
470
|
def with_failed_release_cleanup
|
452
471
|
yield
|
453
472
|
rescue Exception
|
454
|
-
|
473
|
+
shell.status "Release #{c.release_path} failed, saving release to #{c.failed_release_dir}."
|
455
474
|
sudo "mv #{c.release_path} #{c.failed_release_dir}"
|
456
475
|
raise
|
457
476
|
end
|
@@ -484,7 +503,7 @@ WRAP
|
|
484
503
|
|
485
504
|
def check_ruby_bundler
|
486
505
|
if gemfile?
|
487
|
-
|
506
|
+
shell.status "Bundling gems..."
|
488
507
|
|
489
508
|
clean_bundle_on_system_version_change
|
490
509
|
|
@@ -515,18 +534,13 @@ WRAP
|
|
515
534
|
|
516
535
|
def check_node_npm
|
517
536
|
if File.exist?("#{c.release_path}/package.json")
|
518
|
-
info "~> package.json detected, installing npm packages"
|
537
|
+
shell.info "~> package.json detected, installing npm packages"
|
519
538
|
run "cd #{c.release_path} && npm install"
|
520
539
|
end
|
521
540
|
end
|
522
541
|
end # DeployBase
|
523
542
|
|
524
543
|
class Deploy < DeployBase
|
525
|
-
def self.new(config)
|
526
|
-
# include the correct fetch strategy
|
527
|
-
include EY::Serverside::Strategies.const_get(config.strategy)::Helpers
|
528
|
-
super
|
529
|
-
end
|
530
544
|
end
|
531
545
|
end
|
532
546
|
end
|