sunshine 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,29 @@
1
+ === 1.2.1 / 2010-10-05
2
+
3
+ * Improvements:
4
+
5
+ * Added customizable behaviors for sigint and failures during deploys.
6
+
7
+ * Added Sunshine irb shell to work directly with deploying apps.
8
+
9
+ * Added ability to launch a pseudo terminal from a RemoteShell.
10
+
11
+ * Added exit code to CmdError.
12
+
13
+ * Added exclude paths for checkout by copy.
14
+
15
+ * Bugfixes:
16
+
17
+ * Fixed custom scripts.
18
+
19
+ * Fixed deploy env defaults.
20
+
21
+ * Removed Shell#update_timeout as it was unnecessary.
22
+
23
+ * RemoteShell rsync recurses by default.
24
+
25
+ * Fixed start and stop script permissions when calling sunshine commands.
26
+
1
27
  === 1.2.0 / 2010-09-08
2
28
 
3
29
  * Improvements:
@@ -46,6 +46,7 @@ lib/sunshine/repos/rsync_repo.rb
46
46
  lib/sunshine/repos/svn_repo.rb
47
47
  lib/sunshine/server_app.rb
48
48
  lib/sunshine/shell.rb
49
+ lib/sunshine/trap_stack.rb
49
50
  templates/apache/apache.conf.erb
50
51
  templates/mongrel_rails/mongrel_rails.conf.erb
51
52
  templates/nginx/nginx.conf.erb
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env ruby -w
2
2
 
3
- require 'sunshine'
3
+ begin
4
+ require 'sunshine'
5
+ rescue LoadError => e
6
+ raise e unless e.message =~ %r{no such file to load -- sunshine}
7
+
8
+ $: << File.join(File.dirname(__FILE__), "../lib")
9
+ require 'sunshine'
10
+ end
4
11
 
5
12
  Sunshine.run
@@ -16,6 +16,7 @@ module Sunshine
16
16
  # -f, --format FORMAT Set the output format (txt, yml, json)
17
17
  # -u, --user USER User to use for remote login. Use with -r
18
18
  # -r, --remote svr1,svr2 Run on one or more remote servers.
19
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
19
20
  # -v, --verbose Run in verbose mode.
20
21
 
21
22
  class ListCommand < DefaultCommand
@@ -176,7 +177,11 @@ module Sunshine
176
177
 
177
178
  def status(*app_names)
178
179
  each_app(*app_names) do |server_app|
179
- server_app.status
180
+ begin
181
+ server_app.status
182
+ rescue => e
183
+ e.message
184
+ end
180
185
  end
181
186
  end
182
187
 
@@ -185,17 +190,17 @@ module Sunshine
185
190
  # Runs a command and returns the status for each app_name:
186
191
  # status_after_command 'restart', ['app1', 'app2']
187
192
 
188
- def status_after_command cmd, app_names
193
+ def status_after_command cmd, app_names, options=nil
189
194
  each_app(*app_names) do |server_app|
190
195
 
191
196
  yield(server_app) if block_given?
192
197
 
193
198
  begin
194
- server_app.run_script cmd
195
- server_app.running? ? 'running' : 'down'
199
+ server_app.run_script! cmd, options
200
+ server_app.status.to_s
196
201
 
197
202
  rescue CmdError => e
198
- raise "Failed running #{cmd}: #{server_app.status}"
203
+ raise "Failed running #{cmd}: #{server_app.status rescue :not_found}"
199
204
  end
200
205
  end
201
206
  end
@@ -268,7 +273,7 @@ module Sunshine
268
273
  # Load the app list yaml file from the server.
269
274
 
270
275
  def self.load_list server
271
- yml_list = server.call "cat #{Sunshine::APP_LIST_PATH} || echo ''"
276
+ yml_list = server.call "cat #{Sunshine::APP_LIST_PATH} || echo"
272
277
 
273
278
  list = YAML.load yml_list
274
279
  list = {} unless Hash === list
@@ -9,10 +9,11 @@ module Sunshine
9
9
  # app_name Name of the application to restart.
10
10
  #
11
11
  # Options:
