sunshine 1.0.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/History.txt +237 -0
- data/Manifest.txt +70 -0
- data/README.txt +277 -0
- data/Rakefile +46 -0
- data/bin/sunshine +5 -0
- data/examples/deploy.rb +61 -0
- data/examples/deploy_tasks.rake +112 -0
- data/examples/standalone_deploy.rb +31 -0
- data/lib/commands/add.rb +96 -0
- data/lib/commands/default.rb +169 -0
- data/lib/commands/list.rb +322 -0
- data/lib/commands/restart.rb +62 -0
- data/lib/commands/rm.rb +83 -0
- data/lib/commands/run.rb +151 -0
- data/lib/commands/start.rb +72 -0
- data/lib/commands/stop.rb +61 -0
- data/lib/sunshine/app.rb +876 -0
- data/lib/sunshine/binder.rb +70 -0
- data/lib/sunshine/crontab.rb +143 -0
- data/lib/sunshine/daemon.rb +380 -0
- data/lib/sunshine/daemons/ar_sendmail.rb +28 -0
- data/lib/sunshine/daemons/delayed_job.rb +30 -0
- data/lib/sunshine/daemons/nginx.rb +104 -0
- data/lib/sunshine/daemons/rainbows.rb +35 -0
- data/lib/sunshine/daemons/server.rb +66 -0
- data/lib/sunshine/daemons/unicorn.rb +26 -0
- data/lib/sunshine/dependencies.rb +103 -0
- data/lib/sunshine/dependency_lib.rb +200 -0
- data/lib/sunshine/exceptions.rb +54 -0
- data/lib/sunshine/healthcheck.rb +83 -0
- data/lib/sunshine/output.rb +131 -0
- data/lib/sunshine/package_managers/apt.rb +48 -0
- data/lib/sunshine/package_managers/dependency.rb +349 -0
- data/lib/sunshine/package_managers/gem.rb +54 -0
- data/lib/sunshine/package_managers/yum.rb +62 -0
- data/lib/sunshine/remote_shell.rb +241 -0
- data/lib/sunshine/repo.rb +128 -0
- data/lib/sunshine/repos/git_repo.rb +122 -0
- data/lib/sunshine/repos/rsync_repo.rb +29 -0
- data/lib/sunshine/repos/svn_repo.rb +78 -0
- data/lib/sunshine/server_app.rb +554 -0
- data/lib/sunshine/shell.rb +384 -0
- data/lib/sunshine.rb +391 -0
- data/templates/logrotate/logrotate.conf.erb +11 -0
- data/templates/nginx/nginx.conf.erb +109 -0
- data/templates/nginx/nginx_optimize.conf +23 -0
- data/templates/nginx/nginx_proxy.conf +13 -0
- data/templates/rainbows/rainbows.conf.erb +18 -0
- data/templates/tasks/sunshine.rake +114 -0
- data/templates/unicorn/unicorn.conf.erb +6 -0
- data/test/fixtures/app_configs/test_app.yml +11 -0
- data/test/fixtures/sunshine_test/test_upload +0 -0
- data/test/mocks/mock_object.rb +179 -0
- data/test/mocks/mock_open4.rb +117 -0
- data/test/test_helper.rb +188 -0
- data/test/unit/test_app.rb +489 -0
- data/test/unit/test_binder.rb +20 -0
- data/test/unit/test_crontab.rb +128 -0
- data/test/unit/test_git_repo.rb +26 -0
- data/test/unit/test_healthcheck.rb +70 -0
- data/test/unit/test_nginx.rb +107 -0
- data/test/unit/test_rainbows.rb +26 -0
- data/test/unit/test_remote_shell.rb +102 -0
- data/test/unit/test_repo.rb +42 -0
- data/test/unit/test_server.rb +324 -0
- data/test/unit/test_server_app.rb +425 -0
- data/test/unit/test_shell.rb +97 -0
- data/test/unit/test_sunshine.rb +157 -0
- data/test/unit/test_svn_repo.rb +55 -0
- data/test/unit/test_unicorn.rb +22 -0
- metadata +217 -0
data/lib/sunshine/app.rb
ADDED
@@ -0,0 +1,876 @@
|
|
1
|
+
module Sunshine
|
2
|
+
|
3
|
+
##
|
4
|
+
# App objects are the core of sunshine deployment. The sunshine paradygm
|
5
|
+
# is to construct an app object, and run custom deploy code by passing
|
6
|
+
# a block to its deploy method:
|
7
|
+
#
|
8
|
+
# options = {
|
9
|
+
# :name => 'myapp',
|
10
|
+
# :repo => {:type => :svn, :url => 'svn://blah...'},
|
11
|
+
# :root_path => '/usr/local/myapp',
|
12
|
+
# :remote_shells => ['user@someserver.com']
|
13
|
+
# }
|
14
|
+
#
|
15
|
+
# app = Sunshine::App.new(options)
|
16
|
+
#
|
17
|
+
# app.deploy do |app|
|
18
|
+
#
|
19
|
+
# app_server = Sunshine::Rainbows.new(app)
|
20
|
+
# app_server.restart
|
21
|
+
#
|
22
|
+
# Sunshine::Nginx.new(app, :point_to => app_server).restart
|
23
|
+
#
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Multiple apps can be defined, and deployed from a single deploy script.
|
27
|
+
# The constructor also supports passing a yaml file path:
|
28
|
+
#
|
29
|
+
# Sunshine::App.new("path/to/config.yml")
|
30
|
+
#
|
31
|
+
# Deployment can be expressed more concisely by calling App::deploy:
|
32
|
+
#
|
33
|
+
# App.deploy("path/to/config.yml") do |app|
|
34
|
+
# Sunshine::Rainbows.new(app).restart
|
35
|
+
# end
|
36
|
+
|
37
|
+
class App
|
38
|
+
|
39
|
+
##
|
40
|
+
# Initialize and deploy an application.
|
41
|
+
# Takes any arguments supported by the constructor.
|
42
|
+
|
43
|
+
def self.deploy(*args, &block)
|
44
|
+
app = new(*args)
|
45
|
+
app.deploy(&block)
|
46
|
+
app
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
attr_reader :name, :repo, :server_apps, :sudo
|
51
|
+
attr_reader :root_path, :checkout_path, :current_path, :deploys_path
|
52
|
+
attr_reader :shared_path, :log_path, :deploy_name, :deploy_env
|
53
|
+
|
54
|
+
|
55
|
+
def initialize config_file=Sunshine::DATA, options={}
|
56
|
+
options, config_file = config_file, Sunshine::DATA if Hash === config_file
|
57
|
+
|
58
|
+
|
59
|
+
@deploy_env = options[:deploy_env] || Sunshine.deploy_env
|
60
|
+
|
61
|
+
binder = Binder.new self
|
62
|
+
binder.import_hash options
|
63
|
+
binder.forward :deploy_env
|
64
|
+
|
65
|
+
options = config_from_file(config_file, binder.get_binding).merge options
|
66
|
+
|
67
|
+
|
68
|
+
@repo = repo_from_config options[:repo]
|
69
|
+
|
70
|
+
@name = options[:name] || @repo.name
|
71
|
+
|
72
|
+
@deploy_name = options[:deploy_name] || Time.now.to_i.to_s
|
73
|
+
|
74
|
+
@server_app_filter = nil
|
75
|
+
|
76
|
+
set_deploy_paths options[:root_path]
|
77
|
+
|
78
|
+
@server_apps = server_apps_from_config options[:remote_shells]
|
79
|
+
|
80
|
+
self.sudo = options[:sudo] || Sunshine.sudo
|
81
|
+
|
82
|
+
@shell_env = {
|
83
|
+
"RACK_ENV" => @deploy_env.to_s,
|
84
|
+
"RAILS_ENV" => @deploy_env.to_s
|
85
|
+
}
|
86
|
+
shell_env options[:shell_env]
|
87
|
+
|
88
|
+
@post_user_lambdas = []
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
##
|
93
|
+
# Connect server apps.
|
94
|
+
|
95
|
+
def connect options=nil
|
96
|
+
with_server_apps options,
|
97
|
+
:msg => "Connecting..." do |server_app|
|
98
|
+
server_app.shell.connect
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
##
|
104
|
+
# Check if server apps are connected.
|
105
|
+
|
106
|
+
def connected? options=nil
|
107
|
+
with_server_apps options, :no_threads => true do |server_app|
|
108
|
+
return false unless server_app.shell.connected?
|
109
|
+
end
|
110
|
+
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
##
|
116
|
+
# Disconnect server apps.
|
117
|
+
|
118
|
+
def disconnect options=nil
|
119
|
+
with_server_apps options,
|
120
|
+
:msg => "Disconnecting..." do |server_app|
|
121
|
+
server_app.shell.disconnect
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
##
|
127
|
+
# Deploy the application to deploy servers and
|
128
|
+
# call user's post-deploy code. Supports any App#find options.
|
129
|
+
|
130
|
+
def deploy options=nil
|
131
|
+
Sunshine.logger.info :app, "Beginning deploy of #{@name}" do
|
132
|
+
connect options
|
133
|
+
end
|
134
|
+
|
135
|
+
deploy_trap = Sunshine.add_trap "Reverting deploy of #{@name}" do
|
136
|
+
revert! options
|
137
|
+
end
|
138
|
+
|
139
|
+
with_filter options do |app|
|
140
|
+
make_app_directories
|
141
|
+
checkout_codebase
|
142
|
+
symlink_current_dir
|
143
|
+
|
144
|
+
yield(self) if block_given?
|
145
|
+
|
146
|
+
run_post_user_lambdas
|
147
|
+
|
148
|
+
health :enable
|
149
|
+
|
150
|
+
build_control_scripts
|
151
|
+
build_deploy_info_file
|
152
|
+
build_crontab
|
153
|
+
|
154
|
+
register_as_deployed
|
155
|
+
remove_old_deploys
|
156
|
+
end
|
157
|
+
|
158
|
+
rescue => e
|
159
|
+
message = "#{e.class}: #{e.message}"
|
160
|
+
|
161
|
+
Sunshine.logger.error :app, message do
|
162
|
+
Sunshine.logger.error '>>', e.backtrace.join("\n")
|
163
|
+
revert!
|
164
|
+
end
|
165
|
+
|
166
|
+
ensure
|
167
|
+
Sunshine.delete_trap deploy_trap
|
168
|
+
|
169
|
+
Sunshine.logger.info :app, "Ending deploy of #{@name}" do
|
170
|
+
disconnect options
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
##
|
176
|
+
# Symlink current directory to previous checkout and remove
|
177
|
+
# the current deploy directory.
|
178
|
+
|
179
|
+
def revert!(options=nil)
|
180
|
+
with_server_apps options,
|
181
|
+
:msg => "Reverting to previous deploy.",
|
182
|
+
:send => :revert!
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
##
|
187
|
+
# Add paths the the shell $PATH env.
|
188
|
+
|
189
|
+
def add_shell_paths(*paths)
|
190
|
+
path = @shell_env["PATH"] || "$PATH"
|
191
|
+
paths << path
|
192
|
+
|
193
|
+
shell_env "PATH" => paths.join(":")
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
##
|
198
|
+
# Add a command to the crontab to be generated remotely:
|
199
|
+
# add_to_crontab "reboot", "@reboot /path/to/app/start", :role => :web
|
200
|
+
|
201
|
+
def add_to_crontab name, cronjob, options=nil
|
202
|
+
with_server_apps options do |server_app|
|
203
|
+
server_app.crontab[name] = cronjob
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
##
|
209
|
+
# Add a command to a control script to be generated remotely:
|
210
|
+
# add_to_script :start, "do this on start"
|
211
|
+
# add_to_script :start, "start_mail", :role => :mail
|
212
|
+
|
213
|
+
def add_to_script name, script, options=nil
|
214
|
+
with_server_apps options do |server_app|
|
215
|
+
server_app.scripts[name] << script
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
##
|
221
|
+
# Define lambdas to run right after the user's yield.
|
222
|
+
# app.after_user_script do |app|
|
223
|
+
# ...
|
224
|
+
# end
|
225
|
+
|
226
|
+
def after_user_script &block
|
227
|
+
@post_user_lambdas << block
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
##
|
232
|
+
# Creates and uploads all control scripts for the application.
|
233
|
+
# To add to, or define a control script, see App#add_to_script.
|
234
|
+
|
235
|
+
def build_control_scripts options=nil
|
236
|
+
with_server_apps options,
|
237
|
+
:msg => "Building control scripts",
|
238
|
+
:send => :build_control_scripts
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
##
|
243
|
+
# Writes the crontab on all or selected server apps.
|
244
|
+
# To add or remove from the crontab, see App#add_to_crontab and
|
245
|
+
# App#remove_cronjob.
|
246
|
+
|
247
|
+
def build_crontab options=nil
|
248
|
+
with_server_apps options,
|
249
|
+
:msg => "Building the crontab" do |server_app|
|
250
|
+
server_app.crontab.write!
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
##
|
256
|
+
# Creates a yaml file with deploy information. To add custom information
|
257
|
+
# to the info file, use the app's info hash attribute:
|
258
|
+
# app.info[:key] = "some value"
|
259
|
+
|
260
|
+
def build_deploy_info_file options=nil
|
261
|
+
with_server_apps options,
|
262
|
+
:msg => "Creating info file",
|
263
|
+
:send => :build_deploy_info_file
|
264
|
+
end
|
265
|
+
|
266
|
+
|
267
|
+
##
|
268
|
+
# Parse an erb file and return the newly created string.
|
269
|
+
# Default binding is the app's binding.
|
270
|
+
|
271
|
+
def build_erb erb_file, custom_binding=nil
|
272
|
+
str = File === erb_file ? erb_file.read : File.read(erb_file)
|
273
|
+
ERB.new(str, nil, '-').result(custom_binding || binding)
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
##
|
278
|
+
# Checks out the app's codebase to one or all deploy servers.
|
279
|
+
|
280
|
+
def checkout_codebase options=nil
|
281
|
+
with_server_apps options,
|
282
|
+
:msg => "Checking out codebase",
|
283
|
+
:send => [:checkout_repo, @repo]
|
284
|
+
|
285
|
+
rescue => e
|
286
|
+
raise CriticalDeployError, e
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
##
|
291
|
+
# Get a hash of deploy information for each server app.
|
292
|
+
# Post-deploy only.
|
293
|
+
|
294
|
+
def deploy_details options=nil
|
295
|
+
details = {}
|
296
|
+
|
297
|
+
with_server_apps options, :msg => "Getting deploy info..." do |server_app|
|
298
|
+
details[server_app.shell.host] = server_app.deploy_details
|
299
|
+
end
|
300
|
+
|
301
|
+
details
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
##
|
306
|
+
# Check if app has been deployed successfully.
|
307
|
+
|
308
|
+
def deployed? options=nil
|
309
|
+
with_server_apps options, :no_threads => true do |server_app|
|
310
|
+
return false unless server_app.deployed?
|
311
|
+
end
|
312
|
+
|
313
|
+
true
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
##
|
318
|
+
# Iterate over each server app.
|
319
|
+
|
320
|
+
def each(options=nil, &block)
|
321
|
+
server_apps = find(options)
|
322
|
+
server_apps.each(&block)
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
##
|
327
|
+
# Find server apps matching the passed requirements.
|
328
|
+
# Returns an array of server apps.
|
329
|
+
# find :user => 'db'
|
330
|
+
# find :host => 'someserver.com'
|
331
|
+
# find :role => :web
|
332
|
+
|
333
|
+
def find query=nil
|
334
|
+
if @server_app_filter
|
335
|
+
if Hash === query && Hash === @server_app_filter
|
336
|
+
query.merge! @server_app_filter
|
337
|
+
else
|
338
|
+
query = @server_app_filter
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
return @server_apps if query.nil? || query == :all
|
343
|
+
|
344
|
+
@server_apps.select do |sa|
|
345
|
+
next unless sa.shell.user == query[:user] if query[:user]
|
346
|
+
next unless sa.shell.host == query[:host] if query[:host]
|
347
|
+
|
348
|
+
next unless sa.has_roles?(query[:role]) if query[:role]
|
349
|
+
|
350
|
+
true
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
##
|
356
|
+
# Decrypt a file using gpg. Allows all DeployServerDispatcher#find
|
357
|
+
# options, plus:
|
358
|
+
# :output:: str - the path the output file should go to
|
359
|
+
# :passphrase:: str - the passphrase gpg should use
|
360
|
+
|
361
|
+
def gpg_decrypt gpg_file, options={}
|
362
|
+
options[:passphrase] ||=
|
363
|
+
Sunshine.shell.ask("Enter gpg passphrase:") do |q|
|
364
|
+
q.echo = false
|
365
|
+
end
|
366
|
+
|
367
|
+
with_server_apps options,
|
368
|
+
:msg => "Gpg decrypt: #{gpg_file}",
|
369
|
+
:send => [:gpg_decrypt, gpg_file, options]
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
%w{gem yum apt}.each do |dep_type|
|
374
|
+
self.class_eval <<-STR, __FILE__, __LINE__ + 1
|
375
|
+
##
|
376
|
+
# Install one or more #{dep_type} packages.
|
377
|
+
# See Settler::#{dep_type.capitalize}#new for supported options.
|
378
|
+
|
379
|
+
def #{dep_type}_install(*names)
|
380
|
+
options = names.last if Hash === names.last
|
381
|
+
with_server_apps options,
|
382
|
+
:msg => "Installing #{dep_type} packages",
|
383
|
+
:send => [:#{dep_type}_install, *names]
|
384
|
+
end
|
385
|
+
STR
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
##
|
390
|
+
# Gets or sets the healthcheck state. Returns a hash of host/state
|
391
|
+
# pairs. State values are :enabled, :disabled, and :down. The method
|
392
|
+
# argument can be omitted or take a value of :enable, :disable, or :remove:
|
393
|
+
# app.health
|
394
|
+
# #=> Returns the health status for all server_apps
|
395
|
+
#
|
396
|
+
# app.health :role => :web
|
397
|
+
# #=> Returns the status of all server_apps of role :web
|
398
|
+
#
|
399
|
+
# app.health :enable
|
400
|
+
# #=> Enables all health checking and returns the status
|
401
|
+
#
|
402
|
+
# app.health :disable, :role => :web
|
403
|
+
# #=> Disables health checking for :web server_apps and returns the status
|
404
|
+
|
405
|
+
def health method=nil, options=nil
|
406
|
+
valid_methods = [:enable, :disable, :remove]
|
407
|
+
options = method if options.nil? && Hash === method
|
408
|
+
|
409
|
+
valid_method = valid_methods.include? method
|
410
|
+
|
411
|
+
message = "#{method.to_s.capitalize[0..-2]}ing" if valid_method
|
412
|
+
message ||= "Getting status of"
|
413
|
+
message = "#{message} healthcheck"
|
414
|
+
|
415
|
+
statuses = {}
|
416
|
+
with_server_apps options, :msg => message do |server_app|
|
417
|
+
server_app.health.send method if valid_method
|
418
|
+
|
419
|
+
statuses[server_app.shell.host] = server_app.health.status
|
420
|
+
end
|
421
|
+
|
422
|
+
statuses
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
##
|
427
|
+
# Install dependencies defined as a Sunshine dependency object:
|
428
|
+
# rake = Sunshine.dependencies.gem 'rake', :version => '~>0.8'
|
429
|
+
# apache = Sunshine.dependencies.yum 'apache'
|
430
|
+
# app.install_deps rake, apache
|
431
|
+
#
|
432
|
+
# Deploy servers can also be specified as a dispatcher, array, or single
|
433
|
+
# deploy server, by passing standard 'find' options:
|
434
|
+
# postgres = Sunshine.dependencies.yum 'postgresql'
|
435
|
+
# pgserver = Sunshine.dependencies.yum 'postgresql-server'
|
436
|
+
# app.install_deps postgres, pgserver, :role => 'db'
|
437
|
+
#
|
438
|
+
# If a dependency was already defined in the Sunshine dependency tree,
|
439
|
+
# the dependency name may be passed instead of the object:
|
440
|
+
# app.install_deps 'nginx', 'ruby'
|
441
|
+
|
442
|
+
def install_deps(*deps)
|
443
|
+
options = Hash === deps[-1] ? deps.delete_at(-1) : {}
|
444
|
+
|
445
|
+
with_server_apps options,
|
446
|
+
:msg => "Installing dependencies: #{deps.map{|d| d.to_s}.join(" ")}",
|
447
|
+
:send => [:install_deps, *deps]
|
448
|
+
end
|
449
|
+
|
450
|
+
|
451
|
+
##
|
452
|
+
# Creates the required application directories.
|
453
|
+
|
454
|
+
def make_app_directories options=nil
|
455
|
+
with_server_apps options,
|
456
|
+
:msg => "Creating #{@name} directories",
|
457
|
+
:send => :make_app_directories
|
458
|
+
|
459
|
+
rescue => e
|
460
|
+
raise FatalDeployError, e
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
##
|
465
|
+
# Assign the prefered package manager to all server_apps:
|
466
|
+
# app.prefer_pkg_manager Settler::Yum
|
467
|
+
#
|
468
|
+
# Package managers are typically detected automatically by each
|
469
|
+
# individual server_apps.
|
470
|
+
|
471
|
+
def prefer_pkg_manager pkg_manager, options=nil
|
472
|
+
with_server_apps options,
|
473
|
+
:send => [:pkg_manager=, pkg_manager]
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
##
|
478
|
+
# Run a rake task on any or all deploy servers.
|
479
|
+
|
480
|
+
def rake command, options=nil
|
481
|
+
with_server_apps options,
|
482
|
+
:msg => "Running Rake task '#{command}'",
|
483
|
+
:send => [:rake, command]
|
484
|
+
end
|
485
|
+
|
486
|
+
|
487
|
+
##
|
488
|
+
# Adds the app to the deploy servers deployed-apps list.
|
489
|
+
|
490
|
+
def register_as_deployed options=nil
|
491
|
+
with_server_apps options,
|
492
|
+
:msg => "Registering app with deploy servers",
|
493
|
+
:send => :register_as_deployed
|
494
|
+
end
|
495
|
+
|
496
|
+
|
497
|
+
##
|
498
|
+
# Remove a cron job from the remote crontabs:
|
499
|
+
# remove_cronjob "reboot", :role => :web
|
500
|
+
# remove_cronjob :all
|
501
|
+
# #=> deletes all cronjobs related to this app
|
502
|
+
|
503
|
+
def remove_cronjob name, options=nil
|
504
|
+
with_server_apps options,
|
505
|
+
:msg => "Removing cronjob #{name.inspect}" do |server_app|
|
506
|
+
if name == :all
|
507
|
+
server_app.crontab.clear
|
508
|
+
else
|
509
|
+
server_app.crontab.delete(name)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
##
|
516
|
+
# Removes old deploys from the checkout_dir
|
517
|
+
# based on Sunshine's max_deploy_versions.
|
518
|
+
|
519
|
+
def remove_old_deploys options=nil
|
520
|
+
with_server_apps options,
|
521
|
+
:msg => "Removing old deploys (max = #{Sunshine.max_deploy_versions})",
|
522
|
+
:send => :remove_old_deploys
|
523
|
+
end
|
524
|
+
|
525
|
+
|
526
|
+
##
|
527
|
+
# Run the restart script of a deployed app on the specified
|
528
|
+
# deploy servers.
|
529
|
+
# Post-deploy only.
|
530
|
+
|
531
|
+
def restart options=nil
|
532
|
+
with_server_apps options,
|
533
|
+
:msg => "Running restart script",
|
534
|
+
:send => :restart
|
535
|
+
end
|
536
|
+
|
537
|
+
|
538
|
+
##
|
539
|
+
# Runs bundler on deploy servers.
|
540
|
+
|
541
|
+
def run_bundler options=nil
|
542
|
+
with_server_apps options,
|
543
|
+
:msg => "Running Bundler",
|
544
|
+
:send => :run_bundler
|
545
|
+
|
546
|
+
rescue => e
|
547
|
+
raise CriticalDeployError, e
|
548
|
+
end
|
549
|
+
|
550
|
+
|
551
|
+
##
|
552
|
+
# Runs GemInstaller on deploy servers.
|
553
|
+
|
554
|
+
def run_geminstaller options=nil
|
555
|
+
with_server_apps options,
|
556
|
+
:msg => "Running GemInstaller",
|
557
|
+
:send => :run_geminstaller
|
558
|
+
|
559
|
+
rescue => e
|
560
|
+
raise CriticalDeployError, e
|
561
|
+
end
|
562
|
+
|
563
|
+
|
564
|
+
##
|
565
|
+
# Run lambdas that were saved for after the user's script.
|
566
|
+
# See #after_user_script.
|
567
|
+
|
568
|
+
def run_post_user_lambdas
|
569
|
+
@post_user_lambdas.each{|l| l.call self}
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
##
|
574
|
+
# Run a sass task on any or all deploy servers.
|
575
|
+
|
576
|
+
def sass *sass_names
|
577
|
+
options = sass_names.delete_at(-1) if Hash === sass_names.last
|
578
|
+
|
579
|
+
with_server_apps options,
|
580
|
+
:msg => "Running Sass for #{sass_names.join(' ')}",
|
581
|
+
:send => [:sass, *sass_names]
|
582
|
+
end
|
583
|
+
|
584
|
+
|
585
|
+
##
|
586
|
+
# Set and return the remote shell env variables.
|
587
|
+
# Also assigns shell environment to the app's deploy servers.
|
588
|
+
|
589
|
+
def shell_env env_hash=nil
|
590
|
+
env_hash ||= {}
|
591
|
+
|
592
|
+
@shell_env.merge!(env_hash)
|
593
|
+
|
594
|
+
with_server_apps :all,
|
595
|
+
:msg => "Shell env: #{@shell_env.inspect}" do |server_app|
|
596
|
+
server_app.shell_env.merge!(@shell_env)
|
597
|
+
end
|
598
|
+
|
599
|
+
@shell_env.dup
|
600
|
+
end
|
601
|
+
|
602
|
+
|
603
|
+
##
|
604
|
+
# Run the start script of a deployed app on the specified
|
605
|
+
# deploy servers.
|
606
|
+
# Post-deploy only.
|
607
|
+
|
608
|
+
def start options=nil
|
609
|
+
with_server_apps options,
|
610
|
+
:msg => "Running start script",
|
611
|
+
:send => [:start, options]
|
612
|
+
end
|
613
|
+
|
614
|
+
|
615
|
+
##
|
616
|
+
# Get a hash of which deploy server apps are :running or :down.
|
617
|
+
# Post-deploy only.
|
618
|
+
|
619
|
+
def status options=nil
|
620
|
+
statuses = {}
|
621
|
+
|
622
|
+
with_server_apps options, :msg => "Querying app status..." do |server_app|
|
623
|
+
statuses[server_app.shell.host] = server_app.status
|
624
|
+
end
|
625
|
+
|
626
|
+
statuses
|
627
|
+
end
|
628
|
+
|
629
|
+
|
630
|
+
##
|
631
|
+
# Run the stop script of a deployed app on the specified
|
632
|
+
# deploy servers.
|
633
|
+
# Post-deploy only.
|
634
|
+
|
635
|
+
def stop options=nil
|
636
|
+
with_server_apps options,
|
637
|
+
:msg => "Running stop script",
|
638
|
+
:send => :stop
|
639
|
+
end
|
640
|
+
|
641
|
+
|
642
|
+
##
|
643
|
+
# Use sudo on deploy servers. Set to true/false, or
|
644
|
+
# a username to use 'sudo -u'.
|
645
|
+
|
646
|
+
def sudo=(value)
|
647
|
+
with_server_apps :all,
|
648
|
+
:msg => "Using sudo = #{value.inspect}" do |server_app|
|
649
|
+
server_app.shell.sudo = value
|
650
|
+
end
|
651
|
+
|
652
|
+
@sudo = value
|
653
|
+
end
|
654
|
+
|
655
|
+
|
656
|
+
##
|
657
|
+
# Creates a symlink to the app's checkout path.
|
658
|
+
|
659
|
+
def symlink_current_dir options=nil
|
660
|
+
with_server_apps options,
|
661
|
+
:msg => "Symlinking #{@checkout_path} -> #{@current_path}",
|
662
|
+
:send => :symlink_current_dir
|
663
|
+
|
664
|
+
rescue => e
|
665
|
+
raise CriticalDeployError, e
|
666
|
+
end
|
667
|
+
|
668
|
+
|
669
|
+
##
|
670
|
+
# Iterate over all deploy servers but create a thread for each
|
671
|
+
# deploy server. Means you can't return from the passed block!
|
672
|
+
|
673
|
+
def threaded_each(options=nil, &block)
|
674
|
+
mutex = Mutex.new
|
675
|
+
threads = []
|
676
|
+
|
677
|
+
return_val = each(options) do |server_app|
|
678
|
+
|
679
|
+
thread = Thread.new do
|
680
|
+
server_app.shell.with_mutex mutex do
|
681
|
+
yield server_app
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# We don't want deploy servers to keep doing things if one fails
|
686
|
+
thread.abort_on_exception = true
|
687
|
+
|
688
|
+
threads << thread
|
689
|
+
end
|
690
|
+
|
691
|
+
threads.each{|t| t.join }
|
692
|
+
|
693
|
+
return_val
|
694
|
+
end
|
695
|
+
|
696
|
+
|
697
|
+
##
|
698
|
+
# Upload common rake tasks from the sunshine lib.
|
699
|
+
# app.upload_tasks
|
700
|
+
# #=> upload all tasks
|
701
|
+
# app.upload_tasks 'app', 'common', :role => :web
|
702
|
+
# #=> upload app and common rake files
|
703
|
+
#
|
704
|
+
# Allows standard DeployServerDispatcher#find options, plus:
|
705
|
+
# :remote_path:: str - the remote absolute path to upload the files to
|
706
|
+
|
707
|
+
def upload_tasks *files
|
708
|
+
options = Hash === files.last ? files.last.dup : {}
|
709
|
+
|
710
|
+
options.delete(:remote_path)
|
711
|
+
options = :all if options.empty?
|
712
|
+
|
713
|
+
with_server_apps options,
|
714
|
+
:msg => "Uploading tasks: #{files.join(" ")}",
|
715
|
+
:send => [:upload_tasks, *files]
|
716
|
+
end
|
717
|
+
|
718
|
+
|
719
|
+
##
|
720
|
+
# Execute a block with a specified server app filter:
|
721
|
+
# app.with_filter :role => :cdn do |app|
|
722
|
+
# app.sass 'file1', 'file2', 'file3'
|
723
|
+
# app.rake 'asset:packager:build_all'
|
724
|
+
# end
|
725
|
+
|
726
|
+
def with_filter filter_hash
|
727
|
+
old_filter, @server_app_filter = @server_app_filter, filter_hash
|
728
|
+
|
729
|
+
yield self
|
730
|
+
|
731
|
+
@server_app_filter = old_filter
|
732
|
+
end
|
733
|
+
|
734
|
+
|
735
|
+
##
|
736
|
+
# Calls a method for server_apps found with the passed options,
|
737
|
+
# and with an optional log message. Supports all App#find
|
738
|
+
# options, plus:
|
739
|
+
# :no_threads:: bool - disable threaded execution
|
740
|
+
# :msg:: "some message" - log message
|
741
|
+
#
|
742
|
+
# app.with_server_apps :all, :msg => "doing something" do |server_app|
|
743
|
+
# # do something here
|
744
|
+
# end
|
745
|
+
#
|
746
|
+
# app.with_server_apps :role => :db, :user => "bob" do |server_app|
|
747
|
+
# # do something here
|
748
|
+
# end
|
749
|
+
|
750
|
+
def with_server_apps search_options, options={}
|
751
|
+
options = search_options.merge options if Hash === search_options
|
752
|
+
|
753
|
+
message = options[:msg]
|
754
|
+
method = options[:no_threads] ? :each : :threaded_each
|
755
|
+
|
756
|
+
block = lambda do
|
757
|
+
send(method, search_options) do |server_app|
|
758
|
+
|
759
|
+
if block_given?
|
760
|
+
yield(server_app)
|
761
|
+
|
762
|
+
elsif options[:send]
|
763
|
+
server_app.send(*options[:send])
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
|
769
|
+
if message
|
770
|
+
Sunshine.logger.info(:app, message, &block)
|
771
|
+
|
772
|
+
else
|
773
|
+
block.call
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
|
778
|
+
private
|
779
|
+
|
780
|
+
|
781
|
+
##
|
782
|
+
# Set all the app paths based on the root deploy path.
|
783
|
+
|
784
|
+
def set_deploy_paths path
|
785
|
+
@root_path = path || File.join(Sunshine.web_directory, @name)
|
786
|
+
@current_path = "#{@root_path}/current"
|
787
|
+
@deploys_path = "#{@root_path}/deploys"
|
788
|
+
@shared_path = "#{@root_path}/shared"
|
789
|
+
@log_path = "#{@shared_path}/log"
|
790
|
+
@checkout_path = "#{@deploys_path}/#{@deploy_name}"
|
791
|
+
end
|
792
|
+
|
793
|
+
|
794
|
+
##
|
795
|
+
# Set the app's deploy servers:
|
796
|
+
# server_apps_from_config "some_server"
|
797
|
+
# #=> [<ServerApp @host="some_server"...>]
|
798
|
+
#
|
799
|
+
# server_apps_from_config ["svr1", "svr2", "svr3"]
|
800
|
+
# #=> [<ServerApp @host="svr1">,<ServerApp @host="svr2">, ...]
|
801
|
+
#
|
802
|
+
# d_servers = [["svr1", {:roles => "web db app"}], "svr2", "svr3"]
|
803
|
+
# server_apps_from_config d_servers
|
804
|
+
# #=> [<ServerApp @host="svr1">,<ServerApp @host="svr2">, ...]
|
805
|
+
|
806
|
+
def server_apps_from_config d_servers
|
807
|
+
d_servers = [*d_servers].compact
|
808
|
+
d_servers.map{|ds| ServerApp.new(*[self,*ds]) }
|
809
|
+
end
|
810
|
+
|
811
|
+
|
812
|
+
##
|
813
|
+
# Set the app's repo:
|
814
|
+
# repo_from_config SvnRepo.new("myurl")
|
815
|
+
# repo_from_config :type => :svn, :url => "myurl"
|
816
|
+
|
817
|
+
def repo_from_config repo_def
|
818
|
+
case repo_def
|
819
|
+
when Sunshine::Repo
|
820
|
+
repo_def
|
821
|
+
when Hash
|
822
|
+
Sunshine::Repo.new_of_type repo_def[:type],
|
823
|
+
repo_def[:url], repo_def
|
824
|
+
else
|
825
|
+
Sunshine::Repo.detect Sunshine::PATH
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
|
830
|
+
##
|
831
|
+
# Load a yml config file, parses it with erb and resolves deploy env
|
832
|
+
# inheritance.
|
833
|
+
|
834
|
+
def config_from_file config_file, erb_binding=binding, env=@deploy_env
|
835
|
+
return {} unless config_file
|
836
|
+
|
837
|
+
config_data = YAML.load build_erb(config_file, erb_binding)
|
838
|
+
|
839
|
+
load_config_for config_data, env
|
840
|
+
end
|
841
|
+
|
842
|
+
|
843
|
+
##
|
844
|
+
# Loads an app yml config file, gets the default config
|
845
|
+
# and the current deploy env and returns a merged config hash.
|
846
|
+
|
847
|
+
def load_config_for config_hash, env
|
848
|
+
return {} unless config_hash
|
849
|
+
|
850
|
+
deploy_env_config = (config_hash[env] || {}).dup
|
851
|
+
deploy_env_config[:inherits] ||= []
|
852
|
+
deploy_env_config[:inherits].unshift(:default) if
|
853
|
+
:default != env && config_hash[:default]
|
854
|
+
|
855
|
+
merge_config_inheritance deploy_env_config, config_hash
|
856
|
+
end
|
857
|
+
|
858
|
+
|
859
|
+
##
|
860
|
+
# Recursively merges config hashes based on the value at :inherits
|
861
|
+
|
862
|
+
def merge_config_inheritance main_config, all_configs
|
863
|
+
new_config = {}
|
864
|
+
parents = [*main_config[:inherits]].compact
|
865
|
+
|
866
|
+
parents.each do |config_name|
|
867
|
+
parent = all_configs[config_name]
|
868
|
+
parent = merge_config_inheritance parent, all_configs
|
869
|
+
|
870
|
+
new_config = new_config.merge parent
|
871
|
+
end
|
872
|
+
|
873
|
+
new_config.merge main_config # Two merges important for inheritance order
|
874
|
+
end
|
875
|
+
end
|
876
|
+
end
|