capistrano 2.0.0 → 2.15.2

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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +715 -18
  5. data/Gemfile +12 -0
  6. data/README.md +94 -0
  7. data/Rakefile +11 -0
  8. data/bin/cap +0 -0
  9. data/bin/capify +37 -22
  10. data/capistrano.gemspec +40 -0
  11. data/lib/capistrano/callback.rb +5 -1
  12. data/lib/capistrano/cli/execute.rb +10 -7
  13. data/lib/capistrano/cli/help.rb +39 -16
  14. data/lib/capistrano/cli/help.txt +44 -16
  15. data/lib/capistrano/cli/options.rb +71 -11
  16. data/lib/capistrano/cli/ui.rb +13 -1
  17. data/lib/capistrano/cli.rb +5 -5
  18. data/lib/capistrano/command.rb +215 -58
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
  20. data/lib/capistrano/configuration/actions/inspect.rb +3 -3
  21. data/lib/capistrano/configuration/actions/invocation.rb +212 -22
  22. data/lib/capistrano/configuration/alias_task.rb +26 -0
  23. data/lib/capistrano/configuration/callbacks.rb +26 -27
  24. data/lib/capistrano/configuration/connections.rb +130 -52
  25. data/lib/capistrano/configuration/execution.rb +34 -18
  26. data/lib/capistrano/configuration/loading.rb +91 -6
  27. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  28. data/lib/capistrano/configuration/namespaces.rb +45 -12
  29. data/lib/capistrano/configuration/roles.rb +28 -2
  30. data/lib/capistrano/configuration/servers.rb +51 -10
  31. data/lib/capistrano/configuration/variables.rb +3 -3
  32. data/lib/capistrano/configuration.rb +20 -4
  33. data/lib/capistrano/errors.rb +12 -8
  34. data/lib/capistrano/ext/multistage.rb +62 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +1 -1
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +112 -5
  39. data/lib/capistrano/processable.rb +55 -0
  40. data/lib/capistrano/recipes/compat.rb +2 -2
  41. data/lib/capistrano/recipes/deploy/assets.rb +185 -0
  42. data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
  43. data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
  44. data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
  45. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  46. data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
  47. data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
  48. data/lib/capistrano/recipes/deploy/scm/cvs.rb +10 -8
  49. data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
  50. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  51. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
  52. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  53. data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
  54. data/lib/capistrano/recipes/deploy/scm/subversion.rb +35 -17
  55. data/lib/capistrano/recipes/deploy/scm.rb +1 -1
  56. data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
  57. data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
  58. data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
  59. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
  60. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  61. data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
  62. data/lib/capistrano/recipes/deploy.rb +265 -123
  63. data/lib/capistrano/recipes/standard.rb +1 -1
  64. data/lib/capistrano/role.rb +102 -0
  65. data/lib/capistrano/server_definition.rb +6 -1
  66. data/lib/capistrano/shell.rb +30 -33
  67. data/lib/capistrano/ssh.rb +46 -60
  68. data/lib/capistrano/task_definition.rb +16 -8
  69. data/lib/capistrano/transfer.rb +218 -0
  70. data/lib/capistrano/version.rb +6 -17
  71. data/lib/capistrano.rb +4 -1
  72. data/test/cli/execute_test.rb +3 -3
  73. data/test/cli/help_test.rb +33 -7
  74. data/test/cli/options_test.rb +109 -6
  75. data/test/cli/ui_test.rb +2 -2
  76. data/test/cli_test.rb +3 -3
  77. data/test/command_test.rb +144 -124
  78. data/test/configuration/actions/file_transfer_test.rb +41 -20
  79. data/test/configuration/actions/inspect_test.rb +21 -7
  80. data/test/configuration/actions/invocation_test.rb +91 -30
  81. data/test/configuration/alias_task_test.rb +118 -0
  82. data/test/configuration/callbacks_test.rb +41 -46
  83. data/test/configuration/connections_test.rb +187 -36
  84. data/test/configuration/execution_test.rb +18 -2
  85. data/test/configuration/loading_test.rb +17 -4
  86. data/test/configuration/namespace_dsl_test.rb +54 -5
  87. data/test/configuration/roles_test.rb +114 -4
  88. data/test/configuration/servers_test.rb +97 -4
  89. data/test/configuration/variables_test.rb +12 -2
  90. data/test/configuration_test.rb +9 -13
  91. data/test/deploy/local_dependency_test.rb +76 -0
  92. data/test/deploy/remote_dependency_test.rb +146 -0
  93. data/test/deploy/scm/accurev_test.rb +23 -0
  94. data/test/deploy/scm/base_test.rb +1 -1
  95. data/test/deploy/scm/bzr_test.rb +51 -0
  96. data/test/deploy/scm/darcs_test.rb +37 -0
  97. data/test/deploy/scm/git_test.rb +221 -0
  98. data/test/deploy/scm/mercurial_test.rb +134 -0
  99. data/test/deploy/scm/none_test.rb +35 -0
  100. data/test/deploy/scm/perforce_test.rb +23 -0
  101. data/test/deploy/scm/subversion_test.rb +40 -0
  102. data/test/deploy/strategy/copy_test.rb +240 -26
  103. data/test/extensions_test.rb +2 -2
  104. data/test/logger_formatting_test.rb +149 -0
  105. data/test/logger_test.rb +13 -2
  106. data/test/recipes_test.rb +25 -0
  107. data/test/role_test.rb +11 -0
  108. data/test/server_definition_test.rb +15 -2
  109. data/test/shell_test.rb +33 -1
  110. data/test/ssh_test.rb +40 -24
  111. data/test/task_definition_test.rb +18 -2
  112. data/test/transfer_test.rb +168 -0
  113. data/test/utils.rb +27 -33
  114. metadata +215 -102
  115. data/MIT-LICENSE +0 -20
  116. data/README +0 -43
  117. data/examples/sample.rb +0 -14
  118. data/lib/capistrano/gateway.rb +0 -131
  119. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  120. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
  121. data/lib/capistrano/recipes/upgrade.rb +0 -33
  122. data/lib/capistrano/upload.rb +0 -146
  123. data/test/gateway_test.rb +0 -167
  124. data/test/upload_test.rb +0 -131
  125. data/test/version_test.rb +0 -24