12
- # -f, --format FORMAT Set the output format (txt, yml, json)
13
- # -u, --user USER User to use for remote login. Use with -r.
14
- # -r, --remote svr1,svr2 Run on one or more remote servers.
15
- # -v, --verbose Run in verbose mode.
12
+ # -f, --format FORMAT Set the output format (txt, yml, json)
13
+ # -u, --user USER User to use for remote login. Use with -r.
14
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
15
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
16
+ # -v, --verbose Run in verbose mode.
16
17
 
17
18
  class RestartCommand < ListCommand
18
19
 
@@ -38,7 +39,7 @@ module Sunshine
38
39
  # Restart specified apps.
39
40
 
40
41
  def restart app_names
41
- status_after_command :restart, app_names
42
+ status_after_command :restart, app_names, :sudo => false
42
43
  end
43
44
 
44
45
 
@@ -9,11 +9,12 @@ module Sunshine
9
9
  # app_name Name of the application to remove.
10
10
  #
11
11
  # Options:
12
- # -d, --delete Delete the app directory.
13
- # -f, --format FORMAT Set the output format (txt, yml, json)
14
- # -u, --user USER User to use for remote login. Use with -r.
15
- # -r, --remote svr1,svr2 Run on one or more remote servers.
16
- # -v, --verbose Run in verbose mode.
12
+ # -d, --delete Delete the app directory.
13
+ # -f, --format FORMAT Set the output format (txt, yml, json)
14
+ # -u, --user USER User to use for remote login. Use with -r.
15
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
16
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
17
+ # -v, --verbose Run in verbose mode.
17
18
 
18
19
  class RmCommand < ListCommand
19
20
 
@@ -10,10 +10,11 @@ module Sunshine
10
10
  # app_name Name of the application to run script for.
11
11
  #
12
12
  # Options:
13
- # -f, --format FORMAT Set the output format (txt, yml, json)
14
- # -u, --user USER User to use for remote login. Use with -r.
15
- # -r, --remote svr1,svr2 Run on one or more remote servers.
16
- # -v, --verbose Run in verbose mode.
13
+ # -f, --format FORMAT Set the output format (txt, yml, json)
14
+ # -u, --user USER User to use for remote login. Use with -r.
15
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
16
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
17
+ # -v, --verbose Run in verbose mode.
17
18
 
18
19
  class ScriptCommand < ListCommand
19
20
 
@@ -9,11 +9,12 @@ module Sunshine
9
9
  # app_name Name of the application to start.
10
10
  #
11
11
  # Options:
12
- # -F, --force Stop apps that are running, then start them.
13
- # -f, --format FORMAT Set the output format (txt, yml, json)
14
- # -u, --user USER User to use for remote login. Use with -r.
15
- # -r, --remote svr1,svr2 Run on one or more remote servers.
16
- # -v, --verbose Run in verbose mode.
12
+ # -F, --force Stop apps that are running, then start them.
13
+ # -f, --format FORMAT Set the output format (txt, yml, json)
14
+ # -u, --user USER User to use for remote login. Use with -r.
15
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
16
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
17
+ # -v, --verbose Run in verbose mode.
17
18
 
18
19
  class StartCommand < ListCommand
19
20
 
@@ -41,7 +42,7 @@ module Sunshine
41
42
  # Start specified apps.
42
43
 
43
44
  def start app_names, force=false
44
- status_after_command :start, app_names do |server_app|
45
+ status_after_command :start, app_names, :sudo => false do |server_app|
45
46
 
46
47
  server_app.stop if server_app.running? && force
47
48
  end
@@ -9,10 +9,11 @@ module Sunshine
9
9
  # app_name Name of the application to stop.
10
10
  #
11
11
  # Options:
12
- # -f, --format FORMAT Set the output format (txt, yml, json)
13
- # -u, --user USER User to use for remote login. Use with -r.
14
- # -r, --remote svr1,svr2 Run on one or more remote servers.
15
- # -v, --verbose Run in verbose mode.
12
+ # -f, --format FORMAT Set the output format (txt, yml, json)
13
+ # -u, --user USER User to use for remote login. Use with -r.
14
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
15
+ # -S, --sudo Run remote commands using sudo or sudo -u USER
16
+ # -v, --verbose Run in verbose mode.
16
17
 
17
18
  class StopCommand < ListCommand
18
19
 
@@ -38,7 +39,7 @@ module Sunshine
38
39
  # Stop specified apps.
39
40
 
40
41
  def stop app_names
