capistrano-edge 2.5.6

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 (109) hide show
  1. data/CHANGELOG.rdoc +770 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +66 -0
  4. data/Rakefile +35 -0
  5. data/bin/cap +4 -0
  6. data/bin/capify +95 -0
  7. data/capistrano.gemspec +51 -0
  8. data/examples/sample.rb +14 -0
  9. data/lib/capistrano.rb +2 -0
  10. data/lib/capistrano/callback.rb +45 -0
  11. data/lib/capistrano/cli.rb +47 -0
  12. data/lib/capistrano/cli/execute.rb +84 -0
  13. data/lib/capistrano/cli/help.rb +125 -0
  14. data/lib/capistrano/cli/help.txt +75 -0
  15. data/lib/capistrano/cli/options.rb +224 -0
  16. data/lib/capistrano/cli/ui.rb +40 -0
  17. data/lib/capistrano/command.rb +283 -0
  18. data/lib/capistrano/configuration.rb +43 -0
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
  20. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  21. data/lib/capistrano/configuration/actions/invocation.rb +293 -0
  22. data/lib/capistrano/configuration/callbacks.rb +148 -0
  23. data/lib/capistrano/configuration/connections.rb +204 -0
  24. data/lib/capistrano/configuration/execution.rb +143 -0
  25. data/lib/capistrano/configuration/loading.rb +197 -0
  26. data/lib/capistrano/configuration/namespaces.rb +197 -0
  27. data/lib/capistrano/configuration/roles.rb +73 -0
  28. data/lib/capistrano/configuration/servers.rb +85 -0
  29. data/lib/capistrano/configuration/variables.rb +127 -0
  30. data/lib/capistrano/errors.rb +15 -0
  31. data/lib/capistrano/extensions.rb +57 -0
  32. data/lib/capistrano/logger.rb +59 -0
  33. data/lib/capistrano/processable.rb +53 -0
  34. data/lib/capistrano/recipes/compat.rb +32 -0
  35. data/lib/capistrano/recipes/deploy.rb +438 -0
  36. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  37. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  38. data/lib/capistrano/recipes/deploy/remote_dependency.rb +105 -0
  39. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  40. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  41. data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
  42. data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
  43. data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
  44. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  45. data/lib/capistrano/recipes/deploy/scm/git.rb +274 -0
  46. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  47. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  48. data/lib/capistrano/recipes/deploy/scm/perforce.rb +138 -0
  49. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  50. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  51. data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
  52. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  53. data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -0
  54. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  55. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  56. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +56 -0
  57. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  58. data/lib/capistrano/recipes/ext/rails-database-migrations.rb +50 -0
  59. data/lib/capistrano/recipes/ext/web-disable-enable.rb +40 -0
  60. data/lib/capistrano/recipes/standard.rb +37 -0
  61. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  62. data/lib/capistrano/recipes/upgrade.rb +33 -0
  63. data/lib/capistrano/role.rb +102 -0
  64. data/lib/capistrano/server_definition.rb +56 -0
  65. data/lib/capistrano/shell.rb +260 -0
  66. data/lib/capistrano/ssh.rb +99 -0
  67. data/lib/capistrano/task_definition.rb +70 -0
  68. data/lib/capistrano/transfer.rb +216 -0
  69. data/lib/capistrano/version.rb +18 -0
  70. data/setup.rb +1346 -0
  71. data/test/cli/execute_test.rb +132 -0
  72. data/test/cli/help_test.rb +165 -0
  73. data/test/cli/options_test.rb +317 -0
  74. data/test/cli/ui_test.rb +28 -0
  75. data/test/cli_test.rb +17 -0
  76. data/test/command_test.rb +286 -0
  77. data/test/configuration/actions/file_transfer_test.rb +61 -0
  78. data/test/configuration/actions/inspect_test.rb +65 -0
  79. data/test/configuration/actions/invocation_test.rb +224 -0
  80. data/test/configuration/callbacks_test.rb +220 -0
  81. data/test/configuration/connections_test.rb +349 -0
  82. data/test/configuration/execution_test.rb +175 -0
  83. data/test/configuration/loading_test.rb +132 -0
  84. data/test/configuration/namespace_dsl_test.rb +311 -0
  85. data/test/configuration/roles_test.rb +144 -0
  86. data/test/configuration/servers_test.rb +121 -0
  87. data/test/configuration/variables_test.rb +184 -0
  88. data/test/configuration_test.rb +88 -0
  89. data/test/deploy/local_dependency_test.rb +76 -0
  90. data/test/deploy/remote_dependency_test.rb +114 -0
  91. data/test/deploy/scm/accurev_test.rb +23 -0
  92. data/test/deploy/scm/base_test.rb +55 -0
  93. data/test/deploy/scm/git_test.rb +184 -0
  94. data/test/deploy/scm/mercurial_test.rb +129 -0
  95. data/test/deploy/scm/none_test.rb +35 -0
  96. data/test/deploy/strategy/copy_test.rb +258 -0
  97. data/test/extensions_test.rb +69 -0
  98. data/test/fixtures/cli_integration.rb +5 -0
  99. data/test/fixtures/config.rb +5 -0
  100. data/test/fixtures/custom.rb +3 -0
  101. data/test/logger_test.rb +123 -0
  102. data/test/role_test.rb +11 -0
  103. data/test/server_definition_test.rb +121 -0
  104. data/test/shell_test.rb +90 -0
  105. data/test/ssh_test.rb +104 -0
  106. data/test/task_definition_test.rb +101 -0
  107. data/test/transfer_test.rb +160 -0
  108. data/test/utils.rb +38 -0
  109. metadata +321 -0