@@ -8,22 +8,11 @@ module Capistrano
8
8
  base.send :alias_method, :initialize, :initialize_with_execution
9
9
  end
10
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
11
  # A struct for representing a single instance of an invoked task.
22
12
  TaskCallFrame = Struct.new(:task, :rollback)
23
13
 
24
14
  def initialize_with_execution(*args) #:nodoc:
25
15
  initialize_without_execution(*args)
26
- @task_call_frames = []
27
16
  end
28
17
  private :initialize_with_execution
29
18
 
@@ -32,6 +21,25 @@ module Capistrano
32
21
  !rollback_requests.nil?
33
22
  end
34
23
 
24
+ # The call stack of the tasks. The currently executing task may inspect
25
+ # this to see who its caller was. The current task is always the last
26
+ # element of this stack.
27
+ def task_call_frames
28
+ Thread.current[:task_call_frames] ||= []
29
+ end
30
+
31
+
32
+ # The stack of tasks that have registered rollback handlers within the
33
+ # current transaction. If this is nil, then there is no transaction
34
+ # that is currently active.
35
+ def rollback_requests
36
+ Thread.current[:rollback_requests]
37
+ end
38
+
39
+ def rollback_requests=(rollback_requests)
40
+ Thread.current[:rollback_requests] = rollback_requests
41
+ end
42
+
35
43
  # Invoke a set of tasks in a transaction. If any task fails (raises an
36
44
  # exception), all tasks executed within the transaction are inspected to
37
45
  # see if they have an associated on_rollback hook, and if so, that hook
@@ -44,14 +52,14 @@ module Capistrano
44
52
 
45
53
  logger.info "transaction: start"
46
54
  begin
47
- @rollback_requests = []
55
+ self.rollback_requests = []
48
56
  yield
49
57
  logger.info "transaction: commit"