41
- status_after_command :stop, app_names
42
+ status_after_command :stop, app_names, :sudo => false
42
43
  end
43
44
 
44
45
  ##
@@ -11,6 +11,12 @@ require 'optparse'
11
11
  require 'time'
12
12
  require 'fileutils'
13
13
  require 'tmpdir'
14
+ require 'irb'
15
+
16
+
17
+ # Turn off EOF tracking to be able to prompt on deploy exceptions.
18
+ HighLine.track_eof = false
19
+
14
20
 
15
21
  ##
16
22
  # Main module, used for configuration and running commands.
@@ -19,7 +25,7 @@ module Sunshine
19
25
 
20
26
  ##
21
27
  # Sunshine version.
22
- VERSION = '1.2.0'
28
+ VERSION = '1.2.1'
23
29
 
24
30
  ##
25
31
  # Path to the list of installed sunshine apps.
@@ -36,18 +42,16 @@ module Sunshine
36
42
  ##
37
43
  # Default configuration.
38
44
  DEFAULT_CONFIG = {
39
- 'auto' => false,
45
+ 'interactive' => true,
40
46
  'auto_dependencies' => true,
41
- 'deploy_env' =>
42
- ( ENV['DEPLOY_ENV'] ||
43
- ENV['env'] ||
44
- ENV['RACK_ENV'] ||
45
- ENV['RAILS_ENV'] ||
46
- :development ),
47
+ 'deploy_env' => :development,
48
+ 'exception_behavior' => :revert,
49
+ 'exclude_paths' => [],
47
50
  'level' => 'info',
48
51
  'max_deploy_versions' => 5,
49
52
  'remote_checkouts' => false,
50
53
  'timeout' => 300,
54
+ 'sigint_behavior' => :revert,
51
55
  'web_directory' => '/srv/http'
52
56
  }
53
57
 
@@ -113,11 +117,34 @@ module Sunshine
113
117
 
114
118
 
115
119
  ##
116
- # Should sunshine ever ask for user input? True by default; overridden with
117
- # the -a option.
120
+ # Defines what to do when deploy raises an exception.
121
+ # Supported values are:
122
+ # ::revert: Revert to the previous deploy.
123
+ # ::console: Start an interactive ruby shell within the app's context.
124
+ # ::exit: Stop deploy and exit, leaving deploy in unfinished state.
125
+ # ::prompt: Ask what to do.
126
+ # Defaults to :revert. Overridden in the config.
127
+
128
+ def self.exception_behavior
129
+ @config['exception_behavior']
130
+ end
131
+
132
+
133
+ ##
134
+ # Array of paths or globs that should be excluded from the checkout.
135
+ # Does not work with remote_checkouts enabled.
136
+
137
+ def self.exclude_paths
138
+ @config['exclude_paths']
139
+ end
140
+
141
+
142
+ ##
143
+ # Should sunshine ever ask for user input? True by default.
144
+ # Overridden in the config or with the -a option.
118
145
 
119
146
  def self.interactive?
120
- !@config['auto']
147
+ @config['interactive']
121
148
  end
122
149
 
123
150
 
@@ -155,6 +182,20 @@ module Sunshine
155
182
  end
156
183
 
157
184
 
185
+ ##
186
+ # Defines what to do when sigint is sent during deploys.
187
+ # Supported values are:
188
+ # ::revert: Revert to the previous deploy.
189
+ # ::console: Start an interactive ruby shell within the app's context.
190
+ # ::exit: Stop deploy and exit, leaving deploy in unfinished state.
191
+ # ::prompt: Ask what to do.
192
+ # Defaults to :revert. Overridden in the config.
193
+
194
+ def self.sigint_behavior
195
+ @config['sigint_behavior']
196
+ end
197
+
198
+
158
199
  ##
159
200
  # How long to wait on a command to finish when no output is received.
160
201
  # Defaults to 300 (seconds). Overridden in the config.
@@ -186,42 +227,6 @@ module Sunshine
186
227
  end
187
228
 
188
229
 
