capistrano 1.4.2 → 2.0.0

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