capistrano 2.1.0 → 3.0.0

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 (166) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +89 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +226 -0
  8. data/Rakefile +5 -0
  9. data/bin/cap +2 -3
  10. data/bin/capify +7 -77
  11. data/capistrano-public_cert.pem +22 -0
  12. data/capistrano.gemspec +35 -0
  13. data/features/deploy.feature +52 -0
  14. data/features/installation.feature +16 -0
  15. data/features/remote_file_task.feature +14 -0
  16. data/features/step_definitions/assertions.rb +90 -0
  17. data/features/step_definitions/cap_commands.rb +8 -0
  18. data/features/step_definitions/setup.rb +25 -0
  19. data/features/support/env.rb +12 -0
  20. data/features/support/remote_command_helpers.rb +20 -0
  21. data/lib/Capfile +3 -0
  22. data/lib/capistrano/all.rb +16 -0
  23. data/lib/capistrano/application.rb +60 -0
  24. data/lib/capistrano/configuration/question.rb +42 -0
  25. data/lib/capistrano/configuration/server.rb +133 -0
  26. data/lib/capistrano/configuration/servers/role_filter.rb +86 -0
  27. data/lib/capistrano/configuration/servers.rb +53 -58
  28. data/lib/capistrano/configuration.rb +84 -30
  29. data/lib/capistrano/console.rb +1 -0
  30. data/lib/capistrano/defaults.rb +13 -0
  31. data/lib/capistrano/deploy.rb +3 -0
  32. data/lib/capistrano/dotfile.rb +3 -0
  33. data/lib/capistrano/dsl/env.rb +64 -0
  34. data/lib/capistrano/dsl/paths.rb +94 -0
  35. data/lib/capistrano/dsl/stages.rb +15 -0
  36. data/lib/capistrano/dsl/task_enhancements.rb +53 -0
  37. data/lib/capistrano/dsl.rb +48 -0
  38. data/lib/capistrano/git.rb +1 -0
  39. data/lib/capistrano/hg.rb +1 -0
  40. data/lib/capistrano/i18n.rb +34 -0
  41. data/lib/capistrano/install.rb +1 -0
  42. data/lib/capistrano/setup.rb +21 -0
  43. data/lib/capistrano/tasks/console.rake +21 -0
  44. data/lib/capistrano/tasks/deploy.rake +204 -0
  45. data/lib/capistrano/tasks/framework.rake +67 -0
  46. data/lib/capistrano/tasks/git.rake +62 -0
  47. data/lib/capistrano/tasks/hg.rake +39 -0
  48. data/lib/capistrano/tasks/install.rake +39 -0
  49. data/lib/capistrano/templates/Capfile +26 -0
  50. data/lib/capistrano/templates/deploy.rb.erb +40 -0
  51. data/lib/capistrano/templates/stage.rb.erb +42 -0
  52. data/lib/capistrano/version.rb +1 -20
  53. data/lib/capistrano/version_validator.rb +37 -0
  54. data/lib/capistrano.rb +0 -2
  55. data/spec/integration/dsl_spec.rb +344 -0
  56. data/spec/integration_spec_helper.rb +7 -0
  57. data/spec/lib/capistrano/application_spec.rb +61 -0
  58. data/spec/lib/capistrano/configuration/question_spec.rb +54 -0
  59. data/spec/lib/capistrano/configuration/server_spec.rb +249 -0
  60. data/spec/lib/capistrano/configuration/servers/role_filter_spec.rb +140 -0
  61. data/spec/lib/capistrano/configuration/servers_spec.rb +184 -0
  62. data/spec/lib/capistrano/configuration_spec.rb +101 -0
  63. data/spec/lib/capistrano/dsl/env_spec.rb +10 -0
  64. data/spec/lib/capistrano/dsl/paths_spec.rb +69 -0
  65. data/spec/lib/capistrano/dsl_spec.rb +63 -0
  66. data/spec/lib/capistrano/version_validator_spec.rb +103 -0
  67. data/spec/lib/capistrano_spec.rb +8 -0
  68. data/spec/spec_helper.rb +15 -0
  69. data/spec/support/.gitignore +1 -0
  70. data/spec/support/Vagrantfile +13 -0
  71. data/spec/support/matchers.rb +5 -0
  72. data/spec/support/tasks/database.cap +11 -0
  73. data/spec/support/test_app.rb +138 -0
  74. metadata +251 -179
  75. data/CHANGELOG +0 -512
  76. data/MIT-LICENSE +0 -20
  77. data/README +0 -43
  78. data/examples/sample.rb +0 -14
  79. data/lib/capistrano/callback.rb +0 -45
  80. data/lib/capistrano/cli/execute.rb +0 -82
  81. data/lib/capistrano/cli/help.rb +0 -102
  82. data/lib/capistrano/cli/help.txt +0 -53
  83. data/lib/capistrano/cli/options.rb +0 -183
  84. data/lib/capistrano/cli/ui.rb +0 -28
  85. data/lib/capistrano/cli.rb +0 -47
  86. data/lib/capistrano/command.rb +0 -161
  87. data/lib/capistrano/configuration/actions/file_transfer.rb +0 -35
  88. data/lib/capistrano/configuration/actions/inspect.rb +0 -46
  89. data/lib/capistrano/configuration/actions/invocation.rb +0 -134
  90. data/lib/capistrano/configuration/callbacks.rb +0 -148
  91. data/lib/capistrano/configuration/connections.rb +0 -159
  92. data/lib/capistrano/configuration/execution.rb +0 -126
  93. data/lib/capistrano/configuration/loading.rb +0 -198
  94. data/lib/capistrano/configuration/namespaces.rb +0 -196
  95. data/lib/capistrano/configuration/roles.rb +0 -51
  96. data/lib/capistrano/configuration/variables.rb +0 -127
  97. data/lib/capistrano/errors.rb +0 -15
  98. data/lib/capistrano/extensions.rb +0 -57
  99. data/lib/capistrano/gateway.rb +0 -131
  100. data/lib/capistrano/logger.rb +0 -59
  101. data/lib/capistrano/recipes/compat.rb +0 -32
  102. data/lib/capistrano/recipes/deploy/dependencies.rb +0 -44
  103. data/lib/capistrano/recipes/deploy/local_dependency.rb +0 -46
  104. data/lib/capistrano/recipes/deploy/remote_dependency.rb +0 -96
  105. data/lib/capistrano/recipes/deploy/scm/accurev.rb +0 -169
  106. data/lib/capistrano/recipes/deploy/scm/base.rb +0 -192
  107. data/lib/capistrano/recipes/deploy/scm/bzr.rb +0 -86
  108. data/lib/capistrano/recipes/deploy/scm/cvs.rb +0 -151
  109. data/lib/capistrano/recipes/deploy/scm/darcs.rb +0 -85
  110. data/lib/capistrano/recipes/deploy/scm/git.rb +0 -191
  111. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +0 -129
  112. data/lib/capistrano/recipes/deploy/scm/perforce.rb +0 -126
  113. data/lib/capistrano/recipes/deploy/scm/subversion.rb +0 -114
  114. data/lib/capistrano/recipes/deploy/scm.rb +0 -19
  115. data/lib/capistrano/recipes/deploy/strategy/base.rb +0 -64
  116. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +0 -20
  117. data/lib/capistrano/recipes/deploy/strategy/copy.rb +0 -144
  118. data/lib/capistrano/recipes/deploy/strategy/export.rb +0 -20
  119. data/lib/capistrano/recipes/deploy/strategy/remote.rb +0 -52
  120. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +0 -47
  121. data/lib/capistrano/recipes/deploy/strategy.rb +0 -19
  122. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  123. data/lib/capistrano/recipes/deploy.rb +0 -494
  124. data/lib/capistrano/recipes/standard.rb +0 -37
  125. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
  126. data/lib/capistrano/recipes/upgrade.rb +0 -33
  127. data/lib/capistrano/server_definition.rb +0 -51
  128. data/lib/capistrano/shell.rb +0 -256
  129. data/lib/capistrano/ssh.rb +0 -109
  130. data/lib/capistrano/task_definition.rb +0 -69
  131. data/lib/capistrano/upload.rb +0 -146
  132. data/test/cli/execute_test.rb +0 -132
  133. data/test/cli/help_test.rb +0 -139
  134. data/test/cli/options_test.rb +0 -226
  135. data/test/cli/ui_test.rb +0 -28
  136. data/test/cli_test.rb +0 -17
  137. data/test/command_test.rb +0 -309
  138. data/test/configuration/actions/file_transfer_test.rb +0 -40
  139. data/test/configuration/actions/inspect_test.rb +0 -62
  140. data/test/configuration/actions/invocation_test.rb +0 -202
  141. data/test/configuration/callbacks_test.rb +0 -206
  142. data/test/configuration/connections_test.rb +0 -288
  143. data/test/configuration/execution_test.rb +0 -159
  144. data/test/configuration/loading_test.rb +0 -127
  145. data/test/configuration/namespace_dsl_test.rb +0 -297
  146. data/test/configuration/roles_test.rb +0 -47
  147. data/test/configuration/servers_test.rb +0 -90
  148. data/test/configuration/variables_test.rb +0 -180
  149. data/test/configuration_test.rb +0 -81
  150. data/test/deploy/scm/accurev_test.rb +0 -23
  151. data/test/deploy/scm/base_test.rb +0 -55
  152. data/test/deploy/scm/git_test.rb +0 -112
  153. data/test/deploy/strategy/copy_test.rb +0 -147
  154. data/test/extensions_test.rb +0 -69
  155. data/test/fixtures/cli_integration.rb +0 -5
  156. data/test/fixtures/config.rb +0 -5
  157. data/test/fixtures/custom.rb +0 -3
  158. data/test/gateway_test.rb +0 -167
  159. data/test/logger_test.rb +0 -123
  160. data/test/server_definition_test.rb +0 -108
  161. data/test/shell_test.rb +0 -64
  162. data/test/ssh_test.rb +0 -97
  163. data/test/task_definition_test.rb +0 -101
  164. data/test/upload_test.rb +0 -131
  165. data/test/utils.rb +0 -42
  166. data/test/version_test.rb +0 -24