189
- ##
190
- # Adds an INT signal trap with its description on the stack.
191
- # Returns a trap_item Array.
192
-
193
- def self.add_trap desc, &block
194
- trap_item = [desc, block]
195
- (@trap_stack ||= []).unshift trap_item
196
- trap_item
197
- end
198
-
199
- add_trap "Disconnecting all remote shells." do
200
- RemoteShell.disconnect_all
201
- end
202
-
203
-
204
- ##
205
- # Call a trap item and display it's message.
206
-
207
- def self.call_trap trap_item
208
- return unless trap_item
209
-
210
- msg, block = trap_item
211
-
212
- logger.info :INT, msg do
213
- block.call
214
- end
215
- end
216
-
217
-
218
- ##
219
- # Remove a trap_item from the stack.
220
-
221
- def self.delete_trap trap_item
222
- @trap_stack.delete trap_item
223
- end
224
-
225
230
 
226
231
  ##
227
232
  # Global value of sudo to use. Returns true, nil, or a username.
@@ -264,6 +269,15 @@ module Sunshine
264
269
  end
265
270
 
266
271
  load_config_file USER_CONFIG_FILE
272
+
273
+ @config['deploy_env'] =
274
+ ENV['DEPLOY_ENV'] ||
275
+ ENV['env'] ||
276
+ ENV['RACK_ENV'] ||
277
+ ENV['RAILS_ENV'] ||
278
+ @config['deploy_env']
279
+
280
+ @config
267
281
  end
268
282
 
269
283
 
@@ -282,15 +296,18 @@ module Sunshine
282
296
  def self.setup new_config={}, reset=false
283
297
  @config = DEFAULT_CONFIG.dup if reset
284
298
 
285
- trap "INT" do
299
+ TrapStack.trap_signal :INT do |msg|
286
300
  $stderr << "\n\n"
287
301
  logger.indent = 0
288
302
  logger.fatal :INT, "Caught INT signal!"
303
+ logger.info :INT, msg
304
+ end
289
305
 
290
- call_trap @trap_stack.shift
291
- exit 1
306
+ TrapStack.add_trap "Disconnecting all remote shells." do
307
+ RemoteShell.disconnect_all
292
308
  end
293
309
 
310
+
294
311
  require_libs(*new_config['require'])
295
312
 
296
313
  config.merge! new_config
@@ -369,6 +386,7 @@ module Sunshine
369
386
 
370
387
 
371
388
  require 'sunshine/exceptions'
389
+ require 'sunshine/trap_stack'
372
390
 
373
391
  require 'sunshine/shell'
374
392
  require 'sunshine/remote_shell'
@@ -176,6 +176,8 @@ module Sunshine
176
176
 
177
177
  @deploy_name = options[:deploy_name] || Time.now.to_i.to_s
178
178
 
179
+ @deploy_env = options[:deploy_env] if options[:deploy_env]
180
+
179
181
  set_deploy_paths options[:root_path]
180
182
 
181
183
  @server_apps = server_apps_from_config options[:remote_shells]
@@ -192,7 +194,7 @@ module Sunshine
192
194
 
193
195
  @post_user_lambdas = []
194
196
 
195
- @has_session = false
197
+ @on_sigint = @on_exception = nil
196
198
  end
197
199
 
198
200
 
@@ -220,7 +222,8 @@ module Sunshine
220
222
 
221
223
 
222
224
  ##
223
- # Check if server apps are connected. Supports any App#find options.
225
+ # Check if all server apps are connected and returns a boolean.
226
+ # Supports any App#find options.
224
227
 
225
228
  def connected? options=nil
226
229
  each options do |server_app|
@@ -231,6 +234,19 @@ module Sunshine
231
234
  end
232
235
 
233
236
 
237
+ ##
238
+ # Check if any server apps are connected and returns a boolean.
239
+ # Supports any App#find options.
240
+
241
+ def any_connected? options=nil
242
+ each options do |server_app|
243
+ return true if server_app.shell.connected?
244
+ end
245
+
246
+ false
247
+ end
248
+
249
+
234
250
  ##
235
251
  # Disconnect server apps. Supports any App#find options.
236
252
 
@@ -247,6 +263,13 @@ module Sunshine
247
263
  # Deploy the application to deploy servers and
248
264
  # call user's post-deploy code. Supports any App#find options.
249
265
  #
266
+ # If the deploy fails or an exception is raised, it will attempt to
267
+ # run the Sunshine.failed_deploy_behavior, which is set to :revert by
268
+ # default. However, this is not true of ssh connection failures.
269
+ #
270
+ # If the deploy is interrupted by a SIGINT, it will attempt to run
271
+ # the Sunshine.sigint_behavior, which is set to :revert by default.
272
+ #
250
273
  # Note: The deploy method will stop the former deploy just before