50
58
  rescue Object => e
51
59
  rollback!
52
60
  raise
53
61
  ensure
54
- @rollback_requests = nil
62
+ self.rollback_requests = nil
55
63
  end
56
64
  end
57
65
 
@@ -60,8 +68,9 @@ module Capistrano
60
68
  # hook will be executed.
61
69
  def on_rollback(&block)
62
70
  if transaction?
71
+ # don't note a new rollback request if one has already been set
72
+ rollback_requests << task_call_frames.last unless task_call_frames.last.rollback
63
73
  task_call_frames.last.rollback = block
64
- rollback_requests << task_call_frames.last
65
74
  end
66
75
  end
67
76
 
@@ -72,12 +81,12 @@ module Capistrano
72
81
  task_call_frames.last.task
73
82
  end
74
83
 
75
- # Executes the task with the given name, including the before and after
76
- # hooks.
84
+ # Executes the task with the given name, without invoking any associated
85
+ # callbacks.
77
86
  def execute_task(task)
78
87
  logger.debug "executing `#{task.fully_qualified_name}'"
79
88
  push_task_call_frame(task)
80
- task.namespace.instance_eval(&task.body)
89
+ invoke_task_directly(task)
81
90
  ensure
82
91
  pop_task_call_frame
83
92
  end
@@ -98,6 +107,8 @@ module Capistrano
98
107
  protected
99
108
 
100
109
  def rollback!
110
+ return if Thread.current[:rollback_requests].nil?
111
+
101
112
  # throw the task back on the stack so that roles are properly
102
113
  # interpreted in the scope of the task in question.
103
114
  rollback_requests.reverse.each do |frame|
@@ -121,6 +132,11 @@ module Capistrano
121
132
  def pop_task_call_frame
122
133
  task_call_frames.pop
123
134
  end
135
+
136
+ # Invokes the task's body directly, without setting up the call frame.
137
+ def invoke_task_directly(task)
138
+ task.namespace.instance_eval(&task.body)
139
+ end
124
140
  end
125
141
  end
126
- end
142
+ end
@@ -25,6 +25,28 @@ module Capistrano
25
25
  def instance=(config)
26
26
  Thread.current[:capistrano_configuration] = config
27
27
  end
28
+
29
+ # Used internally by Capistrano to track which recipes have been loaded
30
+ # via require, so that they may be successfully reloaded when require
31
+ # is called again.
32
+ def recipes_per_feature
33
+ @recipes_per_feature ||= {}
34
+ end
35
+
36
+ # Used internally to determine what the current "feature" being
37
+ # required is. This is used to track which files load which recipes
38
+ # via require.
39
+ def current_feature
40
+ Thread.current[:capistrano_current_feature]
41
+ end
42
+
43
+ # Used internally to specify the current file being required, so that
44
+ # any recipes loaded by that file can be remembered. This allows
45
+ # recipes loaded via require to be correctly reloaded in different
46
+ # Configuration instances in the same Ruby instance.
47
+ def current_feature=(feature)
48
+ Thread.current[:capistrano_current_feature] = feature
49
+ end
28
50
  end
29
51
 
30
52
  # The load paths used for locating recipe files.
@@ -33,6 +55,7 @@ module Capistrano
33
55
  def initialize_with_loading(*args) #:nodoc:
34
56
  initialize_without_loading(*args)
35
57
  @load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))]
58
+ @loaded_features = []
36
59
  end
37
60
  private :initialize_with_loading
38
61
 
@@ -66,9 +89,11 @@ module Capistrano
66
89
  load_from_file(options[:file], options[:name])
67
90
 
68
91
  elsif options[:string]
92
+ remember_load(options) unless options[:reloading]
69
93
  instance_eval(options[:string], options[:name] || "<eval>")
70
94
 
71
95
  elsif options[:proc]
96
+ remember_load(options) unless options[:reloading]
72
97
  instance_eval(&options[:proc])
73
98
 
74
99
  else
@@ -80,12 +105,62 @@ module Capistrano
80
105
  # with the exception that it sets the receiver as the "current" configuration
