minmb-capistrano 2.15.4

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.
Files changed (119) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG +1170 -0
  4. data/Gemfile +13 -0
  5. data/README.md +94 -0
  6. data/Rakefile +11 -0
  7. data/bin/cap +4 -0
  8. data/bin/capify +92 -0
  9. data/capistrano.gemspec +40 -0
  10. data/lib/capistrano.rb +5 -0
  11. data/lib/capistrano/callback.rb +45 -0
  12. data/lib/capistrano/cli.rb +47 -0
  13. data/lib/capistrano/cli/execute.rb +85 -0
  14. data/lib/capistrano/cli/help.rb +125 -0
  15. data/lib/capistrano/cli/help.txt +81 -0
  16. data/lib/capistrano/cli/options.rb +243 -0
  17. data/lib/capistrano/cli/ui.rb +40 -0
  18. data/lib/capistrano/command.rb +303 -0
  19. data/lib/capistrano/configuration.rb +57 -0
  20. data/lib/capistrano/configuration/actions/file_transfer.rb +50 -0
  21. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  22. data/lib/capistrano/configuration/actions/invocation.rb +329 -0
  23. data/lib/capistrano/configuration/alias_task.rb +26 -0
  24. data/lib/capistrano/configuration/callbacks.rb +147 -0
  25. data/lib/capistrano/configuration/connections.rb +237 -0
  26. data/lib/capistrano/configuration/execution.rb +142 -0
  27. data/lib/capistrano/configuration/loading.rb +205 -0
  28. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  29. data/lib/capistrano/configuration/namespaces.rb +223 -0
  30. data/lib/capistrano/configuration/roles.rb +77 -0
  31. data/lib/capistrano/configuration/servers.rb +116 -0
  32. data/lib/capistrano/configuration/variables.rb +127 -0
  33. data/lib/capistrano/errors.rb +19 -0
  34. data/lib/capistrano/ext/multistage.rb +64 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +57 -0
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +166 -0
  39. data/lib/capistrano/processable.rb +57 -0
  40. data/lib/capistrano/recipes/compat.rb +32 -0
  41. data/lib/capistrano/recipes/deploy.rb +625 -0
  42. data/lib/capistrano/recipes/deploy/assets.rb +201 -0
  43. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  44. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  45. data/lib/capistrano/recipes/deploy/remote_dependency.rb +117 -0
  46. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  47. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  48. data/lib/capistrano/recipes/deploy/scm/base.rb +200 -0
  49. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  50. data/lib/capistrano/recipes/deploy/scm/cvs.rb +153 -0
  51. data/lib/capistrano/recipes/deploy/scm/darcs.rb +96 -0
  52. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  53. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  54. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  55. data/lib/capistrano/recipes/deploy/scm/perforce.rb +152 -0
  56. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  57. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  58. data/lib/capistrano/recipes/deploy/strategy/base.rb +92 -0
  59. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  60. data/lib/capistrano/recipes/deploy/strategy/copy.rb +338 -0
  61. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  62. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  63. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +57 -0
  64. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  65. data/lib/capistrano/recipes/standard.rb +37 -0
  66. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  67. data/lib/capistrano/role.rb +102 -0
  68. data/lib/capistrano/server_definition.rb +56 -0
  69. data/lib/capistrano/shell.rb +265 -0
  70. data/lib/capistrano/ssh.rb +95 -0
  71. data/lib/capistrano/task_definition.rb +77 -0
  72. data/lib/capistrano/transfer.rb +218 -0
  73. data/lib/capistrano/version.rb +11 -0
  74. data/test/cli/execute_test.rb +132 -0
  75. data/test/cli/help_test.rb +165 -0
  76. data/test/cli/options_test.rb +329 -0
  77. data/test/cli/ui_test.rb +28 -0
  78. data/test/cli_test.rb +17 -0
  79. data/test/command_test.rb +322 -0
  80. data/test/configuration/actions/file_transfer_test.rb +61 -0
  81. data/test/configuration/actions/inspect_test.rb +76 -0
  82. data/test/configuration/actions/invocation_test.rb +288 -0
  83. data/test/configuration/alias_task_test.rb +118 -0
  84. data/test/configuration/callbacks_test.rb +201 -0
  85. data/test/configuration/connections_test.rb +439 -0
  86. data/test/configuration/execution_test.rb +175 -0
  87. data/test/configuration/loading_test.rb +148 -0
  88. data/test/configuration/namespace_dsl_test.rb +332 -0
  89. data/test/configuration/roles_test.rb +157 -0
  90. data/test/configuration/servers_test.rb +183 -0
  91. data/test/configuration/variables_test.rb +190 -0
  92. data/test/configuration_test.rb +77 -0
  93. data/test/deploy/local_dependency_test.rb +76 -0
  94. data/test/deploy/remote_dependency_test.rb +146 -0
  95. data/test/deploy/scm/accurev_test.rb +23 -0
  96. data/test/deploy/scm/base_test.rb +55 -0
  97. data/test/deploy/scm/bzr_test.rb +51 -0
  98. data/test/deploy/scm/darcs_test.rb +37 -0
  99. data/test/deploy/scm/git_test.rb +221 -0
  100. data/test/deploy/scm/mercurial_test.rb +134 -0
  101. data/test/deploy/scm/none_test.rb +35 -0
  102. data/test/deploy/scm/perforce_test.rb +23 -0
  103. data/test/deploy/scm/subversion_test.rb +40 -0
  104. data/test/deploy/strategy/copy_test.rb +360 -0
  105. data/test/extensions_test.rb +69 -0
  106. data/test/fixtures/cli_integration.rb +5 -0
  107. data/test/fixtures/config.rb +5 -0
  108. data/test/fixtures/custom.rb +3 -0
  109. data/test/logger_formatting_test.rb +149 -0
  110. data/test/logger_test.rb +134 -0
  111. data/test/recipes_test.rb +25 -0
  112. data/test/role_test.rb +11 -0
  113. data/test/server_definition_test.rb +121 -0
  114. data/test/shell_test.rb +96 -0
  115. data/test/ssh_test.rb +113 -0
  116. data/test/task_definition_test.rb +117 -0
  117. data/test/transfer_test.rb +168 -0
  118. data/test/utils.rb +37 -0
  119. metadata +316 -0