251
274
  # symlink and the passed block is run.
252
275
  #
@@ -254,28 +277,28 @@ module Sunshine
254
277
  # run App#start.
255
278
 
256
279
  def deploy options=nil
257
- success = false
258
- prev_connection = connected?
259
280
 
260
- deploy_trap = Sunshine.add_trap "Reverting deploy of #{@name}" do
261
- revert! options
262
- start options
263
- disconnect options unless prev_connection
264
- end
281
+ state = {
282
+ :success => false,
283
+ :stopped => false,
284
+ :symlinked => false
285
+ }
265
286
 
266
- Sunshine.logger.info :app, "Beginning deploy of #{@name}"
287
+ Sunshine.logger.info :app, "Beginning #{@name} deploy"
267
288
 
268
- with_session options do
289
+ with_session options do |app|
290
+
291
+ interruptable state do
292
+ raise DeployError, "No servers defined for #{@name}" if
293
+ @server_apps.empty?
269
294
 
270
- with_filter options do |app|
271
295
  make_app_directories
272
296
  checkout_codebase
273
297
 
274
- stop
275
-
276
- symlink_current_dir
298
+ state[:stopped] = true if stop
299
+ state[:symlinked] = true if symlink_current_dir
277
300
 
278
- yield(self) if block_given?
301
+ yield self if block_given?
279
302
 
280
303
  run_post_user_lambdas
281
304
 
@@ -287,29 +310,136 @@ module Sunshine
287
310
 
288
311
  register_as_deployed
289
312
 
290
- success = start :force => true
291
-
292
- remove_old_deploys
293
- success &&= deployed?
313
+ state[:success] = true if start! :force => true
294
314
  end
315
+
316
+ remove_old_deploys if state[:success] rescue
317
+ Sunshine.logger.error :app, "Could not remove old deploys"
318
+
319
+ state[:success] &&= deployed?
295
320
  end
296
321
 
297
- Sunshine.logger.info :app, "Ending deploy of #{@name}"
322
+ Sunshine.logger.info :app, "Finished #{@name} deploy" if state[:success]
323
+ state[:success]
324
+ end
298
325
 
299
- rescue => e
300
- message = "#{e.class}: #{e.message}"
301
326
 
302
- Sunshine.logger.error :app, message do
327
+ ##
328
+ # Handles SIGINTs and exceptions according to rules set by
329
+ # Sunshine.sigint_behavior and Sunshine.exception_behavior
330
+ # or with the override hooks App#on_sigint and App#on_exception.
331
+
332
+ def interruptable options={}
333
+ interrupt_trap =
334
+ TrapStack.add_trap "Interrupted #{@name}" do
335
+ handle_sigint options
336
+ end
337
+
338
+ yield if block_given?
339
+
340
+ rescue => e
341
+ Sunshine.logger.error :app, "#{e.class}: #{e.message}" do
303
342
  Sunshine.logger.error '>>', e.backtrace.join("\n")
304
- revert! options
305
- start options
306
343
  end
307
344
 
345
+ handle_exception e, options
346
+
308
347
  ensure
309
- disconnect options unless prev_connection
310
- Sunshine.delete_trap deploy_trap
348
+ TrapStack.delete_trap interrupt_trap
349
+ end
350
+
351
+
352
+ ##
353
+ # Calls the Apps on_sigint hook or the default Sunshine.sigint_behavior.
311
354
 
