sunshine 1.2.0 → 1.2.1

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.
@@ -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