81
106
  # so that third-party task bundles can include themselves relative to
82
107
  # that configuration.
108
+ #
109
+ # This is a bit more complicated than an initial review would seem to
110
+ # necessitate, but the use case that complicates things is this: An
111
+ # advanced user wants to embed capistrano, and needs to instantiate
112
+ # more than one capistrano configuration at a time. They also want each
113
+ # configuration to require a third-party capistrano extension. Using a
114
+ # naive require implementation, this would allow the first configuration
115
+ # to successfully load the third-party extension, but the require would
116
+ # fail for the second configuration because the extension has already
117
+ # been loaded.
118
+ #
119
+ # To work around this, we do a few things:
120
+ #
121
+ # 1. Each time a 'require' is invoked inside of a capistrano recipe,
122
+ # we remember the arguments (see "current_feature").
123
+ # 2. Each time a 'load' is invoked inside of a capistrano recipe, and
124
+ # "current_feature" is not nil (meaning we are inside of a pending
125
+ # require) we remember the options (see "remember_load" and
126
+ # "recipes_per_feature").
127
+ # 3. Each time a 'require' is invoked inside of a capistrano recipe,
128
+ # we check to see if this particular configuration has ever seen these
129
+ # arguments to require (see @loaded_features), and if not, we proceed
130
+ # as if the file had never been required. If the superclass' require
131
+ # returns false (meaning, potentially, that the file has already been
132
+ # required), then we look in the recipes_per_feature collection and
133
+ # load any remembered recipes from there.
134
+ #
135
+ # It's kind of a bear, but it works, and works transparently. Note that
136
+ # a simpler implementation would just muck with $", allowing files to be
137
+ # required multiple times, but that will cause warnings (and possibly
138
+ # errors) if the file to be required contains constant definitions and
139
+ # such, alongside (or instead of) capistrano recipe definitions.
83
140
  def require(*args) #:nodoc:
84
- original, self.class.instance = self.class.instance, self
85
- super
86
- ensure
87
- # restore the original, so that require's can be nested
88
- self.class.instance = original
141
+ # look to see if this specific configuration instance has ever seen
142
+ # these arguments to require before
143
+ if @loaded_features.include?(args)
144
+ return false
145
+ end
146
+
147
+ @loaded_features << args
148
+ begin
149
+ original_instance, self.class.instance = self.class.instance, self
150
+ original_feature, self.class.current_feature = self.class.current_feature, args
151
+
152
+ result = super
153
+ if !result # file has been required previously, load up the remembered recipes
154
+ list = self.class.recipes_per_feature[args] || []
155
+ list.each { |options| load(options.merge(:reloading => true)) }
156
+ end
157
+
158
+ return result
159
+ ensure
160
+ # restore the original, so that require's can be nested
161
+ self.class.instance = original_instance
162
+ self.class.current_feature = original_feature
163
+ end
89
164
  end
90
165
 
91
166
  private
@@ -93,7 +168,7 @@ module Capistrano
93
168
  # Load a recipe from the named file. If +name+ is given, the file will
94
169
  # be reported using that name.
95
170
  def load_from_file(file, name=nil)
96
- file = find_file_in_load_path(file) unless file[0] == ?/
171
+ file = find_file_in_load_path(file) unless File.file?(file)
97
172
  load :string => File.read(file), :name => name || file
98
173
  end
99
174
 
@@ -107,6 +182,16 @@ module Capistrano
107
182
 
108
183
  raise LoadError, "no such file to load -- #{file}"
109
184
  end
185
+
186
+ # If a file is being required, the options associated with loading a
187
+ # recipe are remembered in the recipes_per_feature archive under the
188
+ # name of the file currently being required.
189
+ def remember_load(options)
190
+ if self.class.current_feature
191
+ list = (self.class.recipes_per_feature[self.class.current_feature] ||= [])
192
+ list << options
193
+ end
194
+ end
110
195
  end
111
196
  end
112
197
  end
