capistrano-edge 2.5.6

Sign up to get free protection for your applications and to get access to all the features.
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