@@ -0,0 +1,20 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # Implements the deployment strategy which does an SCM export on each
8
+ # target host.
9
+ class Export < Remote
10
+ protected
11
+
12
+ # Returns the SCM's export command for the revision to deploy.
13
+ def command
14
+ @command ||= source.export(revision, configuration[:release_path])
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ require 'capistrano/recipes/deploy/strategy/base'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # An abstract superclass, which forms the base for all deployment
8
+ # strategies which work by grabbing the code from the repository directly
9
+ # from remote host. This includes deploying by checkout (the default),
10
+ # and deploying by export.
11
+ class Remote < Base
12
+ # Executes the SCM command for this strategy and writes the REVISION
13
+ # mark file to each host.
14
+ def deploy!
15
+ scm_run "#{command} && #{mark}"
16
+ end
17
+
18
+ def check!
19
+ super.check do |d|
20
+ d.remote.command(source.command)
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ # Runs the given command, filtering output back through the
27
+ # #handle_data filter of the SCM implementation.
28
+ def scm_run(command)
29
+ run(command) do |ch,stream,text|
30
+ ch[:state] ||= { :channel => ch }
31
+ output = source.handle_data(ch[:state], stream, text)
32
+ ch.send_data(output) if output
33
+ end
34
+ end
35
+
36
+ # An abstract method which must be overridden in subclasses, to
37
+ # return the actual SCM command(s) which must be executed on each
38
+ # target host in order to perform the deployment.
39
+ def command
40
+ raise NotImplementedError, "`command' is not implemented by #{self.class.name}"
41
+ end
42
+
43
+ # Returns the command which will write the identifier of the
44
+ # revision being deployed to the REVISION file on each host.
45
+ def mark
46
+ "(echo #{revision} > #{configuration[:release_path]}/REVISION)"
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # Implements the deployment strategy that keeps a cached checkout of
8
+ # the source code on each remote server. Each deploy simply updates the
9
+ # cached checkout, and then does a copy from the cached copy to the
10
+ # final deployment location.
11
+ class RemoteCache < Remote
12
+ # Executes the SCM command for this strategy and writes the REVISION
13
+ # mark file to each host.
14
+ def deploy!
15
+ update_repository_cache
16
+ copy_repository_cache
17
+ end
18
+
19
+ def check!
20
+ super.check do |d|
21
+ d.remote.command("rsync") unless copy_exclude.empty?
22
+ d.remote.writable(shared_path)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def repository_cache
29
+ File.join(shared_path, configuration[:repository_cache] || "cached-copy")
30
+ end
31
+
32
+ def update_repository_cache
33
+ logger.trace "updating the cached checkout on all servers"
34
+ command = "if [ -d #{repository_cache} ]; then " +
35
+ "#{source.sync(revision, repository_cache)}; " +
36
+ "else #{source.checkout(revision, repository_cache)}; fi"
37
+ scm_run(command)
38
+ end
39
+
40
+ def copy_repository_cache
41
+ logger.trace "copying the cached version to #{configuration[:release_path]}"
42
+ if copy_exclude.empty?
43
+ run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
44
+ else
45
+ exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
46
+ run "rsync -lrpt #{exclusions} #{repository_cache}/ #{configuration[:release_path]} && #{mark}"
47
+ end
48
+ end
49
+
50
+ def copy_exclude
51
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote_cache'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+ class UnsharedRemoteCache < RemoteCache
7
+ def check!
8
+ super.check do |d|
9
+ d.remote.writable(repository_cache)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def repository_cache
16
+ configuration[:repository_cache]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ desc <<-DESC
2
+ Invoke a single command on the remote servers. This is useful for performing \
3
+ one-off commands that may not require a full task to be written for them. \
4
+ Simply specify the command to execute via the COMMAND environment variable. \
5
+ To execute the command only on certain roles, specify the ROLES environment \
6
+ variable as a comma-delimited list of role names. Alternatively, you can \
7
+ specify the HOSTS environment variable as a comma-delimited list of hostnames \
8
+ to execute the task on those hosts, explicitly. Lastly, if you want to \
9
+ execute the command via sudo, specify a non-empty value for the SUDO \
10
+ environment variable.
11
+
12
+ Sample usage:
13
+
14
+ $ cap COMMAND=uptime HOSTS=foo.capistano.test invoke
15
+ $ cap ROLES=app,web SUDO=1 COMMAND="tail -f /var/log/messages" invoke
16
+ DESC
17
+ task :invoke do
18
+ command = ENV["COMMAND"] || ""
19
+ abort "Please specify a command to execute on the remote servers (via the COMMAND environment variable)" if command.empty?
20
+ method = ENV["SUDO"] ? :sudo : :run
21
+ invoke_command(command, :via => method)
22
+ end
23
+
24
+ desc <<-DESC
25
+ Begin an interactive Capistrano session. This gives you an interactive \
26
+ terminal from which to execute tasks and commands on all of your servers. \
27
+ (This is still an experimental feature, and is subject to change without \
28
+ notice!)
29
+
30
+ Sample usage:
31
+
32
+ $ cap shell
33
+ DESC
34
+ task :shell do
35
+ require 'capistrano/shell'
36
+ Capistrano::Shell.run(self)
37
+ end
@@ -0,0 +1,53 @@
1
+
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+
7
+ <head>
8
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
9
+ <title>System down for maintenance</title>
10
+
11
+ <style type="text/css">
12
+ div.outer {
13
+ position: absolute;
14
+ left: 50%;
15
+ top: 50%;
16
+ width: 500px;
17
+ height: 300px;
18
+ margin-left: -260px;
19
+ margin-top: -150px;
20
+ }
21
+
22
+ .DialogBody {
23
+ margin: 0;
24
+ padding: 10px;
25
+ text-align: left;
26
+ border: 1px solid #ccc;
27
+ border-right: 1px solid #999;
28
+ border-bottom: 1px solid #999;
29
+ background-color: #fff;
30
+ }
31
+
32
+ body { background-color: #fff; }
33
+ </style>
34
+ </head>
35
+
36
+ <body>
37
+
38
+ <div class="outer">
39
+ <div class="DialogBody" style="text-align: center;">
40
+ <div style="text-align: center; width: 200px; margin: 0 auto;">
41
+ <p style="color: red; font-size: 16px; line-height: 20px;">
42
+ The system is down for <%= reason ? reason : "maintenance" %>
43
+ as of <%= Time.now.strftime("%H:%M %Z") %>.
44
+ </p>
45
+ <p style="color: #666;">
46
+ It'll be back <%= deadline ? deadline : "shortly" %>.
47
+ </p>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ </body>
53
+ </html>
@@ -0,0 +1,102 @@
1
+ module Capistrano
2
+ class Role
3
+ include Enumerable
4
+
5
+ def initialize(*list)
6
+ @static_servers = []
7
+ @dynamic_servers = []
8
+ push(*list)
9
+ end
10
+
11
+ def each(&block)
12
+ servers.each &block
13
+ end
14
+
15
+ def push(*list)
16
+ options = list.last.is_a?(Hash) ? list.pop : {}
17
+ list.each do |item|
18
+ if item.respond_to?(:call)
19
+ @dynamic_servers << DynamicServerList.new(item, options)
20
+ else
21
+ @static_servers << self.class.wrap_server(item, options)
22
+ end
23
+ end
24
+ end
25
+ alias_method :<<, :push
26
+
27
+ def servers
28
+ @static_servers + dynamic_servers
29
+ end
30
+ alias_method :to_ary, :servers
31
+
32
+ def empty?
33
+ servers.empty?
34
+ end
35
+
36
+ def clear
37
+ @dynamic_servers.clear
38
+ @static_servers.clear
39
+ end
40
+
41
+ def include?(server)
42
+ servers.include?(server)
43
+ end
44
+
45
+ protected
46
+
47
+ # This is the combination of a block, a hash of options, and a cached value.
48
+ class DynamicServerList
49
+ def initialize (block, options)
50
+ @block = block
51
+ @options = options
52
+ @cached = []
53
+ @is_cached = false
54
+ end
55
+
56
+ # Convert to a list of ServerDefinitions
57
+ def to_ary
58
+ unless @is_cached
59
+ @cached = Role::wrap_list(@block.call(@options), @options)
60
+ @is_cached = true
61
+ end
62
+ @cached
63
+ end
64
+
65
+ # Clear the cached value
66
+ def reset!
67
+ @cached.clear
68
+ @is_cached = false
69
+ end
70
+ end
71
+
72
+ # Attribute reader for the cached results of executing the blocks in turn
73
+ def dynamic_servers
74
+ @dynamic_servers.inject([]) { |list, item| list.concat item }
75
+ end
76
+
77
+ # Wraps a string in a ServerDefinition, if it isn't already.
78
+ # This and wrap_list should probably go in ServerDefinition in some form.
79
+ def self.wrap_server (item, options)
80
+ item.is_a?(ServerDefinition) ? item : ServerDefinition.new(item, options)
81
+ end
82
+
83
+ # Turns a list, or something resembling a list, into a properly-formatted
84
+ # ServerDefinition list. Keep an eye on this one -- it's entirely too
85
+ # magical for its own good. In particular, if ServerDefinition ever inherits
86
+ # from Array, this will break.
87
+ def self.wrap_list (*list)
88
+ options = list.last.is_a?(Hash) ? list.pop : {}
89
+ if list.length == 1
90
+ if list.first.nil?
91
+ return []
92
+ elsif list.first.is_a?(Array)
93
+ list = list.first
94
+ end
95
+ end
96
+ options.merge! list.pop if list.last.is_a?(Hash)
97
+ list.map do |item|
98
+ self.wrap_server item, options
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,56 @@
1
+ module Capistrano
2
+ class ServerDefinition
3
+ include Comparable
4
+
5
+ attr_reader :host
6
+ attr_reader :user
7
+ attr_reader :port
8
+ attr_reader :options
9
+
10
+ # The default user name to use when a user name is not explicitly provided
11
+ def self.default_user
12
+ ENV['USER'] || ENV['USERNAME'] || "not-specified"
13
+ end
14
+
15
+ def initialize(string, options={})
16
+ @user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
17
+
18
+ @options = options.dup
19
+ user_opt, port_opt = @options.delete(:user), @options.delete(:port)
20
+
21
+ @user ||= user_opt
22
+ @port ||= port_opt
23
+
24
+ @port = @port.to_i if @port
25
+ end
26
+
27
+ def <=>(server)
28
+ [host, port, user] <=> [server.host, server.port, server.user]
29
+ end
30
+
31
+ # Redefined, so that Array#uniq will work to remove duplicate server
32
+ # definitions, based solely on their host names.
33
+ def eql?(server)
34
+ host == server.host &&
35
+ user == server.user &&
36
+ port == server.port
37
+ end
38
+
39
+ alias :== :eql?
40
+
41
+ # Redefined, so that Array#uniq will work to remove duplicate server
42
+ # definitions, based on their connection information.
43
+ def hash
44
+ @hash ||= [host, user, port].hash
45
+ end
46
+
47
+ def to_s
48
+ @to_s ||= begin
49
+ s = host
50
+ s = "#{user}@#{s}" if user
51
+ s = "#{s}:#{port}" if port && port != 22
52
+ s
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,265 @@
1
+ require 'thread'
2
+ require 'capistrano/processable'
3
+
4
+ module Capistrano
5
+ # The Capistrano::Shell class is the guts of the "shell" task. It implements
6
+ # an interactive REPL interface that users can employ to execute tasks and
7
+ # commands. It makes for a GREAT way to monitor systems, and perform quick
8
+ # maintenance on one or more machines.
9
+ class Shell
10
+ include Processable
11
+
12
+ # A Readline replacement for platforms where readline is either
13
+ # unavailable, or has not been installed.
14
+ class ReadlineFallback #:nodoc:
15
+ HISTORY = []
16
+
17
+ def self.readline(prompt)
18
+ STDOUT.print(prompt)
19
+ STDOUT.flush
20
+ STDIN.gets
21
+ end
22
+ end
23
+
24
+ # The configuration instance employed by this shell
25
+ attr_reader :configuration
26
+
27
+ # Instantiate a new shell and begin executing it immediately.
28
+ def self.run(config)
29
+ new(config).run!
30
+ end
31
+
32
+ # Instantiate a new shell
33
+ def initialize(config)
34
+ @configuration = config
35
+ end
36
+
37
+ # Start the shell running. This method will block until the shell
38
+ # terminates.
39
+ def run!
40
+ setup
41
+
42
+ puts <<-INTRO
43
+ ====================================================================
44
+ Welcome to the interactive Capistrano shell! This is an experimental
45
+ feature, and is liable to change in future releases. Type 'help' for
46
+ a summary of how to use the shell.
47
+ --------------------------------------------------------------------
48
+ INTRO
49
+
50
+ loop do
51
+ break if !read_and_execute
52
+ end
53
+
54
+ @bgthread.kill
55
+ end
56
+
57
+ def read_and_execute
58
+ command = read_line
59
+
60
+ case command
61
+ when "?", "help" then help
62
+ when "quit", "exit" then
63
+ puts "exiting"
64
+ return false
65
+ when /^set -(\w)\s*(\S+)/
66
+ set_option($1, $2)
67
+ when /^set :(.*)\s+(.*)/
68
+ configuration.set($1.to_sym, $2)
69
+ puts "updated :#{$1} to #{$2}"
70
+ when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
71
+ process_command($1, $2, $3)
72
+ else
73
+ raise "eh?"
74
+ end
75
+
76
+ return true
77
+ end
78
+
79
+ private
80
+
81
+ # Present the prompt and read a single line from the console. It also
82
+ # detects ^D and returns "exit" in that case. Adds the input to the
83
+ # history, unless the input is empty. Loops repeatedly until a non-empty
84
+ # line is input.
85
+ def read_line
86
+ loop do
87
+ command = reader.readline("cap> ")
88
+
89
+ if command.nil?
90
+ command = "exit"
91
+ puts(command)
92
+ else
93
+ command.strip!
94
+ end
95
+
96
+ unless command.empty?
97
+ reader::HISTORY << command
98
+ return command
99
+ end
100
+ end
101
+ end
102
+
103
+ # Display a verbose help message.
104
+ def help
105
+ puts <<-HELP
106
+ --- HELP! ---------------------------------------------------
107
+ "Get me out of this thing. I just want to quit."
108
+ -> Easy enough. Just type "exit", or "quit". Or press ctrl-D.
109
+
110
+ "I want to execute a command on all servers."
111
+ -> Just type the command, and press enter. It will be passed,
112
+ verbatim, to all defined servers.
113
+
114
+ "What if I only want it to execute on a subset of them?"
115
+ -> No problem, just specify the list of servers, separated by
116
+ commas, before the command, with the `on' keyword:
117
+
118
+ cap> on app1.foo.com,app2.foo.com echo ping
119
+
120
+ "Nice, but can I specify the servers by role?"
121
+ -> You sure can. Just use the `with' keyword, followed by the
122
+ comma-delimited list of role names:
123
+
124
+ cap> with app,db echo ping
125
+
126
+ "Can I execute a Capistrano task from within this shell?"
127
+ -> Yup. Just prefix the task with an exclamation mark:
128
+
129
+ cap> !deploy
130
+ HELP
131
+ end
132
+
133
+ # Determine which servers the given task requires a connection to, and
134
+ # establish connections to them if necessary. Return the list of
135
+ # servers (names).
136
+ def connect(task)
137
+ servers = configuration.find_servers_for_task(task)
138
+ needing_connections = servers - configuration.sessions.keys
139
+ unless needing_connections.empty?
140
+ puts "[establishing connection(s) to #{needing_connections.join(', ')}]"
141
+ configuration.establish_connections_to(needing_connections)
142
+ end
143
+ servers
144
+ end
145
+
146
+ # Execute the given command. If the command is prefixed by an exclamation
147
+ # mark, it is assumed to refer to another capistrano task, which will
148
+ # be invoked. Otherwise, it is executed as a command on all associated
149
+ # servers.
150
+ def exec(command)
151
+ @mutex.synchronize do
152
+ if command[0] == ?!
153
+ exec_tasks(command[1..-1].split)
154
+ else
155
+ servers = connect(configuration.current_task)
156
+ exec_command(command, servers)
157
+ end
158
+ end
159
+ ensure
160
+ STDOUT.flush
161
+ end
162
+
163
+ # Given an array of task names, invoke them in sequence.
164
+ def exec_tasks(list)
165
+ list.each do |task_name|
166
+ task = configuration.find_task(task_name)
167
+ raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task
168
+ connect(task)
169
+ configuration.execute_task(task)
170
+ end
171
+ rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
172
+ warn "error: #{error.message}"
173
+ end
174
+
175
+ # Execute a command on the given list of servers.
176
+ def exec_command(command, servers)
177
+ command = command.gsub(/^(\s*)sudo\b|([|;&])\s*sudo\b/, "\\0 -p '#{configuration.sudo_prompt}'")
178
+ processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
179
+ sessions = servers.map { |server| configuration.sessions[server] }
180
+ options = configuration.add_default_command_options({})
181
+ cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor)
182
+ previous = trap("INT") { cmd.stop! }
183
+ cmd.process!
184
+ rescue Capistrano::Error => error
185
+ warn "error: #{error.message}"
186
+ ensure
187
+ trap("INT", previous)
188
+ end
189
+
190
+ # Return the object that will be used to query input from the console.
191
+ # The returned object will quack (more or less) like Readline.
192
+ def reader
193
+ @reader ||= begin
194
+ require 'readline'
195
+ Readline
196
+ rescue LoadError
197
+ ReadlineFallback
198
+ end
199
+ end
200
+
201
+ # Prepare every little thing for the shell. Starts the background
202
+ # thread and generally gets things ready for the REPL.
203
+ def setup
204
+ configuration.logger.level = Capistrano::Logger::INFO
205
+ wait_for = 0.1
206
+
207
+ @mutex = Mutex.new
208
+ @bgthread = Thread.new do
209
+ loop do
210
+ ret = @mutex.synchronize { process_iteration(wait_for) }
211
+ sleep wait_for if !ret
212
+ end
213
+ end
214
+ end
215
+
216
+ # Set the given option to +value+.
217
+ def set_option(opt, value)
218
+ case opt
219
+ when "v" then
220
+ puts "setting log verbosity to #{value.to_i}"
221
+ configuration.logger.level = value.to_i
222
+ when "o" then
223
+ case value
224
+ when "vi" then
225
+ puts "using vi edit mode"
226
+ reader.vi_editing_mode
227
+ when "emacs" then
228
+ puts "using emacs edit mode"
229
+ reader.emacs_editing_mode
230
+ else
231
+ puts "unknown -o option #{value.inspect}"
232
+ end
233
+ else
234
+ puts "unknown setting #{opt.inspect}"
235
+ end
236
+ end
237
+
238
+ # Process a command. Interprets the scope_type (must be nil, "with", or
239
+ # "on") and the command. If no command is given, then the scope is made
240
+ # effective for all subsequent commands. If the scope value is "all",
241
+ # then the scope is unrestricted.
242
+ def process_command(scope_type, scope_value, command)
243
+ env_var = case scope_type
244
+ when "with" then "ROLES"
245
+ when "on" then "HOSTS"
246
+ end
247
+
248
+ old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
249
+ if command
250
+ begin
251
+ exec(command)
252
+ ensure
253
+ ENV[env_var] = old_var if env_var
254
+ end
255
+ else
256
+ puts "scoping #{scope_type} #{scope_value}"
257
+ end
258
+ end
259
+
260
+ # All open sessions, needed to satisfy the Command::Processable include
261
+ def sessions
262
+ configuration.sessions.values
263
+ end
264
+ end
265
+ end