@@ -0,0 +1,75 @@
1
+ # Add custom log formatters
2
+ #
3
+ # Passing a hash or a array of hashes with custom log formatters.
4
+ #
5
+ # Add the following to your deploy.rb or in your ~/.caprc
6
+ #
7
+ # == Example:
8
+ #
9
+ # capistrano_log_formatters = [
10
+ # { :match => /command finished/, :color => :hide, :priority => 10, :prepend => "$$$" },
11
+ # { :match => /executing command/, :color => :blue, :priority => 10, :style => :underscore, :timestamp => true },
12
+ # { :match => /^transaction: commit$/, :color => :magenta, :priority => 10, :style => :blink },
13
+ # { :match => /git/, :color => :white, :priority => 20, :style => :reverse }
14
+ # ]
15
+ #
16
+ # format_logs capistrano_log_formatters
17
+ #
18
+ # You can call format_logs multiple times, with either a hash or an array of hashes.
19
+ #
20
+ # == Colors:
21
+ #
22
+ # :color can have the following values:
23
+ #
24
+ # * :hide (hides the row completely)
25
+ # * :none
26
+ # * :black
27
+ # * :red
28
+ # * :green
29
+ # * :yellow
30
+ # * :blue
31
+ # * :magenta
32
+ # * :cyan
33
+ # * :white
34
+ #
35
+ # == Styles:
36
+ #
37
+ # :style can have the following values:
38
+ #
39
+ # * :bright
40
+ # * :dim
41
+ # * :underscore
42
+ # * :blink
43
+ # * :reverse
44
+ # * :hidden
45
+ #
46
+ #
47
+ # == Text alterations
48
+ #
49
+ # :prepend gives static text to be prepended to the output
50
+ # :replace replaces the matched text in the output
51
+ # :timestamp adds the current time before the output
52
+
53
+ module Capistrano
54
+ class Configuration
55
+ module LogFormatters
56
+ def log_formatter(options)
57
+ if options.class == Array
58
+ options.each do |option|
59
+ Capistrano::Logger.add_formatter(option)
60
+ end
61
+ else
62
+ Capistrano::Logger.add_formatter(options)
63
+ end
64
+ end
65
+
66
+ def default_log_formatters(formatters)
67
+ default_formatters = [*formatters]
68
+ end
69
+
70
+ def disable_log_formatters
71
+ @logger.disable_formatters = true
72
+ end
73
+ end
74
+ end
75
+ end
@@ -32,6 +32,12 @@ module Capistrano
32
32
  end
33
33
  private :initialize_with_namespaces
34
34
 
35
+ # Returns the top-level namespace (the one with no parent).
36
+ def top
37
+ return parent.top if parent
38
+ return self
39
+ end
40
+
35
41
  # Returns the fully-qualified name of this namespace, or nil if the
36
42
  # namespace is at the top-level.
37
43
  def fully_qualified_name
@@ -61,7 +67,7 @@ module Capistrano
61
67
  raise ArgumentError, "expected a block" unless block_given?
62
68
 
63
69
  namespace_already_defined = namespaces.key?(name)
64
- if all_methods.include?(name.to_s) && !namespace_already_defined
70
+ if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
65
71
  thing = tasks.key?(name) ? "task" : "method"
66
72
  raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
67
73
  end
@@ -86,17 +92,22 @@ module Capistrano
86
92
  raise ArgumentError, "expected a block" unless block_given?
87
93
 
88
94
  task_already_defined = tasks.key?(name)
89
- if all_methods.include?(name.to_s) && !task_already_defined
95
+ if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
90
96
  thing = namespaces.key?(name) ? "namespace" : "method"
91
97
  raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
92
98
  end
93
99
 
94
- tasks[name] = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
95
100
 
96
- if !task_already_defined
97
- metaclass = class << self; self; end
98
- metaclass.send(:define_method, name) { execute_task(tasks[name]) }
99
- end
101
+ task = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
102
+
103
+ define_task(task)
104
+ end
105
+
106
+ def define_task(task)
107
+ tasks[task.name] = task
108
+
109
+ metaclass = class << self; self; end
110
+ metaclass.send(:define_method, task.name) { execute_task(tasks[task.name]) }
100
111
  end