@@ -0,0 +1,50 @@
1
+ namespace :deploy do
2
+
3
+ desc <<-DESC
4
+ Run the migrate rake task. By default, it runs this in most recently \
5
+ deployed version of the app. However, you can specify a different release \
6
+ via the migrate_target variable, which must be one of :latest (for the \
7
+ default behavior), or :current (for the release indicated by the \
8
+ `current' symlink). Strings will work for those values instead of symbols, \
9
+ too. You can also specify additional environment variables to pass to rake \
10
+ via the migrate_env variable. Finally, you can specify the full path to the \
11
+ rake executable by setting the rake variable. The defaults are:
12
+
13
+ set :rake, "rake"
14
+ set :rails_env, "production"
15
+ set :migrate_env, ""
16
+ set :migrate_target, :latest
17
+ DESC
18
+ task :migrate, :roles => :db, :only => { :primary => true } do
19
+ rake = fetch(:rake, "rake")
20
+ rails_env = fetch(:rails_env, "production")
21
+ migrate_env = fetch(:migrate_env, "")
22
+ migrate_target = fetch(:migrate_target, :latest)
23
+
24
+ directory = case migrate_target.to_sym
25
+ when :current then current_path
26
+ when :latest then current_release
27
+ else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
28
+ end
29
+
30
+ run "cd #{directory}; #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
31
+ end
32
+
33
+ desc <<-DESC
34
+ Deploy and run pending migrations. This will work similarly to the \
35
+ `deploy' task, but will also run any pending migrations (via the \
36
+ `deploy:migrate' task) prior to updating the symlink. Note that the \
37
+ update in this case it is not atomic, and transactions are not used, \
38
+ because migrations are not guaranteed to be reversible.
39
+ DESC
40
+ task :migrations do
41
+ set :migrate_target, :latest
42
+ update_code
43
+ migrate
44
+ symlink
45
+ restart
46
+ end
47
+
48
+ end
49
+
50
+ after('deploy:update_code', 'deploy:migrate')
@@ -0,0 +1,40 @@
1
+ namespace :web do
2
+ desc <<-DESC
3
+ Present a maintenance page to visitors. Disables your application's web \
4
+ interface by writing a "maintenance.html" file to each web server. The \
5
+ servers must be configured to detect the presence of this file, and if \
6
+ it is present, always display it instead of performing the request.
7
+
8
+ By default, the maintenance page will just say the site is down for \
9
+ "maintenance", and will be back "shortly", but you can customize the \
10
+ page by specifying the REASON and UNTIL environment variables:
11
+
12
+ $ cap deploy:web:disable \\
13
+ REASON="hardware upgrade" \\
14
+ UNTIL="12pm Central Time"
15
+
16
+ Further customization will require that you write your own task.
17
+ DESC
18
+ task :disable, :roles => :web, :except => { :no_release => true } do
19
+ require 'erb'
20
+ on_rollback { run "rm #{shared_path}/system/maintenance.html" }
21
+
22
+ reason = ENV['REASON']
23
+ deadline = ENV['UNTIL']
24
+
25
+ template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml"))
26
+ result = ERB.new(template).result(binding)
27
+
28
+ put result, "#{shared_path}/system/maintenance.html", :mode => 0644
29
+ end
30
+
31
+ desc <<-DESC
32
+ Makes the application web-accessible again. Removes the \
33
+ "maintenance.html" page generated by deploy:web:disable, which (if your \
34
+ web servers are configured correctly) will make your application \
35
+ web-accessible again.
36
+ DESC
37
+ task :enable, :roles => :web, :except => { :no_release => true } do
38
+ run "rm #{shared_path}/system/maintenance.html"
39
+ end
40
+ 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,33 @@
1
+ # Tasks to aid the migration of an established Capistrano 1.x installation to
2
+ # Capistrano 2.x.
3
+
4
+ namespace :upgrade do
5
+ desc <<-DESC
6
+ Migrate from the revisions log to REVISION. Capistrano 1.x recorded each \
7
+ deployment to a revisions.log file. Capistrano 2.x is cleaner, and just \
8
+ puts a REVISION file in the root of the deployed revision. This task \
9
+ migrates from the revisions.log used in Capistrano 1.x, to the REVISION \
10
+ tag file used in Capistrano 2.x. It is non-destructive and may be safely \
11
+ run any number of times.
12
+ DESC
13
+ task :revisions, :except => { :no_release => true } do
14
+ revisions = capture("cat #{deploy_to}/revisions.log")
15
+
16
+ mapping = {}
17
+ revisions.each do |line|
18
+ revision, directory = line.chomp.split[-2,2]
19
+ mapping[directory] = revision
20
+ end
21
+
22
+ commands = mapping.keys.map do |directory|
23
+ "echo '.'; test -d #{directory} && echo '#{mapping[directory]}' > #{directory}/REVISION"
24
+ end
25
+
26
+ command = commands.join(";")
27
+
28
+ run "cd #{releases_path}; #{command}; true" do |ch, stream, out|
29
+ STDOUT.print(".")
30
+ STDOUT.flush
31
+ end
32
+ end
33
+ end
@@ -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,260 @@
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 /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
68
+ process_command($1, $2, $3)
69
+ else
70
+ raise "eh?"
71
+ end
72
+
73
+ return true
74
+ end
75
+
76
+ private
77
+
78
+ # Present the prompt and read a single line from the console. It also
79
+ # detects ^D and returns "exit" in that case. Adds the input to the
80
+ # history, unless the input is empty. Loops repeatedly until a non-empty
81
+ # line is input.
82
+ def read_line
83
+ loop do
84
+ command = reader.readline("cap> ")
85
+
86
+ if command.nil?
87
+ command = "exit"
88
+ puts(command)
89
+ else
90
+ command.strip!
91
+ end
92
+
93
+ unless command.empty?
94
+ reader::HISTORY << command
95
+ return command
96
+ end
97
+ end
98
+ end
99
+
100
+ # Display a verbose help message.
101
+ def help
102
+ puts <<-HELP
103
+ --- HELP! ---------------------------------------------------
104
+ "Get me out of this thing. I just want to quit."
105
+ -> Easy enough. Just type "exit", or "quit". Or press ctrl-D.
106
+
107
+ "I want to execute a command on all servers."
108
+ -> Just type the command, and press enter. It will be passed,
109
+ verbatim, to all defined servers.
110
+
111
+ "What if I only want it to execute on a subset of them?"
112
+ -> No problem, just specify the list of servers, separated by
113
+ commas, before the command, with the `on' keyword:
114
+
115
+ cap> on app1.foo.com,app2.foo.com echo ping
116
+
117
+ "Nice, but can I specify the servers by role?"
118
+ -> You sure can. Just use the `with' keyword, followed by the
119
+ comma-delimited list of role names:
120
+
121
+ cap> with app,db echo ping
122
+
123
+ "Can I execute a Capistrano task from within this shell?"
124
+ -> Yup. Just prefix the task with an exclamation mark:
125
+
126
+ cap> !deploy
127
+ HELP
128
+ end
129
+
130
+ # Determine which servers the given task requires a connection to, and
131
+ # establish connections to them if necessary. Return the list of
132
+ # servers (names).
133
+ def connect(task)
134
+ servers = configuration.find_servers_for_task(task)
135
+ needing_connections = servers - configuration.sessions.keys
136
+ unless needing_connections.empty?
137
+ puts "[establishing connection(s) to #{needing_connections.join(', ')}]"
138
+ configuration.establish_connections_to(needing_connections)
139
+ end
140
+ servers
141
+ end
142
+
143
+ # Execute the given command. If the command is prefixed by an exclamation
144
+ # mark, it is assumed to refer to another capistrano task, which will
145
+ # be invoked. Otherwise, it is executed as a command on all associated
146
+ # servers.
147
+ def exec(command)
148
+ @mutex.synchronize do
149
+ if command[0] == ?!
150
+ exec_tasks(command[1..-1].split)
151
+ else
152
+ servers = connect(configuration.current_task)
153
+ exec_command(command, servers)
154
+ end
155
+ end
156
+ ensure
157
+ STDOUT.flush
158
+ end
159
+
160
+ # Given an array of task names, invoke them in sequence.
161
+ def exec_tasks(list)
162
+ list.each do |task_name|
163
+ task = configuration.find_task(task_name)
164
+ raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task
165
+ connect(task)
166
+ configuration.execute_task(task)
167
+ end
168
+ rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
169
+ warn "error: #{error.message}"
170
+ end
171
+
172
+ # Execute a command on the given list of servers.
173
+ def exec_command(command, servers)
174
+ command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'")
175
+ processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
176
+ sessions = servers.map { |server| configuration.sessions[server] }
177
+ options = configuration.add_default_command_options({})
178
+ cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor)
179
+ previous = trap("INT") { cmd.stop! }
180
+ cmd.process!
181
+ rescue Capistrano::Error => error
182
+ warn "error: #{error.message}"
183
+ ensure
184
+ trap("INT", previous)
185
+ end
186
+
187
+ # Return the object that will be used to query input from the console.
188
+ # The returned object will quack (more or less) like Readline.
189
+ def reader
190
+ @reader ||= begin
191
+ require 'readline'
192
+ Readline
193
+ rescue LoadError
194
+ ReadlineFallback
195
+ end
196
+ end
197
+
198
+ # Prepare every little thing for the shell. Starts the background
199
+ # thread and generally gets things ready for the REPL.
200
+ def setup
201
+ configuration.logger.level = Capistrano::Logger::INFO
202
+
203
+ @mutex = Mutex.new
204
+ @bgthread = Thread.new do
205
+ loop do
206
+ @mutex.synchronize { process_iteration(0.1) }
207
+ end
208
+ end
209
+ end
210
+
211
+ # Set the given option to +value+.
212
+ def set_option(opt, value)
213
+ case opt
214
+ when "v" then
215
+ puts "setting log verbosity to #{value.to_i}"
216
+ configuration.logger.level = value.to_i
217
+ when "o" then
218
+ case value
219
+ when "vi" then
220
+ puts "using vi edit mode"
221
+ reader.vi_editing_mode
222
+ when "emacs" then
223
+ puts "using emacs edit mode"
224
+ reader.emacs_editing_mode
225
+ else
226
+ puts "unknown -o option #{value.inspect}"
227
+ end
228
+ else
229
+ puts "unknown setting #{opt.inspect}"
230
+ end
231
+ end
232
+
233
+ # Process a command. Interprets the scope_type (must be nil, "with", or
234
+ # "on") and the command. If no command is given, then the scope is made
235
+ # effective for all subsequent commands. If the scope value is "all",
236
+ # then the scope is unrestricted.
237
+ def process_command(scope_type, scope_value, command)
238
+ env_var = case scope_type
239
+ when "with" then "ROLES"
240
+ when "on" then "HOSTS"
241
+ end
242
+
243
+ old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
244
+ if command
245
+ begin
246
+ exec(command)
247
+ ensure
248
+ ENV[env_var] = old_var if env_var
249
+ end
250
+ else
251
+ puts "scoping #{scope_type} #{scope_value}"
252
+ end
253
+ end
254
+ end
255
+
256
+ # All open sessions, needed to satisfy the Command::Processable include
257
+ def sessions
258
+ configuration.sessions.values
259
+ end
260
+ end