@@ -1,46 +0,0 @@
1
- require 'capistrano/errors'
2
-
3
- module Capistrano
4
- class Configuration
5
- module Actions
6
- module Inspect
7
-
8
- # Streams the result of the command from all servers that are the
9
- # target of the current task. All these streams will be joined into a
10
- # single one, so you can, say, watch 10 log files as though they were
11
- # one. Do note that this is quite expensive from a bandwidth
12
- # perspective, so use it with care.
13
- #
14
- # The command is invoked via #invoke_command.
15
- #
16
- # Usage:
17
- #
18
- # desc "Run a tail on multiple log files at the same time"
19
- # task :tail_fcgi, :roles => :app do
20
- # stream "tail -f #{shared_path}/log/fastcgi.crash.log"
21
- # end
22
- def stream(command, options={})
23
- invoke_command(command, options) do |ch, stream, out|
24
- puts out if stream == :out
25
- warn "[err :: #{ch[:server]}] #{out}" if stream == :err
26
- end
27
- end
28
-
29
- # Executes the given command on the first server targetted by the
30
- # current task, collects it's stdout into a string, and returns the
31
- # string. The command is invoked via #invoke_command.
32
- def capture(command, options={})
33
- output = ""
34
- invoke_command(command, options.merge(:once => true)) do |ch, stream, data|
35
- case stream
36
- when :out then output << data
37
- when :err then raise CaptureError, "error processing #{command.inspect}: #{data.inspect}"
38
- end
39
- end
40
- output
41
- end
42
-
43
- end
44
- end
45
- end
46
- end
@@ -1,134 +0,0 @@
1
- require 'capistrano/command'
2
-
3
- module Capistrano
4
- class Configuration
5
- module Actions
6
- module Invocation
7
- def self.included(base) #:nodoc:
8
- base.extend(ClassMethods)
9
-
10
- base.send :alias_method, :initialize_without_invocation, :initialize
11
- base.send :alias_method, :initialize, :initialize_with_invocation
12
-
13
- base.default_io_proc = Proc.new do |ch, stream, out|
14
- level = stream == :err ? :important : :info
15
- ch[:options][:logger].send(level, out, "#{stream} :: #{ch[:server]}")
16
- end
17
- end
18
-
19
- module ClassMethods
20
- attr_accessor :default_io_proc
21
- end
22
-
23
- def initialize_with_invocation(*args) #:nodoc:
24
- initialize_without_invocation(*args)
25
- set :default_environment, {}
26
- set :default_run_options, {}
27
- end
28
-
29
- # Invokes the given command. If a +via+ key is given, it will be used
30
- # to determine what method to use to invoke the command. It defaults
31
- # to :run, but may be :sudo, or any other method that conforms to the
32
- # same interface as run and sudo.
33
- def invoke_command(cmd, options={}, &block)
34
- options = options.dup
35
- via = options.delete(:via) || :run
36
- send(via, cmd, options, &block)
37
- end
38
-
39
- # Execute the given command on all servers that are the target of the
40
- # current task. If a block is given, it is invoked for all output
41
- # generated by the command, and should accept three parameters: the SSH
42
- # channel (which may be used to send data back to the remote process),
43
- # the stream identifier (<tt>:err</tt> for stderr, and <tt>:out</tt> for
44
- # stdout), and the data that was received.
45
- def run(cmd, options={}, &block)
46
- block ||= self.class.default_io_proc
47
- logger.debug "executing #{cmd.strip.inspect}"
48
-
49
- options = add_default_command_options(options)
50
-
51
- execute_on_servers(options) do |servers|
52
- targets = servers.map { |s| sessions[s] }
53
- Command.process(cmd, targets, options.merge(:logger => logger), &block)
54
- end
55
- end
56
-
57
- # Like #run, but executes the command via <tt>sudo</tt>. This assumes
58
- # that the sudo password (if required) is the same as the password for
59
- # logging in to the server.
60
- #
61
- # Also, this module accepts a <tt>:sudo</tt> configuration variable,
62
- # which (if specified) will be used as the full path to the sudo
63
- # executable on the remote machine:
64
- #
65
- # set :sudo, "/opt/local/bin/sudo"
66
- def sudo(command, options={}, &block)
67
- block ||= self.class.default_io_proc
68
-
69
- options = options.dup
70
- as = options.delete(:as)
71
-
72
- user = as && "-u #{as}"
73
- command = [fetch(:sudo, "sudo"), "-p '#{sudo_prompt}'", user, command].compact.join(" ")
74
-
75
- run(command, options, &sudo_behavior_callback(block))
76
- end
77
-
78
- # Returns a Proc object that defines the behavior of the sudo
79
- # callback. The returned Proc will defer to the +fallback+ argument
80
- # (which should also be a Proc) for any output it does not
81
- # explicitly handle.
82
- def sudo_behavior_callback(fallback) #:nodoc:
83
- # in order to prevent _each host_ from prompting when the password
84
- # was wrong, let's track which host prompted first and only allow
85
- # subsequent prompts from that host.
86
- prompt_host = nil
87
-
88
- Proc.new do |ch, stream, out|
89
- if out =~ /^#{Regexp.escape(sudo_prompt)}/
90
- ch.send_data "#{self[:password]}\n"
91
- elsif out =~ /try again/
92
- if prompt_host.nil? || prompt_host == ch[:server]
93
- prompt_host = ch[:server]
94
- logger.important out, "#{stream} :: #{ch[:server]}"
95
- reset! :password
96
- end
97
- else
98
- fallback.call(ch, stream, out)
99
- end
100
- end
101
- end
102
-
103
- # Merges the various default command options into the options hash and
104
- # returns the result. The default command options that are understand
105
- # are:
106
- #
107
- # * :default_environment: If the :env key already exists, the :env
108
- # key is merged into default_environment and then added back into
109
- # options.
110
- # * :default_shell: if the :shell key already exists, it will be used.
111
- # Otherwise, if the :default_shell key exists in the configuration,
112
- # it will be used. Otherwise, no :shell key is added.
113
- def add_default_command_options(options)
114
- defaults = self[:default_run_options]
115
- options = defaults.merge(options)
116
-
117
- env = self[:default_environment]
118
- env = env.merge(options[:env]) if options[:env]
119
- options[:env] = env unless env.empty?
120
-
121
- shell = options[:shell] || self[:default_shell]
122
- options[:shell] = shell if shell
123
-
124
- options
125
- end
126
-
127
- # Returns the prompt text to use with sudo
128
- def sudo_prompt
129
- fetch(:sudo_prompt, "sudo password: ")
130
- end
131
- end
132
- end
133
- end
134
- end
@@ -1,148 +0,0 @@
1
- require 'capistrano/callback'
2
-
3
- module Capistrano
4
- class Configuration
5
- module Callbacks
6
- def self.included(base) #:nodoc:
7
- %w(initialize execute_task).each do |method|
8
- base.send :alias_method, "#{method}_without_callbacks", method
9
- base.send :alias_method, method, "#{method}_with_callbacks"
10
- end
11
- end
12
-
13
- # The hash of callbacks that have been registered for this configuration
14
- attr_reader :callbacks
15
-
16
- def initialize_with_callbacks(*args) #:nodoc:
17
- initialize_without_callbacks(*args)
18
- @callbacks = {}
19
- end
20
-
21
- def execute_task_with_callbacks(task) #:nodoc:
22
- before = find_hook(task, :before)
23
- execute_task(before) if before
24
-
25
- trigger :before, task
26
-
27
- result = execute_task_without_callbacks(task)
28
-
29
- trigger :after, task
30
-
31
- after = find_hook(task, :after)
32
- execute_task(after) if after
33
-
34
- return result
35
- end
36
-
37
- # Defines a callback to be invoked before the given task. You must
38
- # specify the fully-qualified task name, both for the primary task, and
39
- # for the task(s) to be executed before. Alternatively, you can pass a
40
- # block to be executed before the given task.
41
- #
42
- # before "deploy:update_code", :record_difference
43
- # before :deploy, "custom:log_deploy"
44
- # before :deploy, :this, "then:this", "and:then:this"
45
- # before :some_task do
46
- # puts "an anonymous hook!"
47
- # end
48
- #
49
- # This just provides a convenient interface to the more general #on method.
50
- def before(task_name, *args, &block)
51
- options = args.last.is_a?(Hash) ? args.pop : {}
52
- args << options.merge(:only => task_name)
53
- on :before, *args, &block
54
- end
55
-
56
- # Defines a callback to be invoked after the given task. You must
57
- # specify the fully-qualified task name, both for the primary task, and
58
- # for the task(s) to be executed after. Alternatively, you can pass a
59
- # block to be executed after the given task.
60
- #
61
- # after "deploy:update_code", :log_difference
62
- # after :deploy, "custom:announce"
63
- # after :deploy, :this, "then:this", "and:then:this"
64
- # after :some_task do
65
- # puts "an anonymous hook!"
66
- # end
67
- #
68
- # This just provides a convenient interface to the more general #on method.
69
- def after(task_name, *args, &block)
70
- options = args.last.is_a?(Hash) ? args.pop : {}
71
- args << options.merge(:only => task_name)
72
- on :after, *args, &block
73
- end
74
-
75
- # Defines one or more callbacks to be invoked in response to some event.
76
- # Capistrano currently understands the following events:
77
- #
78
- # * :before, triggered before a task is invoked
79
- # * :after, triggered after a task is invoked
80
- # * :start, triggered before a top-level task is invoked via the command-line
81
- # * :finish, triggered when a top-level task completes
82
- # * :load, triggered after all recipes have loaded
83
- # * :exit, triggered after all tasks have completed
84
- #
85
- # Specify the (fully-qualified) task names that you want invoked in
86
- # response to the event. Alternatively, you can specify a block to invoke
87
- # when the event is triggered. You can also pass a hash of options as the
88
- # last parameter, which may include either of two keys:
89
- #
90
- # * :only, should specify an array of task names. Restricts this callback
91
- # so that it will only fire when the event applies to those tasks.
92
- # * :except, should specify an array of task names. Restricts this callback
93
- # so that it will never fire when the event applies to those tasks.
94
- #
95
- # Usage:
96
- #
97
- # on :before, "some:hook", "another:hook", :only => "deploy:update"
98
- # on :after, "some:hook", :except => "deploy:symlink"
99
- # on :before, "global:hook"
100
- # on :after, :only => :deploy do
101
- # puts "after deploy here"
102
- # end
103
- def on(event, *args, &block)
104
- options = args.last.is_a?(Hash) ? args.pop : {}
105
- callbacks[event] ||= []
106
-
107
- if args.empty? && block.nil?
108
- raise ArgumentError, "please specify either a task name or a block to invoke"
109
- elsif args.any? && block
110
- raise ArgumentError, "please specify only a task name or a block, but not both"
111
- elsif block
112
- callbacks[event] << ProcCallback.new(block, options)
113
- else
114
- args.each do |name|
115
- callbacks[event] << TaskCallback.new(self, name, options)
116
- end
117
- end
118
- end
119
-
120
- # Trigger the named event for the named task. All associated callbacks
121
- # will be fired, in the order they were defined.
122
- def trigger(event, task=nil)
123
- pending = Array(callbacks[event]).select { |c| c.applies_to?(task) }
124
- if pending.any?
125
- msg = "triggering #{event} callbacks"
126
- msg << " for `#{task.fully_qualified_name}'" if task
127
- logger.trace(msg)
128
- pending.each { |callback| callback.call }
129
- end
130
- end
131
-
132
- private
133
-
134
- # Looks for before_foo or after_foo tasks. This method of extending tasks
135
- # is now discouraged (though not formally deprecated). You should use the
136
- # before and after methods to declare hooks for such callbacks.
137
- def find_hook(task, hook)
138
- if task == task.namespace.default_task
139
- result = task.namespace.search_task("#{hook}_#{task.namespace.name}")
140
- return result if result
141
- end
142
-
143
- task.namespace.search_task("#{hook}_#{task.name}")
144
- end
145
-
146
- end
147
- end
148
- end
@@ -1,159 +0,0 @@
1
- require 'capistrano/gateway'
2
- require 'capistrano/ssh'
3
-
4
- module Capistrano
5
- class Configuration
6
- module Connections
7
- def self.included(base) #:nodoc:
8
- base.send :alias_method, :initialize_without_connections, :initialize
9
- base.send :alias_method, :initialize, :initialize_with_connections
10
- end
11
-
12
- # An adaptor for making the SSH interface look and act like that of the
13
- # Gateway class.
14
- class DefaultConnectionFactory #:nodoc:
15
- def initialize(options)
16
- @options = options
17
- end
18
-
19
- def connect_to(server)
20
- SSH.connect(server, @options)
21
- end
22
- end
23
-
24
- # A hash of the SSH sessions that are currently open and available.
25
- # Because sessions are constructed lazily, this will only contain
26
- # connections to those servers that have been the targets of one or more
27
- # executed tasks.
28
- attr_reader :sessions
29
-
30
- def initialize_with_connections(*args) #:nodoc:
31
- initialize_without_connections(*args)
32
- @sessions = {}
33
- @failed_sessions = []
34
- end
35
-
36
- # Indicate that the given server could not be connected to.
37
- def failed!(server)
38
- @failed_sessions << server
39
- end
40
-
41
- # Query whether previous connection attempts to the given server have
42
- # failed.
43
- def has_failed?(server)
44
- @failed_sessions.include?(server)
45
- end
46
-
47
- # Used to force connections to be made to the current task's servers.
48
- # Connections are normally made lazily in Capistrano--you can use this
49
- # to force them open before performing some operation that might be
50
- # time-sensitive.
51
- def connect!(options={})
52
- execute_on_servers(options) { }
53
- end
54
-
55
- # Returns the object responsible for establishing new SSH connections.
56
- # The factory will respond to #connect_to, which can be used to
57
- # establish connections to servers defined via ServerDefinition objects.
58
- def connection_factory
59
- @connection_factory ||= begin
60
- if exists?(:gateway)
61
- logger.debug "establishing connection to gateway `#{fetch(:gateway)}'"
62
- Gateway.new(ServerDefinition.new(fetch(:gateway)), self)
63
- else
64
- DefaultConnectionFactory.new(self)
65
- end
66
- end
67
- end
68
-
69
- # Ensures that there are active sessions for each server in the list.
70
- def establish_connections_to(servers)
71
- failed_servers = []
72
-
73
- # This attemps to work around the problem where SFTP uploads hang
74
- # for some people. A bit of investigating seemed to reveal that the
75
- # hang only occurred when the SSH connections were established async,
76
- # so this setting allows people to at least work around the problem.
77
- if fetch(:synchronous_connect, false)
78
- logger.trace "synchronous_connect: true"
79
- Array(servers).each { |server| safely_establish_connection_to(server, failed_servers) }
80
- else
81
- # force the connection factory to be instantiated synchronously,
82
- # otherwise we wind up with multiple gateway instances, because
83
- # each connection is done in parallel.
84
- connection_factory
85
-
86
- threads = Array(servers).map { |server| establish_connection_to(server, failed_servers) }
87
- threads.each { |t| t.join }
88
- end
89
-
90
- if failed_servers.any?
91
- errors = failed_servers.map { |h| "#{h[:server]} (#{h[:error].class}: #{h[:error].message})" }
92
- error = ConnectionError.new("connection failed for: #{errors.join(', ')}")
93
- error.hosts = failed_servers.map { |h| h[:server] }
94
- raise error
95
- end
96
- end
97
-
98
- # Determines the set of servers within the current task's scope and
99
- # establishes connections to them, and then yields that list of
100
- # servers.
101
- def execute_on_servers(options={})
102
- raise ArgumentError, "expected a block" unless block_given?
103
-
104
- if task = current_task
105
- servers = find_servers_for_task(task, options)
106
-
107
- if servers.empty?
108
- raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched"
109
- end
110
-
111
- if task.continue_on_error?
112
- servers.delete_if { |s| has_failed?(s) }
113
- return if servers.empty?
114
- end
115
- else
116
- servers = find_servers(options)
117
- raise Capistrano::NoMatchingServersError, "no servers found to match #{options.inspect}" if servers.empty?
118
- end
119
-
120
- servers = [servers.first] if options[:once]
121
- logger.trace "servers: #{servers.map { |s| s.host }.inspect}"
122
-
123
- # establish connections to those servers, as necessary
124
- begin
125
- establish_connections_to(servers)
126
- rescue ConnectionError => error
127
- raise error unless task && task.continue_on_error?
128
- error.hosts.each do |h|
129
- servers.delete(h)
130
- failed!(h)
131
- end
132
- end
133
-
134
- begin
135
- yield servers
136
- rescue RemoteError => error
137
- raise error unless task && task.continue_on_error?
138
- error.hosts.each { |h| failed!(h) }
139
- end
140
- end
141
-
142
- private
143
-
144
- # We establish the connection by creating a thread in a new method--this
145
- # prevents problems with the thread's scope seeing the wrong 'server'
146
- # variable if the thread just happens to take too long to start up.
147
- def establish_connection_to(server, failures=nil)
148
- Thread.new { safely_establish_connection_to(server, failures) }
149
- end
150
-
151
- def safely_establish_connection_to(server, failures=nil)
152
- sessions[server] ||= connection_factory.connect_to(server)
153
- rescue Exception => err
154
- raise unless failures
155
- failures << { :server => server, :error => err }
156
- end
157
- end
158
- end
159
- end
@@ -1,126 +0,0 @@
1
- require 'capistrano/errors'
2
-
3
- module Capistrano
4
- class Configuration
5
- module Execution
6
- def self.included(base) #:nodoc:
7
- base.send :alias_method, :initialize_without_execution, :initialize
8
- base.send :alias_method, :initialize, :initialize_with_execution
9
- end
10
-
11
- # The call stack of the tasks. The currently executing task may inspect
12
- # this to see who its caller was. The current task is always the last
13
- # element of this stack.
14
- attr_reader :task_call_frames
15
-
16
- # The stack of tasks that have registered rollback handlers within the
17
- # current transaction. If this is nil, then there is no transaction
18
- # that is currently active.
19
- attr_reader :rollback_requests
20
-
21
- # A struct for representing a single instance of an invoked task.
22
- TaskCallFrame = Struct.new(:task, :rollback)
23
-
24
- def initialize_with_execution(*args) #:nodoc:
25
- initialize_without_execution(*args)
26
- @task_call_frames = []
27
- end
28
- private :initialize_with_execution
29
-
30
- # Returns true if there is a transaction currently active.
31
- def transaction?
32
- !rollback_requests.nil?
33
- end
34
-
35
- # Invoke a set of tasks in a transaction. If any task fails (raises an
36
- # exception), all tasks executed within the transaction are inspected to
37
- # see if they have an associated on_rollback hook, and if so, that hook
38
- # is called.
39
- def transaction
40
- raise ArgumentError, "expected a block" unless block_given?
41
- raise ScriptError, "transaction must be called from within a task" if task_call_frames.empty?
42
-
43
- return yield if transaction?
44
-
45
- logger.info "transaction: start"
46
- begin
47
- @rollback_requests = []
48
- yield
49
- logger.info "transaction: commit"
50
- rescue Object => e
51
- rollback!
52
- raise
53
- ensure
54
- @rollback_requests = nil
55
- end
56
- end
57
-
58
- # Specifies an on_rollback hook for the currently executing task. If this
59
- # or any subsequent task then fails, and a transaction is active, this
60
- # hook will be executed.
61
- def on_rollback(&block)
62
- if transaction?
63
- task_call_frames.last.rollback = block
64
- rollback_requests << task_call_frames.last
65
- end
66
- end
67
-
68
- # Returns the TaskDefinition object for the currently executing task.
69
- # It returns nil if there is no task being executed.
70
- def current_task
71
- return nil if task_call_frames.empty?
72
- task_call_frames.last.task
73
- end
74
-
75
- # Executes the task with the given name, including the before and after
76
- # hooks.
77
- def execute_task(task)
78
- logger.debug "executing `#{task.fully_qualified_name}'"
79
- push_task_call_frame(task)
80
- task.namespace.instance_eval(&task.body)
81
- ensure
82
- pop_task_call_frame
83
- end
84
-
85
- # Attempts to locate the task at the given fully-qualified path, and
86
- # execute it. If no such task exists, a Capistrano::NoSuchTaskError will
87
- # be raised.
88
- def find_and_execute_task(path, hooks={})
89
- task = find_task(path) or raise NoSuchTaskError, "the task `#{path}' does not exist"
90
-
91
- trigger(hooks[:before], task) if hooks[:before]
92
- result = execute_task(task)
93
- trigger(hooks[:after], task) if hooks[:after]
94
-
95
- result
96
- end
97
-
98
- protected
99
-
100
- def rollback!
101
- # throw the task back on the stack so that roles are properly
102
- # interpreted in the scope of the task in question.
103
- rollback_requests.reverse.each do |frame|
104
- begin
105
- push_task_call_frame(frame.task)
106
- logger.important "rolling back", frame.task.fully_qualified_name
107
- frame.rollback.call
108
- rescue Object => e
109
- logger.info "exception while rolling back: #{e.class}, #{e.message}", frame.task.fully_qualified_name
110
- ensure
111
- pop_task_call_frame
112
- end
113
- end
114
- end
115
-
116
- def push_task_call_frame(task)
117
- frame = TaskCallFrame.new(task)
118
- task_call_frames.push frame
119
- end
120
-
121
- def pop_task_call_frame
122
- task_call_frames.pop
123
- end
124
- end
125
- end
126
- end