312
- success
355
+ def handle_sigint state={}
356
+ return @on_sigint.call(state) if @on_sigint
357
+ handle_interruption Sunshine.sigint_behavior, state
358
+ end
359
+
360
+
361
+ ##
362
+ # Calls the Apps on_exception hook or the default
363
+ # Sunshine.exception_behavior.
364
+
365
+ def handle_exception exception, state={}
366
+ return @on_exception.call(exception, state) if @on_exception
367
+ handle_interruption Sunshine.exception_behavior, state
368
+ end
369
+
370
+
371
+ ##
372
+ # Set this to define the behavior of SIGINT during a deploy.
373
+ # Defines what to do when an INT signal is received when running
374
+ # a proc through App#interruptable. Used primarily to catch SIGINTs
375
+ # during deploys. Passes the block a hash with the state of the deploy:
376
+ #
377
+ # app.on_sigint do |deploy_state_hash|
378
+ # deploy_state_hash
379
+ # #=> {:stopped => true, :symlinked => true, :success => false}
380
+ # end
381
+
382
+ def on_sigint &block
383
+ @on_sigint = block
384
+ end
385
+
386
+
387
+ ##
388
+ # Set this to define the behavior of exceptions during a deploy.
389
+ # Defines what to do when an exception is received when running
390
+ # a proc through App#interruptable. Used primarily to catch exceptions
391
+ # during deploys. Passes the block the exception and a hash with the
392
+ # state of the deploy:
393
+ #
394
+ # app.on_exception do |exception, deploy_state_hash|
395
+ # # do something...
396
+ # end
397
+
398
+ def on_exception &block
399
+ @on_exception = block
400
+ end
401
+
402
+
403
+ ##
404
+ # Handles the behavior of a failed or interrupted deploy.
405
+ # Takes a behavior symbol defining how to handle the interruption
406
+ # and a hash representing the state of the deploy when it was
407
+ # interrupted.
408
+ #
409
+ # Supported bahavior symbols are:
410
+ # ::revert: Revert to previous deploy (default)
411
+ # ::console: Start an interactive console with the app's binding
412
+ # ::exit: Stop deploy and exit
413
+ # ::prompt: Ask what to do
414
+ #
415
+ # The state hash supports the following keys:
416
+ # ::stopped: Was the previous deploy stopped.
417
+ # ::symlinked: Was the new deployed symlinked as the current deploy.
418
+
419
+ def handle_interruption behavior, state={}
420
+ case behavior
421
+
422
+ when :exit
423
+ Sunshine.exit 1, "Error: Deploy of #{@name} failed"
424
+
425
+ when :revert
426
+ revert! if state[:symlinked]
427
+ start if state[:stopped]
428
+
429
+ when Sunshine.interactive? && :console
430
+ self.console!
431
+
432
+ when Sunshine.interactive? && :prompt
433
+ Sunshine.shell.choose do |menu|
434
+ menu.prompt = "Deploy interrupted:"
435
+ menu.choice(:revert) { handle_interruption :revert, state }
436
+ menu.choice(:console){ handle_interruption :console, state }
437
+ menu.choice(:exit) { handle_interruption :exit, state }
438
+ end
439
+
440
+ else
441
+ raise DeployError, "Deploy of #{@name} was interrupted."
442
+ end
313
443
  end
314
444
 
315
445
 
@@ -343,7 +473,7 @@ module Sunshine
343
473
  # application and job name, including previous deploys.
344
474
 
345
475
  def add_to_crontab name, cronjob, options=nil
346
- with_server_apps options do |server_app|
476
+ each options do |server_app|
347
477
  server_app.crontab[name] << cronjob
348
478
  end
349
479
  end
@@ -357,7 +487,7 @@ module Sunshine
357
487
  # application and job name, including previous deploys.
358
488
 
359
489
  def cronjob name, cronjob, options=nil
360
- with_server_apps options do |server_app|
490
+ each options do |server_app|
361
491
  server_app.crontab[name] = cronjob
362
492
  end
363
493
  end
@@ -369,7 +499,7 @@ module Sunshine
369
499
  # add_to_script :start, "start_mail", :role => :mail
370
500
 
371
501
  def add_to_script name, script, options=nil
372
- with_server_apps options do |server_app|
502
+ each options do |server_app|
373
503
  server_app.scripts[name] << script
374
504
  end
375
505
  end
@@ -432,13 +562,44 @@ module Sunshine
432
562
  end
433
563
 
434
564
 
565
+ ##
566
+ # Starts an IRB console with the instance's binding.
567
+
568
+ def console!
569
+ IRB.setup nil unless defined?(IRB::UnrecognizedSwitch)
570
+
571
+ workspace = IRB::WorkSpace.new binding
572
+ irb = IRB::Irb.new workspace
573
+
574
+ irb.context.irb_name = "sunshine(#{@name})"
575
+ irb.context.prompt_c = "%N:%03n:%i* "
576
+ irb.context.prompt_i = "%N:%03n:%i> "
577
+ irb.context.prompt_n = "%N:%03n:%i> "
578
+
579
+ IRB.class_eval do
580
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
581
+ @CONF[:MAIN_CONTEXT] = irb.context
582
+ end
583
+
584
+ #TODO: remove sigint trap when irb session is closed
585
+ #trap("INT") do
586
+ # irb.signal_handle
587
+ #end
588
+
589
+ catch(:IRB_EXIT) do
590
+ irb.eval_input
591
+ end
592
+ end
593
+
594
+
435
595
  ##
