capistrano 1.4.2 → 2.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 (113) hide show
  1. data/CHANGELOG +140 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README +22 -14
  4. data/bin/cap +1 -8
  5. data/bin/capify +77 -0
  6. data/examples/sample.rb +10 -109
  7. data/lib/capistrano.rb +1 -0
  8. data/lib/capistrano/callback.rb +41 -0
  9. data/lib/capistrano/cli.rb +17 -317
  10. data/lib/capistrano/cli/execute.rb +82 -0
  11. data/lib/capistrano/cli/help.rb +102 -0
  12. data/lib/capistrano/cli/help.txt +53 -0
  13. data/lib/capistrano/cli/options.rb +183 -0
  14. data/lib/capistrano/cli/ui.rb +28 -0
  15. data/lib/capistrano/command.rb +62 -29
  16. data/lib/capistrano/configuration.rb +25 -226
  17. data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
  18. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  19. data/lib/capistrano/configuration/actions/invocation.rb +127 -0
  20. data/lib/capistrano/configuration/callbacks.rb +148 -0
  21. data/lib/capistrano/configuration/connections.rb +159 -0
  22. data/lib/capistrano/configuration/execution.rb +126 -0
  23. data/lib/capistrano/configuration/loading.rb +112 -0
  24. data/lib/capistrano/configuration/namespaces.rb +190 -0
  25. data/lib/capistrano/configuration/roles.rb +51 -0
  26. data/lib/capistrano/configuration/servers.rb +75 -0
  27. data/lib/capistrano/configuration/variables.rb +127 -0
  28. data/lib/capistrano/errors.rb +15 -0
  29. data/lib/capistrano/extensions.rb +27 -8
  30. data/lib/capistrano/gateway.rb +54 -29
  31. data/lib/capistrano/logger.rb +11 -11
  32. data/lib/capistrano/recipes/compat.rb +32 -0
  33. data/lib/capistrano/recipes/deploy.rb +483 -0
  34. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  35. data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
  36. data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
  37. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  38. data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
  39. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  40. data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
  41. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  42. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
  43. data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
  44. data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
  45. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  46. data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
  47. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  48. data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
  49. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  50. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  51. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
  52. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  53. data/lib/capistrano/recipes/standard.rb +26 -276
  54. data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
  55. data/lib/capistrano/recipes/upgrade.rb +33 -0
  56. data/lib/capistrano/server_definition.rb +51 -0
  57. data/lib/capistrano/shell.rb +125 -81
  58. data/lib/capistrano/ssh.rb +80 -36
  59. data/lib/capistrano/task_definition.rb +69 -0
  60. data/lib/capistrano/upload.rb +146 -0
  61. data/lib/capistrano/version.rb +13 -17
  62. data/test/cli/execute_test.rb +132 -0
  63. data/test/cli/help_test.rb +139 -0
  64. data/test/cli/options_test.rb +226 -0
  65. data/test/cli/ui_test.rb +28 -0
  66. data/test/cli_test.rb +17 -0
  67. data/test/command_test.rb +284 -25
  68. data/test/configuration/actions/file_transfer_test.rb +40 -0
  69. data/test/configuration/actions/inspect_test.rb +62 -0
  70. data/test/configuration/actions/invocation_test.rb +195 -0
  71. data/test/configuration/callbacks_test.rb +206 -0
  72. data/test/configuration/connections_test.rb +288 -0
  73. data/test/configuration/execution_test.rb +159 -0
  74. data/test/configuration/loading_test.rb +119 -0
  75. data/test/configuration/namespace_dsl_test.rb +283 -0
  76. data/test/configuration/roles_test.rb +47 -0
  77. data/test/configuration/servers_test.rb +90 -0
  78. data/test/configuration/variables_test.rb +180 -0
  79. data/test/configuration_test.rb +60 -212
  80. data/test/deploy/scm/base_test.rb +55 -0
  81. data/test/deploy/strategy/copy_test.rb +146 -0
  82. data/test/extensions_test.rb +69 -0
  83. data/test/fixtures/cli_integration.rb +5 -0
  84. data/test/fixtures/custom.rb +2 -2
  85. data/test/gateway_test.rb +167 -0
  86. data/test/logger_test.rb +123 -0
  87. data/test/server_definition_test.rb +108 -0
  88. data/test/shell_test.rb +64 -0
  89. data/test/ssh_test.rb +67 -154
  90. data/test/task_definition_test.rb +101 -0
  91. data/test/upload_test.rb +131 -0
  92. data/test/utils.rb +31 -39
  93. data/test/version_test.rb +24 -0
  94. metadata +145 -98
  95. data/THANKS +0 -4
  96. data/lib/capistrano/actor.rb +0 -567
  97. data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
  98. data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
  99. data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
  100. data/lib/capistrano/generators/rails/loader.rb +0 -20
  101. data/lib/capistrano/scm/base.rb +0 -61
  102. data/lib/capistrano/scm/baz.rb +0 -118
  103. data/lib/capistrano/scm/bzr.rb +0 -70
  104. data/lib/capistrano/scm/cvs.rb +0 -129
  105. data/lib/capistrano/scm/darcs.rb +0 -27
  106. data/lib/capistrano/scm/mercurial.rb +0 -83
  107. data/lib/capistrano/scm/perforce.rb +0 -139
  108. data/lib/capistrano/scm/subversion.rb +0 -128
  109. data/lib/capistrano/transfer.rb +0 -97
  110. data/lib/capistrano/utils.rb +0 -26
  111. data/test/actor_test.rb +0 -402
  112. data/test/scm/cvs_test.rb +0 -196
  113. data/test/scm/subversion_test.rb +0 -145