101
112
 
102
113
  # Find the task with the given name, where name is the fully-qualified
@@ -110,7 +121,8 @@ module Capistrano
110
121
 
111
122
  ns = self
112
123
  until parts.empty?
113
- ns = ns.namespaces[parts.shift.to_sym]
124
+ next_part = parts.shift
125
+ ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
114
126
  return nil if ns.nil?
115
127
  end
116
128
 
@@ -144,7 +156,7 @@ module Capistrano
144
156
  return nil if parent.nil?
145
157
  return tasks[DEFAULT_TASK]
146
158
  end
147
-
159
+
148
160
  # Returns the tasks in this namespace as an array of TaskDefinition
149
161
  # objects. If a non-false parameter is given, all tasks in all
150
162
  # namespaces under this namespace will be returned as well.
@@ -170,8 +182,8 @@ module Capistrano
170
182
  raise NotImplementedError, "roles cannot be defined in a namespace"
171
183
  end
172
184
 
173
- def respond_to?(sym)
174
- super || parent.respond_to?(sym)
185
+ def respond_to?(sym, include_priv=false)
186
+ super || parent.respond_to?(sym, include_priv)
175
187
  end
176
188
 
177
189
  def method_missing(sym, *args, &block)
@@ -182,9 +194,30 @@ module Capistrano
182
194
  end
183
195
  end
184
196
 
197
+ include Capistrano::Configuration::AliasTask
185
198
  include Capistrano::Configuration::Namespaces
186
199
  undef :desc, :next_description
187
200
  end
188
201
  end
189
202
  end
190
- end
203
+ end
204
+
205
+ module Kernel
206
+ class << self
207
+ alias_method :method_added_without_capistrano, :method_added
208
+
209
+ # Detect method additions to Kernel and remove them in the Namespace class
210
+ def method_added(name)
211
+ result = method_added_without_capistrano(name)
212
+ return result if self != Kernel
213
+
214
+ namespace = Capistrano::Configuration::Namespaces::Namespace
215
+
216
+ if namespace.method_defined?(name) && namespace.instance_method(name).owner == Kernel
217
+ namespace.send :undef_method, name
218
+ end
219
+
220
+ result
221
+ end
222
+ end
223
+ end
@@ -1,4 +1,5 @@
1
1
  require 'capistrano/server_definition'
2
+ require 'capistrano/role'
2
3
 
3
4
  module Capistrano
4
5
  class Configuration
@@ -15,7 +16,7 @@ module Capistrano
15
16
 
16
17
  def initialize_with_roles(*args) #:nodoc:
17
18
  initialize_without_roles(*args)
18
- @roles = Hash.new { |h,k| h[k] = [] }
19
+ @roles = Hash.new { |h,k| h[k] = Role.new }
19
20
  end
20
21
 
21
22
  # Define a new role and its associated servers. You must specify at least
@@ -41,11 +42,36 @@ module Capistrano
41
42
  # that call to "role":
42
43
  #
43
44
  # role :web, "web2", "web3", :user => "www", :port => 2345
44
- def role(which, *args)
45
+ def role(which, *args, &block)
45
46
  options = args.last.is_a?(Hash) ? args.pop : {}
46
47
  which = which.to_sym
48
+
49
+ # The roles Hash is defined so that unrecognized keys always auto-initialize
50
+ # to a new Role instance (see the assignment in the initialize_with_roles method,
51
+ # above). However, we explicitly assign here so that role declarations will
52
+ # vivify the role object even if there are no server arguments. (Otherwise,
53
+ # role(:app) won't actually instantiate a Role object for :app.)
54
+ roles[which] ||= Role.new
55
+
56
+ roles[which].push(block, options) if block_given?
47
57
  args.each { |host| roles[which] << ServerDefinition.new(host, options) }
48
58
  end