436
596
  # Checks out the app's codebase to one or all deploy servers.
437
597
  # Supports all App#find options, plus:
438
598
  # :copy:: Bool - Checkout locally and rsync; defaults to false.
439
599
 
440
600
  def checkout_codebase options=nil
441
- copy_option = options && options.has_key?(:copy) && options[:copy]
601
+ copy_option = options[:copy] if options
602
+ exclude = options.delete(:exclude) if options
442
603
 
443
604
  if @remote_checkout && !copy_option
444
605
  with_server_apps options,
@@ -451,13 +612,13 @@ module Sunshine
451
612
  tmp_path = File.join Sunshine::TMP_DIR, "#{@name}_checkout"
452
613
  scm_info = @repo.checkout_to tmp_path
453
614
 
615
+ scm_info[:exclude] =
616
+ [Sunshine.exclude_paths, exclude].flatten.compact
617
+
454
618
  with_server_apps options,
455
619
  :send => [:upload_codebase, tmp_path, scm_info]
456
620
  end
457
621
  end
458
-
459
- rescue => e
460
- raise CriticalDeployError, e
461
622
  end
462
623
 
463
624
 
@@ -612,9 +773,6 @@ module Sunshine
612
773
  with_server_apps options,
613
774
  :msg => "Creating #{@name} directories",
614
775
  :send => :make_app_directories
615
-
616
- rescue => e
617
- raise FatalDeployError, e
618
776
  end
619
777
 
620
778
 
@@ -692,6 +850,18 @@ module Sunshine
692
850
  end
693
851
 
694
852
 
853
+ ##
854
+ # Run the restart script of a deployed app on the specified
855
+ # deploy servers. Raises an exception on failure.
856
+ # Post-deploy only.
857
+
858
+ def restart! options=nil
859
+ with_server_apps options,
860
+ :msg => "Running restart script",
861
+ :send => :restart!
862
+ end
863
+
864
+
695
865
  ##
696
866
  # Runs bundler on deploy servers.
697
867
 
@@ -699,9 +869,6 @@ module Sunshine
699
869
  with_server_apps options,
700
870
  :msg => "Running Bundler",
701
871
  :send => [:run_bundler, options]
702
-
703
- rescue => e
704
- raise CriticalDeployError, e
705
872
  end
706
873
 
707
874
 
@@ -712,9 +879,6 @@ module Sunshine
712
879
  with_server_apps options,
713
880
  :msg => "Running GemInstaller",
714
881
  :send => [:run_geminstaller, options]
715
-
716
- rescue => e
717
- raise CriticalDeployError, e
718
882
  end
719
883
 
720
884
 
@@ -741,6 +905,18 @@ module Sunshine
741
905
  end
742
906
 
743
907
 
908
+ ##
909
+ # Run the given script of a deployed app on the specified
910
+ # deploy servers. Raises an exception on failure.
911
+ # Post-deploy only.
912
+
913
+ def run_script! name, options=nil
914
+ with_server_apps options,
915
+ :msg => "Running #{name} script",
916
+ :send => [:run_script!, name, options]
917
+ end
918
+
919
+
744
920
  ##
745
921
  # Run a sass task on any or all deploy servers.
746
922
 
@@ -763,6 +939,8 @@ module Sunshine
763
939
  @shell_env.merge!(env_hash)
764
940
 
765
941
  with_server_apps :all,
942
+ :no_threads => true,
943
+ :no_session => true,
766
944
  :msg => "Shell env: #{@shell_env.inspect}" do |server_app|
767
945
  server_app.shell_env.merge!(@shell_env)
768
946
  end
@@ -783,6 +961,18 @@ module Sunshine
783
961
  end
784
962
 
785
963
 
