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
@@ -15,44 +15,236 @@ module Capistrano
15
15
  # of the source code. If you would rather use the export operation,
16
16
  # you can set the :copy_strategy variable to :export.
17
17
  #
18
- # This deployment strategy supports a special variable,
18
+ # set :copy_strategy, :export
19
+ #
20
+ # For even faster deployments, you can set the :copy_cache variable to
21
+ # true. This will cause deployments to do a new checkout of your
22
+ # repository to a new directory, and then copy that checkout. Subsequent
23
+ # deploys will just resync that copy, rather than doing an entirely new
24
+ # checkout. Additionally, you can specify file patterns to exclude from
25
+ # the copy when using :copy_cache; just set the :copy_exclude variable
26
+ # to a file glob (or an array of globs).
27
+ #
28
+ # set :copy_cache, true
29
+ # set :copy_exclude, ".git/*"
30
+ #
31
+ # Note that :copy_strategy is ignored when :copy_cache is set. Also, if
32
+ # you want the copy cache put somewhere specific, you can set the variable
33
+ # to the path you want, instead of merely 'true':
34
+ #
35
+ # set :copy_cache, "/tmp/caches/myapp"
36
+ #
37
+ # This deployment strategy also supports a special variable,
19
38
  # :copy_compression, which must be one of :gzip, :bz2, or
20
39
  # :zip, and which specifies how the source should be compressed for
21
40
  # transmission to each host.
41
+ #
42
+ # By default, files will be transferred across to the remote machines via 'sftp'. If you prefer
43
+ # to use 'scp' you can set the :copy_via variable to :scp.
44
+ #
45
+ # set :copy_via, :scp
46
+ #
47
+ # There is a possibility to pass a build command that will get
48
+ # executed if your code needs to be compiled or something needs to be
49
+ # done before the code is ready to run.
50
+ #
51
+ # set :build_script, "make all"
52
+ #
53
+ # Note that if you use :copy_cache, the :build_script is used on the
54
+ # cache and thus you get faster compilation if your script does not
55
+ # recompile everything.
22
56
  class Copy < Base
23
57
  # Obtains a copy of the source code locally (via the #command method),
24
58
  # compresses it to a single file, copies that file to all target
25
59
  # servers, and uncompresses it on each of them into the deployment
26
60
  # directory.
27
61
  def deploy!