59
+
60
+ # An alternative way to associate servers with roles. If you have a server
61
+ # that participates in multiple roles, this can be a DRYer way to describe
62
+ # the relationships. Pass the host definition as the first parameter, and
63
+ # the roles as the remaining parameters:
64
+ #
65
+ # server "master.example.com", :web, :app
66
+ def server(host, *roles)
67
+ options = roles.last.is_a?(Hash) ? roles.pop : {}
68
+ raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
69
+ roles.each { |name| role(name, host, options) }
70
+ end
71
+
72
+ def role_names_for_host(host)
73
+ roles.map {|role_name, role| role_name if role.include?(host) }.compact || []
74
+ end
49
75
  end
50
76
  end
51
77
  end
@@ -13,14 +13,22 @@ module Capistrano
13
13
  # The options hash may include a :hosts option (which should specify
14
14
  # an array of host names or ServerDefinition instances), a :roles
15
15
  # option (specifying an array of roles), an :only option (specifying
16
- # a hash of key/value pairs that any matching server must match), and
17
- # an :exception option (like :only, but the inverse).
16
+ # a hash of key/value pairs that any matching server must match),
17
+ # an :exception option (like :only, but the inverse), and a
18
+ # :skip_hostfilter option to ignore the HOSTFILTER environment variable
19
+ # described below.
18
20
  #
19
21
  # Additionally, if the HOSTS environment variable is set, it will take
20
22
  # precedence over any other options. Similarly, the ROLES environment
21
23
  # variable will take precedence over other options. If both HOSTS and
22
24
  # ROLES are given, HOSTS wins.
23
25
  #
26
+ # Yet additionally, if the HOSTFILTER environment variable is set, it
27
+ # will limit the result to hosts found in that (comma-separated) list.
28
+ #
29
+ # If the HOSTROLEFILTER environment variable is set, it will limit the
30
+ # result to hosts found in that (comma-separated) list of roles
31
+ #
24
32
  # Usage:
25
33
  #
26
34
  # # return all known servers
@@ -34,23 +42,57 @@ module Capistrano
34
42
  # # returns the given hosts, translated to ServerDefinition objects
35
43
  # servers = find_servers :hosts => "jamis@example.host.com"
36
44
  def find_servers(options={})
45
+ return [] if options.key?(:hosts) && (options[:hosts].nil? || [] == options[:hosts])
46
+ return [] if options.key?(:roles) && (options[:roles].nil? || [] == options[:roles])
47
+
37
48
  hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
38
- roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
39
- only = options[:only] || {}
40
- except = options[:except] || {}
41
49
 
42
50
  if hosts.any?
43
- hosts.uniq
51
+ if options[:skip_hostfilter]
52
+ hosts.uniq
53
+ else
54
+ filter_server_list(hosts.uniq)
55
+ end
44
56
  else
45
- servers = roles.inject([]) { |list, role| list.concat(self.roles[role]) }
57
+ roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
58
+ roles = roles & Array(options[:roles]) if preserve_roles && !options[:roles].nil?
59
+
60
+ only = options[:only] || {}
61
+ except = options[:except] || {}
62
+
63
+ # If we don't have a def for a role it means its bogus, skip it so higher level can handle
64
+ servers = roles.inject([]) { |list, role| list.concat(self.roles[role] || []) }
46
65
  servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
47
66
  servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } }
48
- servers.uniq
67
+
68
+ if options[:skip_hostfilter]
69
+ servers.uniq
70
+ else
71
+ filter_server_list(servers.uniq)
72
+ end
49
73
  end
50
74
  end
51
75
 
52
76
  protected
53
77
 
78
+ def filter_server_list(servers)
79
+ return servers unless ENV['HOSTFILTER'] or ENV['HOSTROLEFILTER']
80
+ if ENV['HOSTFILTER']
81
+ filters = ENV['HOSTFILTER'].split(/,/)
82
+ servers.select { |server| filters.include?(server.host) }
83
+ elsif ENV['HOSTROLEFILTER']
84
+ filters = ENV['HOSTROLEFILTER'].split(/,/).map do |role|
85
+ local_roles = roles[role.to_sym]
86
+ if local_roles.is_a? Array
87
+ roles[role.to_sym]
88
+ else
89
+ roles[role.to_sym].servers
90
+ end
91
+ end.flatten
92
+ servers.select { |server| filters.include?(server) }
93
+ end
94
+ end
95
+
54
96
  def server_list_from(hosts)