964
+ ##
965
+ # Run the start script of a deployed app on the specified
966
+ # deploy servers. Raises an exception on failure.
967
+ # Post-deploy only.
968
+
969
+ def start! options=nil
970
+ with_server_apps options,
971
+ :msg => "Running start script",
972
+ :send => [:start!, options]
973
+ end
974
+
975
+
786
976
  ##
787
977
  # Get a hash of which deploy server apps are :running or :down.
788
978
  # Post-deploy only.
@@ -810,12 +1000,26 @@ module Sunshine
810
1000
  end
811
1001
 
812
1002
 
1003
+ ##
1004
+ # Run the stop script of a deployed app on the specified
1005
+ # deploy servers. Raises an exception on failure.
1006
+ # Post-deploy only.
1007
+
1008
+ def stop! options=nil
1009
+ with_server_apps options,
1010
+ :msg => "Running stop script",
1011
+ :send => :stop!
1012
+ end
1013
+
1014
+
813
1015
  ##
814
1016
  # Use sudo on deploy servers. Set to true/false, or
815
1017
  # a username to use 'sudo -u'.
816
1018
 
817
1019
  def sudo=(value)
818
1020
  with_server_apps :all,
1021
+ :no_threads => true,
1022
+ :no_session => true,
819
1023
  :msg => "Using sudo = #{value.inspect}" do |server_app|
820
1024
  server_app.shell.sudo = value
821
1025
  end
@@ -831,9 +1035,6 @@ module Sunshine
831
1035
  with_server_apps options,
832
1036
  :msg => "Symlinking #{@checkout_path} -> #{@current_path}",
833
1037
  :send => :symlink_current_dir
834
-
835
- rescue => e
836
- raise CriticalDeployError, e
837
1038
  end
838
1039
 
839
1040
 
@@ -916,6 +1117,7 @@ module Sunshine
916
1117
  # a session to avoid multiple ssh login prompts. Supports all App#find
917
1118
  # options, plus:
918
1119
  # :no_threads:: bool - disable threaded execution
1120
+ # :no_session:: bool - disable auto session creation
919
1121
  # :msg:: "some message" - log message
920
1122
  #
921
1123
  # app.with_server_apps :all, :msg => "doing something" do |server_app|
@@ -925,12 +1127,16 @@ module Sunshine
925
1127
  # app.with_server_apps :role => :db, :user => "bob" do |server_app|
926
1128
  # # do something here
927
1129
  # end
1130
+ #
1131
+ # Note: App#with_server_apps calls App#with_session. If you do not need
1132
+ # or want a server connection you can pass :no_session.
928
1133
 
929
1134
  def with_server_apps search_options, options={}
930
1135
  options = search_options.merge options if Hash === search_options
931
1136
 
932
1137
  message = options[:msg]
933
1138
  method = options[:no_threads] ? :each : :threaded_each
1139
+ auto_session = !options[:no_session]
934
1140
 
935
1141
  block = lambda do
936
1142
  send(method, search_options) do |server_app|
@@ -945,7 +1151,7 @@ module Sunshine
945
1151
  end
946
1152
 
947
1153
 
948
- with_session options do
1154
+ msg_block = lambda do
949
1155
  if message
950
1156
  Sunshine.logger.info(:app, message, &block)
951
1157
 
@@ -953,6 +1159,8 @@ module Sunshine
953
1159
  block.call
954
1160
  end
955
1161
  end
1162
+
1163
+ auto_session ? with_session(&msg_block) : msg_block.call
956
1164
  end
957
1165
 
958
1166
 
@@ -960,16 +1168,22 @@ module Sunshine
960
1168
  # Runs block ensuring a connection to remote_shells.
961
1169
  # Connecting and disconnecting will be ignored if a session
962
1170
  # already exists. Supports all App#find options.
1171
+ #
1172
+ # Ensures that servers are disconnected after the block is run
1173
+ # if servers were not previously connected.
963
1174
 
964
1175
  def with_session options=nil
1176
+ with_filter options do
1177
+ prev_connection = connected?
965
1178
 
966
- prev_connection = connected?(options)
967
- connect options unless prev_connection
1179
+ begin
1180
+ connect unless prev_connection
1181
+ yield self
968
1182
 
969
- yield
970
-
971
- ensure
972
- disconnect options unless prev_connection
1183
+ ensure
1184
+ disconnect unless prev_connection
1185
+ end
1186
+ end
973
1187
  end
974
1188
 
975
1189