@@ -0,0 +1,126 @@
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
@@ -0,0 +1,112 @@
1
+ module Capistrano
2
+ class Configuration
3
+ module Loading
4
+ def self.included(base) #:nodoc:
5
+ base.send :alias_method, :initialize_without_loading, :initialize
6
+ base.send :alias_method, :initialize, :initialize_with_loading
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Used by third-party task bundles to identify the capistrano
12
+ # configuration that is loading them. Its return value is not reliable
13
+ # in other contexts. If +require_config+ is not false, an exception
14
+ # will be raised if the current configuration is not set.
15
+ def instance(require_config=false)
16
+ config = Thread.current[:capistrano_configuration]
17
+ if require_config && config.nil?
18
+ raise LoadError, "Please require this file from within a Capistrano recipe"
19
+ end
20
+ config
21
+ end
22
+
23
+ # Used internally by Capistrano to specify the current configuration
24
+ # before loading a third-party task bundle.
25
+ def instance=(config)
26
+ Thread.current[:capistrano_configuration] = config
27
+ end
28
+ end
29
+
30
+ # The load paths used for locating recipe files.
31
+ attr_reader :load_paths
32
+
33
+ def initialize_with_loading(*args) #:nodoc:
34
+ initialize_without_loading(*args)
35
+ @load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))]
36
+ end
37
+ private :initialize_with_loading
38
+
39
+ # Load a configuration file or string into this configuration.
40
+ #
41
+ # Usage:
42
+ #
43
+ # load("recipe"):
44
+ # Look for and load the contents of 'recipe.rb' into this
45
+ # configuration.
46
+ #
47
+ # load(:file => "recipe"):
48
+ # same as above
49
+ #
50
+ # load(:string => "set :scm, :subversion"):
51
+ # Load the given string as a configuration specification.
52
+ #
53
+ # load { ... }
54
+ # Load the block in the context of the configuration.
55
+ def load(*args, &block)
56
+ options = args.last.is_a?(Hash) ? args.pop : {}
57
+
58
+ if block
59
+ raise ArgumentError, "loading a block requires 0 arguments" unless options.empty? && args.empty?
60
+ load(:proc => block)
61
+
62
+ elsif args.any?
63
+ args.each { |arg| load options.merge(:file => arg) }
64
+
65
+ elsif options[:file]
66
+ load_from_file(options[:file], options[:name])
67
+
68
+ elsif options[:string]
69
+ instance_eval(options[:string], options[:name] || "<eval>")
70
+
71
+ elsif options[:proc]
72
+ instance_eval(&options[:proc])
73
+
74
+ else
75
+ raise ArgumentError, "don't know how to load #{options.inspect}"
76
+ end
77
+ end
78
+
79
+ # Require another file. This is identical to the standard require method,
80
+ # with the exception that it sets the receiver as the "current" configuration
81
+ # so that third-party task bundles can include themselves relative to
82
+ # that configuration.
83
+ 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
89
+ end
90
+
91
+ private
92
+
93
+ # Load a recipe from the named file. If +name+ is given, the file will
94
+ # be reported using that name.
95
+ def load_from_file(file, name=nil)
96
+ file = find_file_in_load_path(file) unless file[0] == ?/
97
+ load :string => File.read(file), :name => name || file
98
+ end
99
+
100
+ def find_file_in_load_path(file)
101
+ load_paths.each do |path|
102
+ ["", ".rb"].each do |ext|
103
+ name = File.join(path, "#{file}#{ext}")
104
+ return name if File.file?(name)
105
+ end
106
+ end
107
+
108
+ raise LoadError, "no such file to load -- #{file}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,190 @@
1
+ require 'capistrano/task_definition'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Namespaces
6
+ DEFAULT_TASK = :default
7
+
8
+ def self.included(base) #:nodoc:
9
+ base.send :alias_method, :initialize_without_namespaces, :initialize
10
+ base.send :alias_method, :initialize, :initialize_with_namespaces
11
+ end
12
+
13
+ # The name of this namespace. Defaults to +nil+ for the top-level
14
+ # namespace.
15
+ attr_reader :name
16
+
17
+ # The parent namespace of this namespace. Returns +nil+ for the top-level
18
+ # namespace.
19
+ attr_reader :parent
20
+
21
+ # The hash of tasks defined for this namespace.
22
+ attr_reader :tasks
23
+
24
+ # The hash of namespaces defined for this namespace.
25
+ attr_reader :namespaces
26
+
27
+ def initialize_with_namespaces(*args) #:nodoc:
28
+ @name = @parent = nil
29
+ initialize_without_namespaces(*args)
30
+ @tasks = {}
31
+ @namespaces = {}
32
+ end
33
+ private :initialize_with_namespaces
34
+
35
+ # Returns the fully-qualified name of this namespace, or nil if the
36
+ # namespace is at the top-level.
37
+ def fully_qualified_name
38
+ return nil if name.nil?
39
+ [parent.fully_qualified_name, name].compact.join(":")
40
+ end
41
+
42
+ # Describe the next task to be defined. The given text will be attached to
43
+ # the next task that is defined and used as its description.
44
+ def desc(text)
45
+ @next_description = text
46
+ end
47
+
48
+ # Returns the value set by the last, pending "desc" call. If +reset+ is
49
+ # not false, the value will be reset immediately afterwards.
50
+ def next_description(reset=false)
51
+ @next_description
52
+ ensure
53
+ @next_description = nil if reset
54
+ end
55
+
56
+ # Open a namespace in which to define new tasks. If the namespace was
57
+ # defined previously, it will be reopened, otherwise a new namespace
58
+ # will be created for the given name.
59
+ def namespace(name, &block)
60
+ name = name.to_sym
61
+ raise ArgumentError, "expected a block" unless block_given?
62
+
63
+ namespace_already_defined = namespaces.key?(name)
64
+ if all_methods.include?(name.to_s) && !namespace_already_defined
65
+ thing = tasks.key?(name) ? "task" : "method"
66
+ raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
67
+ end
68
+
69
+ namespaces[name] ||= Namespace.new(name, self)
70
+ namespaces[name].instance_eval(&block)
71
+
72
+ # make sure any open description gets terminated
73
+ namespaces[name].desc(nil)
74
+
75
+ if !namespace_already_defined
76
+ metaclass = class << self; self; end
77
+ metaclass.send(:define_method, name) { namespaces[name] }
78
+ end
79
+ end
80
+
81
+ # Describe a new task. If a description is active (see #desc), it is added
82
+ # to the options under the <tt>:desc</tt> key. The new task is added to
83
+ # the namespace.
84
+ def task(name, options={}, &block)
85
+ name = name.to_sym
86
+ raise ArgumentError, "expected a block" unless block_given?
87
+
88
+ task_already_defined = tasks.key?(name)
89
+ if all_methods.include?(name.to_s) && !task_already_defined
90
+ thing = namespaces.key?(name) ? "namespace" : "method"
91
+ raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
92
+ end
93
+
94
+ tasks[name] = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
95
+
96
+ if !task_already_defined
97
+ metaclass = class << self; self; end
98
+ metaclass.send(:define_method, name) { execute_task(tasks[name]) }
99
+ end
100
+ end
101
+
102
+ # Find the task with the given name, where name is the fully-qualified
103
+ # name of the task. This will search into the namespaces and return
104
+ # the referenced task, or nil if no such task can be found. If the name
105
+ # refers to a namespace, the task in that namespace named "default"
106
+ # will be returned instead, if one exists.
107
+ def find_task(name)
108
+ parts = name.to_s.split(/:/)
109
+ tail = parts.pop.to_sym
110
+
111
+ ns = self
112
+ until parts.empty?
113
+ ns = ns.namespaces[parts.shift.to_sym]
114
+ return nil if ns.nil?
115
+ end
116
+
117
+ if ns.namespaces.key?(tail)
118
+ ns = ns.namespaces[tail]
119
+ tail = DEFAULT_TASK
120
+ end
121
+
122
+ ns.tasks[tail]
123
+ end
124
+
125
+ # Given a task name, this will search the current namespace, and all
126
+ # parent namespaces, looking for a task that matches the name, exactly.
127
+ # It returns the task, if found, or nil, if not.
128
+ def search_task(name)
129
+ name = name.to_sym
130
+ ns = self
131
+
132
+ until ns.nil?
133
+ return ns.tasks[name] if ns.tasks.key?(name)
134
+ ns = ns.parent
135
+ end
136
+
137
+ return nil
138
+ end
139
+
140
+ # Returns the default task for this namespace. This will be +nil+ if
141
+ # the namespace is at the top-level, and will otherwise return the
142
+ # task named "default". If no such task exists, +nil+ will be returned.
143
+ def default_task
144
+ return nil if parent.nil?
145
+ return tasks[DEFAULT_TASK]
146
+ end
147
+
148
+ # Returns the tasks in this namespace as an array of TaskDefinition
149
+ # objects. If a non-false parameter is given, all tasks in all
150
+ # namespaces under this namespace will be returned as well.
151
+ def task_list(all=false)
152
+ list = tasks.values
153
+ namespaces.each { |name,space| list.concat(space.task_list(:all)) } if all
154
+ list
155
+ end
156
+
157
+ private
158
+
159
+ def all_methods
160
+ public_methods.concat(protected_methods).concat(private_methods)
161
+ end
162
+
163
+ class Namespace
164
+ def initialize(name, parent)
165
+ @parent = parent
166
+ @name = name
167
+ end
168
+
169
+ def role(*args)
170
+ raise NotImplementedError, "roles cannot be defined in a namespace"
171
+ end
172
+
173
+ def respond_to?(sym)
174
+ super || parent.respond_to?(sym)
175
+ end
176
+
177
+ def method_missing(sym, *args, &block)
178
+ if parent.respond_to?(sym)
179
+ parent.send(sym, *args, &block)
180
+ else
181
+ super
182
+ end
183
+ end
184
+
185
+ include Capistrano::Configuration::Namespaces
186
+ undef :desc, :next_description
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,51 @@
1
+ require 'capistrano/server_definition'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Roles
6
+ def self.included(base) #:nodoc:
7
+ base.send :alias_method, :initialize_without_roles, :initialize
8
+ base.send :alias_method, :initialize, :initialize_with_roles
9
+ end
10
+
11
+ # The hash of roles defined for this configuration. Each entry in the
12
+ # hash points to an array of server definitions that belong in that
13
+ # role.
14
+ attr_reader :roles
15
+
16
+ def initialize_with_roles(*args) #:nodoc:
17
+ initialize_without_roles(*args)
18
+ @roles = Hash.new { |h,k| h[k] = [] }
19
+ end
20
+
21
+ # Define a new role and its associated servers. You must specify at least
22
+ # one host for each role. Also, you can specify additional information
23
+ # (in the form of a Hash) which can be used to more uniquely specify the
24
+ # subset of servers specified by this specific role definition.
25
+ #
26
+ # Usage:
27
+ #
28
+ # role :db, "db1.example.com", "db2.example.com"
29
+ # role :db, "master.example.com", :primary => true
30
+ # role :app, "app1.example.com", "app2.example.com"
31
+ #
32
+ # You can also encode the username and port number for each host in the
33
+ # server string, if needed:
34
+ #
35
+ # role :web, "www@web1.example.com"
36
+ # role :file, "files.example.com:4144"
37
+ # role :db, "admin@db3.example.com:1234"
38
+ #
39
+ # Lastly, username and port number may be passed as options, if that is
40
+ # preferred; note that the options apply to all servers defined in
41
+ # that call to "role":
42
+ #
43
+ # role :web, "web2", "web3", :user => "www", :port => 2345
44
+ def role(which, *args)
45
+ options = args.last.is_a?(Hash) ? args.pop : {}
46
+ which = which.to_sym
47
+ args.each { |host| roles[which] << ServerDefinition.new(host, options) }
48
+ end
49
+ end
50
+ end
51
+ end