55
97
  hosts = hosts.split(/,/) if String === hosts
56
98
  hosts = build_list(hosts)
@@ -62,7 +104,6 @@ module Capistrano
62
104
  roles = build_list(roles)
63
105
  roles.map do |role|
64
106
  role = String === role ? role.strip.to_sym : role
65
- raise ArgumentError, "unknown role `#{role}'" unless self.roles.key?(role)
66
107
  role
67
108
  end
68
109
  end
@@ -72,4 +113,4 @@ module Capistrano
72
113
  end
73
114
  end
74
115
  end
75
- end
116
+ end
@@ -111,8 +111,8 @@ module Capistrano
111
111
  end
112
112
  private :protect
113
113
 
114
- def respond_to_with_variables?(sym) #:nodoc:
115
- @variables.has_key?(sym) || respond_to_without_variables?(sym)
114
+ def respond_to_with_variables?(sym, include_priv=false) #:nodoc:
115
+ @variables.has_key?(sym.to_sym) || respond_to_without_variables?(sym, include_priv)
116
116
  end
117
117
 
118
118
  def method_missing_with_variables(sym, *args, &block) #:nodoc:
@@ -124,4 +124,4 @@ module Capistrano
124
124
  end
125
125
  end
126
126
  end
127
- end
127
+ end
@@ -1,9 +1,11 @@
1
1
  require 'capistrano/logger'
2
2
 
3
+ require 'capistrano/configuration/alias_task'
3
4
  require 'capistrano/configuration/callbacks'
4
5
  require 'capistrano/configuration/connections'
5
6
  require 'capistrano/configuration/execution'
6
7
  require 'capistrano/configuration/loading'
8
+ require 'capistrano/configuration/log_formatters'
7
9
  require 'capistrano/configuration/namespaces'
8
10
  require 'capistrano/configuration/roles'
9
11
  require 'capistrano/configuration/servers'
@@ -19,10 +21,13 @@ module Capistrano
19
21
  # define roles, and set configuration variables.
20
22
  class Configuration
21
23
  # The logger instance defined for this configuration.
22
- attr_accessor :logger
24
+ attr_accessor :debug, :logger, :dry_run, :preserve_roles
23
25
 
24
- def initialize #:nodoc:
25
- @logger = Logger.new
26
+ def initialize(options={}) #:nodoc:
27
+ @debug = false
28
+ @dry_run = false
29
+ @preserve_roles = false
30
+ @logger = Logger.new(options)
26
31
  end
27
32
 
28
33
  # make the DSL easier to read when using lazy evaluation via lambdas
@@ -30,12 +35,23 @@ module Capistrano
30
35
 
31
36
  # The includes must come at the bottom, since they may redefine methods
32
37
  # defined in the base class.
33
- include Connections, Execution, Loading, Namespaces, Roles, Servers, Variables
38
+ include AliasTask, Connections, Execution, Loading, LogFormatters, Namespaces, Roles, Servers, Variables
34
39
 
35
40
  # Mix in the actions
36
41
  include Actions::FileTransfer, Actions::Inspect, Actions::Invocation
37
42
 
38
43
  # Must mix last, because it hooks into previously defined methods
39
44
  include Callbacks
45
+
46
+ (self.instance_methods & Kernel.methods).select do |name|
47
+ # Select the instance methods owned by the Configuration class.
48
+ self.instance_method(name).owner.to_s.start_with?("Capistrano::Configuration")
49
+ end.select do |name|
50
+ # Of those, select methods that are being shadowed by the Kernel module in the Namespace class.
51
+ Namespaces::Namespace.method_defined?(name) && Namespaces::Namespace.instance_method(name).owner == Kernel
52
+ end.each do |name|
53
+ # Undefine the shadowed methods, since we want Namespace objects to defer handling to the Configuration object.
54
+ Namespaces::Namespace.send(:undef_method, name)
55
+ end
40
56
  end
41
57
  end