28
- logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}"
29
- system(command)
30
- File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
31
-
32
- logger.trace "compressing #{destination} to #{filename}"
33
- Dir.chdir(tmpdir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
62
+ copy_cache ? run_copy_cache_strategy : run_copy_strategy
34
63
 
35
- put File.read(filename), remote_filename
36
- run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
64
+ create_revision_file
65
+ compress_repository
66
+ distribute!
37
67
  ensure
38
- FileUtils.rm filename rescue nil
39
- FileUtils.rm_rf destination rescue nil
68
+ rollback_changes
69
+ end
70
+
71
+ def build directory
72
+ execute "running build script on #{directory}" do
73
+ Dir.chdir(directory) { system(build_script) }
74
+ end if build_script
40
75
  end
41
76
 
42
77
  def check!
43
78
  super.check do |d|
44
- d.local.command(source.local.command)
79
+ d.local.command(source.local.command) if source.local.command
45
80
  d.local.command(compress(nil, nil).first)
46
81
  d.remote.command(decompress(nil).first)
47
82
  end
48
83
  end
49
84
 
85
+ # Returns the location of the local copy cache, if the strategy should
86
+ # use a local cache + copy instead of a new checkout/export every
87
+ # time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
88
+ # is +true+, a default cache location will be returned.
89
+ def copy_cache
90
+ @copy_cache ||= configuration[:copy_cache] == true ?
91
+ File.expand_path(configuration[:application], Dir.tmpdir) :
92
+ File.expand_path(configuration[:copy_cache], Dir.pwd) rescue nil
93
+ end
94
+
50
95
  private
51
96
 
97
+ def run_copy_cache_strategy
98
+ copy_repository_to_local_cache
99
+ build copy_cache
100
+ copy_cache_to_staging_area
101
+ end
102
+
103
+ def run_copy_strategy
104
+ copy_repository_to_server
105
+ build destination
106
+ remove_excluded_files if copy_exclude.any?
107
+ end
108
+
109
+ def execute description, &block
110
+ logger.debug description
111
+ handle_system_errors &block
112
+ end
113
+
114
+ def handle_system_errors &block
115
+ block.call
116
+ raise_command_failed if last_command_failed?
117
+ end
118
+
119
+ def refresh_local_cache
120
+ execute "refreshing local cache to revision #{revision} at #{copy_cache}" do
121
+ system(source.sync(revision, copy_cache))
122
+ end
123
+ end
124
+
125
+ def create_local_cache
126
+ execute "preparing local cache at #{copy_cache}" do
127
+ system(source.checkout(revision, copy_cache))
128
+ end
129
+ end
130
+
131
+ def raise_command_failed
132
+ raise Capistrano::Error, "shell command failed with return code #{$?}"
133
+ end
134
+
135
+ def last_command_failed?
136
+ $? != 0
137
+ end
138
+
139
+ def copy_cache_to_staging_area
140
+ execute "copying cache to deployment staging area #{destination}" do
141
+ create_destination
142
+ Dir.chdir(copy_cache) { copy_files(queue_files) }
143
+ end
144
+ end
145
+
146
+ def create_destination
147
+ FileUtils.mkdir_p(destination)
148
+ end
149
+
150
+ def copy_files files
151
+ files.each { |name| process_file(name) }
152
+ end
153
+
154
+ def process_file name
155
+ send "copy_#{filetype(name)}", name
156
+ end
157
+
158
+ def filetype name
159
+ filetype = File.ftype name
160
+ filetype = "file" unless ["link", "directory"].include? filetype
161
+ filetype
162
+ end
163
+
164
+ def copy_link name
165
+ FileUtils.ln_s(File.readlink(name), File.join(destination, name))
166
+ end
167
+
168
+ def copy_directory name
169
+ FileUtils.mkdir(File.join(destination, name))
170
+ copy_files(queue_files(name))
171
+ end
172
+
173
+ def copy_file name
174
+ FileUtils.ln(name, File.join(destination, name))
175
+ end
176
+
177
+ def queue_files directory=nil
178
+ Dir.glob(pattern_for(directory), File::FNM_DOTMATCH).reject! { |file| excluded_files_contain? file }
179
+ end
180
+
181
+ def pattern_for directory
182
+ !directory.nil? ? "#{escape_globs(directory)}/*" : "*"
183
+ end
184
+
185
+ def escape_globs path
186
+ path.gsub(/[*?{}\[\]]/, '\\\\\\&')
187
+ end
188
+
189
+ def excluded_files_contain? file
190
+ copy_exclude.any? { |p| File.fnmatch(p, file) } or [ ".", ".."].include? File.basename(file)
191
+ end
192
+
193
+ def copy_repository_to_server
194
+ execute "getting (via #{copy_strategy}) revision #{revision} to #{destination}" do
195
+ copy_repository_via_strategy
196
+ end
197
+ end
198
+
199
+ def copy_repository_via_strategy
200
+ system(command)
201
+ end
202
+
203
+ def remove_excluded_files
204
+ logger.debug "processing exclusions..."
205
+
206
+ copy_exclude.each do |pattern|
207
+ delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH)
208
+ # avoid the /.. trap that deletes the parent directories
209
+ delete_list.delete_if { |dir| dir =~ /\/\.\.$/ }
210
+ FileUtils.rm_rf(delete_list.compact)
211
+ end
212
+ end
213
+
214
+ def create_revision_file
215
+ File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
216
+ end
217
+
218
+ def compress_repository
219
+ execute "Compressing #{destination} to #{filename}" do
220
+ Dir.chdir(copy_dir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
221
+ end
222
+ end
223
+
224
+ def rollback_changes
225
+ FileUtils.rm filename rescue nil
226
+ FileUtils.rm_rf destination rescue nil
227
+ end
228
+
229
+ def copy_repository_to_local_cache
230
+ return refresh_local_cache if File.exists?(copy_cache)
231
+ create_local_cache
232
+ end
233
+
234
+ def build_script
235
+ configuration[:build_script]
236
+ end
237
+
238
+ # Specify patterns to exclude from the copy. This is only valid
239
+ # when using a local cache.
240
+ def copy_exclude
241
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
242
+ end
243
+
52
244
  # Returns the basename of the release_path, which will be used to
53
245
  # name the local copy and archive file.
54
246
  def destination
55
- @destination ||= File.join(tmpdir, File.basename(configuration[:release_path]))
247
+ @destination ||= File.join(copy_dir, File.basename(configuration[:release_path]))
56
248
  end
57
249
 
58
250
  # Returns the value of the :copy_strategy variable, defaulting to
@@ -75,12 +267,12 @@ module Capistrano
75
267
  # Returns the name of the file that the source code will be
76
268
  # compressed to.
77
269
  def filename
78
- @filename ||= File.join(tmpdir, "#{File.basename(destination)}.#{compression_extension}")
270
+ @filename ||= File.join(copy_dir, "#{File.basename(destination)}.#{compression.extension}")
79
271
  end
80
272
 
81
273
  # The directory to which the copy should be checked out
82
- def tmpdir
83
- @tmpdir ||= configuration[:copy_dir] || Dir.tmpdir
274
+ def copy_dir
275
+ @copy_dir ||= File.expand_path(configuration[:copy_dir] || Dir.tmpdir, Dir.pwd)
84
276
  end
85
277
 
86
278
  # The directory on the remote server to which the archive should be
@@ -95,46 +287,49 @@ module Capistrano
95
287
  @remote_filename ||= File.join(remote_dir, File.basename(filename))
96
288
  end
97
289
 
290
+ # A struct for representing the specifics of a compression type.
291
+ # Commands are arrays, where the first element is the utility to be
292
+ # used to perform the compression or decompression.
293
+ Compression = Struct.new(:extension, :compress_command, :decompress_command)
294
+
98
295
  # The compression method to use, defaults to :gzip.
99
296
  def compression
100
- configuration[:copy_compression] || :gzip
101
- end
297
+ remote_tar = configuration[:copy_remote_tar] || 'tar'
298
+ local_tar = configuration[:copy_local_tar] || 'tar'
102
299
 
103
- # Returns the file extension used for the compression method in
104
- # question.
105
- def compression_extension
106
- case compression
107
- when :gzip, :gz then "tar.gz"
108
- when :bzip2, :bz2 then "tar.bz2"
109
- when :zip then "zip"
110
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
300
+ type = configuration[:copy_compression] || :gzip
301
+ case type
302
+ when :gzip, :gz then Compression.new("tar.gz", [local_tar, 'czf'], [remote_tar, 'xzf'])
303
+ when :bzip2, :bz2 then Compression.new("tar.bz2", [local_tar, 'cjf'], [remote_tar, 'xjf'])
304
+ when :zip then Compression.new("zip", %w(zip -qyr), %w(unzip -q))
305
+ else raise ArgumentError, "invalid compression type #{type.inspect}"
111
306
  end
112
307
  end
113
308
 
114
309
  # Returns the command necessary to compress the given directory
115
- # into the given file. The command is returned as an array, where
116
- # the first element is the utility to be used to perform the compression.
310
+ # into the given file.
117
311
  def compress(directory, file)
118
- case compression
119
- when :gzip, :gz then ["tar", "czf", file, directory]
120
- when :bzip2, :bz2 then ["tar", "cjf", file, directory]
121
- when :zip then ["zip", "-qr", file, directory]
122
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
123
- end
312
+ compression.compress_command + [file, directory]
124
313
  end
125
314
 
126
315
  # Returns the command necessary to decompress the given file,
127
316
  # relative to the current working directory. It must also
128
- # preserve the directory structure in the file. The command is returned
129
- # as an array, where the first element is the utility to be used to
130
- # perform the decompression.
317
+ # preserve the directory structure in the file.
131
318
  def decompress(file)
132
- case compression
133
- when :gzip, :gz then ["tar", "xzf", file]
134
- when :bzip2, :bz2 then ["tar", "xjf", file]
135
- when :zip then ["unzip", "-q", file]
136
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
137
- end
319
+ compression.decompress_command + [file]
320
+ end
321
+
322
+ def decompress_remote_file
323
+ run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
324
+ end
325
+
326
+ # Distributes the file to the remote servers
327
+ def distribute!
328
+ args = [filename, remote_filename]
329
+ args << { :via => configuration[:copy_via] } if configuration[:copy_via]
330
+
331
+ upload(*args)
332
+ decompress_remote_file
138
333
  end
139
334
  end
140
335
 
@@ -27,7 +27,7 @@ module Capistrano
27
27
  # #handle_data filter of the SCM implementation.
28
28
  def scm_run(command)
29
29
  run(command) do |ch,stream,text|
30
- ch[:state] ||= {}
30
+ ch[:state] ||= { :channel => ch }
31
31
  output = source.handle_data(ch[:state], stream, text)
32
32
  ch.send_data(output) if output
33
33
  end
@@ -18,6 +18,7 @@ module Capistrano
18
18
 
19
19
  def check!
20
20
  super.check do |d|
21
+ d.remote.command("rsync") unless copy_exclude.empty?
21
22
  d.remote.writable(shared_path)
22
23
  end
23
24
  end
@@ -38,7 +39,16 @@ module Capistrano
38
39
 
39
40
  def copy_repository_cache
40
41
  logger.trace "copying the cached version to #{configuration[:release_path]}"
41
- run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
42
+ if copy_exclude.empty?
43
+ run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
44
+ else
45
+ exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
46
+ run "rsync -lrpt #{exclusions} #{repository_cache}/ #{configuration[:release_path]} && #{mark}"
47
+ end
48
+ end
49
+
50
+ def copy_exclude
51
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
42
52
  end
43
53
  end
44
54
 
@@ -0,0 +1,21 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote_cache'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+ class UnsharedRemoteCache < RemoteCache
7
+ def check!
8
+ super.check do |d|
9
+ d.remote.writable(repository_cache)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def repository_cache
16
+ configuration[:repository_cache]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -16,4 +16,4 @@ module Capistrano
16
16
  end
17
17
  end
18
18
  end
19
- end
19
+ end