sunshine 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/History.txt +237 -0
  2. data/Manifest.txt +70 -0
  3. data/README.txt +277 -0
  4. data/Rakefile +46 -0
  5. data/bin/sunshine +5 -0
  6. data/examples/deploy.rb +61 -0
  7. data/examples/deploy_tasks.rake +112 -0
  8. data/examples/standalone_deploy.rb +31 -0
  9. data/lib/commands/add.rb +96 -0
  10. data/lib/commands/default.rb +169 -0
  11. data/lib/commands/list.rb +322 -0
  12. data/lib/commands/restart.rb +62 -0
  13. data/lib/commands/rm.rb +83 -0
  14. data/lib/commands/run.rb +151 -0
  15. data/lib/commands/start.rb +72 -0
  16. data/lib/commands/stop.rb +61 -0
  17. data/lib/sunshine/app.rb +876 -0
  18. data/lib/sunshine/binder.rb +70 -0
  19. data/lib/sunshine/crontab.rb +143 -0
  20. data/lib/sunshine/daemon.rb +380 -0
  21. data/lib/sunshine/daemons/ar_sendmail.rb +28 -0
  22. data/lib/sunshine/daemons/delayed_job.rb +30 -0
  23. data/lib/sunshine/daemons/nginx.rb +104 -0
  24. data/lib/sunshine/daemons/rainbows.rb +35 -0
  25. data/lib/sunshine/daemons/server.rb +66 -0
  26. data/lib/sunshine/daemons/unicorn.rb +26 -0
  27. data/lib/sunshine/dependencies.rb +103 -0
  28. data/lib/sunshine/dependency_lib.rb +200 -0
  29. data/lib/sunshine/exceptions.rb +54 -0
  30. data/lib/sunshine/healthcheck.rb +83 -0
  31. data/lib/sunshine/output.rb +131 -0
  32. data/lib/sunshine/package_managers/apt.rb +48 -0
  33. data/lib/sunshine/package_managers/dependency.rb +349 -0
  34. data/lib/sunshine/package_managers/gem.rb +54 -0
  35. data/lib/sunshine/package_managers/yum.rb +62 -0
  36. data/lib/sunshine/remote_shell.rb +241 -0
  37. data/lib/sunshine/repo.rb +128 -0
  38. data/lib/sunshine/repos/git_repo.rb +122 -0
  39. data/lib/sunshine/repos/rsync_repo.rb +29 -0
  40. data/lib/sunshine/repos/svn_repo.rb +78 -0
  41. data/lib/sunshine/server_app.rb +554 -0
  42. data/lib/sunshine/shell.rb +384 -0
  43. data/lib/sunshine.rb +391 -0
  44. data/templates/logrotate/logrotate.conf.erb +11 -0
  45. data/templates/nginx/nginx.conf.erb +109 -0
  46. data/templates/nginx/nginx_optimize.conf +23 -0
  47. data/templates/nginx/nginx_proxy.conf +13 -0
  48. data/templates/rainbows/rainbows.conf.erb +18 -0
  49. data/templates/tasks/sunshine.rake +114 -0
  50. data/templates/unicorn/unicorn.conf.erb +6 -0
  51. data/test/fixtures/app_configs/test_app.yml +11 -0
  52. data/test/fixtures/sunshine_test/test_upload +0 -0
  53. data/test/mocks/mock_object.rb +179 -0
  54. data/test/mocks/mock_open4.rb +117 -0
  55. data/test/test_helper.rb +188 -0
  56. data/test/unit/test_app.rb +489 -0
  57. data/test/unit/test_binder.rb +20 -0
  58. data/test/unit/test_crontab.rb +128 -0
  59. data/test/unit/test_git_repo.rb +26 -0
  60. data/test/unit/test_healthcheck.rb +70 -0
  61. data/test/unit/test_nginx.rb +107 -0
  62. data/test/unit/test_rainbows.rb +26 -0
  63. data/test/unit/test_remote_shell.rb +102 -0
  64. data/test/unit/test_repo.rb +42 -0
  65. data/test/unit/test_server.rb +324 -0
  66. data/test/unit/test_server_app.rb +425 -0
  67. data/test/unit/test_shell.rb +97 -0
  68. data/test/unit/test_sunshine.rb +157 -0
  69. data/test/unit/test_svn_repo.rb +55 -0
  70. data/test/unit/test_unicorn.rb +22 -0
  71. metadata +217 -0
@@ -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