mbailey-capistrano 2.5.5

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 (105) hide show
  1. data/CHANGELOG.rdoc +761 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +66 -0
  4. data/Rakefile +34 -0
  5. data/bin/cap +4 -0
  6. data/bin/capify +78 -0
  7. data/examples/sample.rb +14 -0
  8. data/lib/capistrano/callback.rb +45 -0
  9. data/lib/capistrano/cli/execute.rb +84 -0
  10. data/lib/capistrano/cli/help.rb +125 -0
  11. data/lib/capistrano/cli/help.txt +75 -0
  12. data/lib/capistrano/cli/options.rb +224 -0
  13. data/lib/capistrano/cli/ui.rb +40 -0
  14. data/lib/capistrano/cli.rb +47 -0
  15. data/lib/capistrano/command.rb +283 -0
  16. data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
  17. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  18. data/lib/capistrano/configuration/actions/invocation.rb +293 -0
  19. data/lib/capistrano/configuration/callbacks.rb +148 -0
  20. data/lib/capistrano/configuration/connections.rb +200 -0
  21. data/lib/capistrano/configuration/execution.rb +132 -0
  22. data/lib/capistrano/configuration/loading.rb +197 -0
  23. data/lib/capistrano/configuration/namespaces.rb +197 -0
  24. data/lib/capistrano/configuration/roles.rb +73 -0
  25. data/lib/capistrano/configuration/servers.rb +85 -0
  26. data/lib/capistrano/configuration/variables.rb +127 -0
  27. data/lib/capistrano/configuration.rb +43 -0
  28. data/lib/capistrano/errors.rb +15 -0
  29. data/lib/capistrano/extensions.rb +57 -0
  30. data/lib/capistrano/logger.rb +59 -0
  31. data/lib/capistrano/processable.rb +53 -0
  32. data/lib/capistrano/recipes/compat.rb +32 -0
  33. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  34. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  35. data/lib/capistrano/recipes/deploy/remote_dependency.rb +105 -0
  36. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  37. data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
  38. data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
  39. data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
  40. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  41. data/lib/capistrano/recipes/deploy/scm/git.rb +271 -0
  42. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  43. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  44. data/lib/capistrano/recipes/deploy/scm/perforce.rb +133 -0
  45. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  46. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  47. data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
  48. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  49. data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -0
  50. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  51. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  52. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +56 -0
  53. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  54. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  55. data/lib/capistrano/recipes/deploy.rb +562 -0
  56. data/lib/capistrano/recipes/standard.rb +37 -0
  57. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  58. data/lib/capistrano/recipes/upgrade.rb +33 -0
  59. data/lib/capistrano/role.rb +102 -0
  60. data/lib/capistrano/server_definition.rb +56 -0
  61. data/lib/capistrano/shell.rb +260 -0
  62. data/lib/capistrano/ssh.rb +99 -0
  63. data/lib/capistrano/task_definition.rb +70 -0
  64. data/lib/capistrano/transfer.rb +216 -0
  65. data/lib/capistrano/version.rb +18 -0
  66. data/lib/capistrano.rb +2 -0
  67. data/setup.rb +1346 -0
  68. data/test/cli/execute_test.rb +132 -0
  69. data/test/cli/help_test.rb +165 -0
  70. data/test/cli/options_test.rb +317 -0
  71. data/test/cli/ui_test.rb +28 -0
  72. data/test/cli_test.rb +17 -0
  73. data/test/command_test.rb +286 -0
  74. data/test/configuration/actions/file_transfer_test.rb +61 -0
  75. data/test/configuration/actions/inspect_test.rb +65 -0
  76. data/test/configuration/actions/invocation_test.rb +224 -0
  77. data/test/configuration/callbacks_test.rb +220 -0
  78. data/test/configuration/connections_test.rb +349 -0
  79. data/test/configuration/execution_test.rb +175 -0
  80. data/test/configuration/loading_test.rb +132 -0
  81. data/test/configuration/namespace_dsl_test.rb +311 -0
  82. data/test/configuration/roles_test.rb +144 -0
  83. data/test/configuration/servers_test.rb +121 -0
  84. data/test/configuration/variables_test.rb +184 -0
  85. data/test/configuration_test.rb +88 -0
  86. data/test/deploy/local_dependency_test.rb +76 -0
  87. data/test/deploy/remote_dependency_test.rb +114 -0
  88. data/test/deploy/scm/accurev_test.rb +23 -0
  89. data/test/deploy/scm/base_test.rb +55 -0
  90. data/test/deploy/scm/git_test.rb +167 -0
  91. data/test/deploy/scm/mercurial_test.rb +129 -0
  92. data/test/deploy/strategy/copy_test.rb +258 -0
  93. data/test/extensions_test.rb +69 -0
  94. data/test/fixtures/cli_integration.rb +5 -0
  95. data/test/fixtures/config.rb +5 -0
  96. data/test/fixtures/custom.rb +3 -0
  97. data/test/logger_test.rb +123 -0
  98. data/test/role_test.rb +11 -0
  99. data/test/server_definition_test.rb +121 -0
  100. data/test/shell_test.rb +90 -0
  101. data/test/ssh_test.rb +104 -0
  102. data/test/task_definition_test.rb +101 -0
  103. data/test/transfer_test.rb +160 -0
  104. data/test/utils.rb +38 -0
  105. metadata +205 -0
@@ -0,0 +1,121 @@
1
+ require 'capistrano/recipes/deploy/scm/base'
2
+ require 'yaml'
3
+
4
+ module Capistrano
5
+ module Deploy
6
+ module SCM
7
+
8
+ # Implements the Capistrano SCM interface for the Subversion revision
9
+ # control system (http://subversion.tigris.org).
10
+ class Subversion < Base
11
+ # Sets the default command name for this SCM. Users may override this
12
+ # by setting the :scm_command variable.
13
+ default_command "svn"
14
+
15
+ # Subversion understands 'HEAD' to refer to the latest revision in the
16
+ # repository.
17
+ def head
18
+ "HEAD"
19
+ end
20
+
21
+ # Returns the command that will check out the given revision to the
22
+ # given destination.
23
+ def checkout(revision, destination)
24
+ scm :checkout, arguments, verbose, authentication, "-r#{revision}", repository, destination
25
+ end
26
+
27
+ # Returns the command that will do an "svn update" to the given
28
+ # revision, for the working copy at the given destination.
29
+ def sync(revision, destination)
30
+ scm :update, arguments, verbose, authentication, "-r#{revision}", destination
31
+ end
32
+
33
+ # Returns the command that will do an "svn export" of the given revision
34
+ # to the given destination.
35
+ def export(revision, destination)
36
+ scm :export, arguments, verbose, authentication, "-r#{revision}", repository, destination
37
+ end
38
+
39
+ # Returns the command that will do an "svn diff" for the two revisions.
40
+ def diff(from, to=nil)
41
+ scm :diff, repository, authentication, "-r#{from}:#{to || head}"
42
+ end
43
+
44
+ # Returns an "svn log" command for the two revisions.
45
+ def log(from, to=nil)
46
+ scm :log, repository, authentication, "-r#{from}:#{to || head}"
47
+ end
48
+
49
+ # Attempts to translate the given revision identifier to a "real"
50
+ # revision. If the identifier is an integer, it will simply be returned.
51
+ # Otherwise, this will yield a string of the commands it needs to be
52
+ # executed (svn info), and will extract the revision from the response.
53
+ def query_revision(revision)
54
+ return revision if revision =~ /^\d+$/
55
+ command = scm(:info, repository, authentication, "-r#{revision}")
56
+ result = yield(command)
57
+ yaml = YAML.load(result)
58
+ raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless Hash === yaml
59
+ yaml['Last Changed Rev'] || yaml['Revision']
60
+ end
61
+
62
+ # Increments the given revision number and returns it.
63
+ def next_revision(revision)
64
+ revision.to_i + 1
65
+ end
66
+
67
+ # Determines what the response should be for a particular bit of text
68
+ # from the SCM. Password prompts, connection requests, passphrases,
69
+ # etc. are handled here.
70
+ def handle_data(state, stream, text)
71
+ host = state[:channel][:host]
72
+ logger.info "[#{host} :: #{stream}] #{text}"
73
+ case text
74
+ when /\bpassword.*:/i
75
+ # subversion is prompting for a password
76
+ "#{scm_password_prompt}\n"
77
+ when %r{\(yes/no\)}
78
+ # subversion is asking whether or not to connect
79
+ "yes\n"
80
+ when /passphrase/i
81
+ # subversion is asking for the passphrase for the user's key
82
+ "#{variable(:scm_passphrase)}\n"
83
+ when /The entry \'(.+?)\' is no longer a directory/
84
+ raise Capistrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
85
+ when /accept \(t\)emporarily/
86
+ # subversion is asking whether to accept the certificate
87
+ "t\n"
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ # If a username is configured for the SCM, return the command-line
94
+ # switches for that. Note that we don't need to return the password
95
+ # switch, since Capistrano will check for that prompt in the output
96
+ # and will respond appropriately.
97
+ def authentication
98
+ username = variable(:scm_username)
99
+ return "" unless username
100
+ result = "--username #{variable(:scm_username)} "
101
+ result << "--password #{variable(:scm_password)} " unless variable(:scm_auth_cache) || variable(:scm_prefer_prompt)
102
+ result << "--no-auth-cache " unless variable(:scm_auth_cache)
103
+ result
104
+ end
105
+
106
+ # If verbose output is requested, return nil, otherwise return the
107
+ # command-line switch for "quiet" ("-q").
108
+ def verbose
109
+ variable(:scm_verbose) ? nil : "-q"
110
+ end
111
+
112
+ def scm_password_prompt
113
+ @scm_password_prompt ||= variable(:scm_password) ||
114
+ variable(:password) ||
115
+ Capistrano::CLI.password_prompt("Subversion password: ")
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,19 @@
1
+ module Capistrano
2
+ module Deploy
3
+ module SCM
4
+ def self.new(scm, config={})
5
+ scm_file = "capistrano/recipes/deploy/scm/#{scm}"
6
+ require(scm_file)
7
+
8
+ scm_const = scm.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
9
+ if const_defined?(scm_const)
10
+ const_get(scm_const).new(config)
11
+ else
12
+ raise Capistrano::Error, "could not find `#{name}::#{scm_const}' in `#{scm_file}'"
13
+ end
14
+ rescue LoadError
15
+ raise Capistrano::Error, "could not find any SCM named `#{scm}'"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,79 @@
1
+ require 'capistrano/recipes/deploy/dependencies'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # This class defines the abstract interface for all Capistrano
8
+ # deployment strategies. Subclasses must implement at least the
9
+ # #deploy! method.
10
+ class Base
11
+ attr_reader :configuration
12
+
13
+ # Instantiates a strategy with a reference to the given configuration.
14
+ def initialize(config={})
15
+ @configuration = config
16
+ end
17
+
18
+ # Executes the necessary commands to deploy the revision of the source
19
+ # code identified by the +revision+ variable. Additionally, this
20
+ # should write the value of the +revision+ variable to a file called
21
+ # REVISION, in the base of the deployed revision. This file is used by
22
+ # other tasks, to perform diffs and such.
23
+ def deploy!
24
+ raise NotImplementedError, "`deploy!' is not implemented by #{self.class.name}"
25
+ end
26
+
27
+ # Performs a check on the remote hosts to determine whether everything
28
+ # is setup such that a deploy could succeed.
29
+ def check!
30
+ Dependencies.new(configuration) do |d|
31
+ d.remote.directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap deploy:setup'.")
32
+ d.remote.writable(configuration[:deploy_to]).or("You do not have permissions to write to `#{configuration[:deploy_to]}'.")
33
+ d.remote.writable(configuration[:releases_path]).or("You do not have permissions to write to `#{configuration[:releases_path]}'.")
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ # This is to allow helper methods like "run" and "put" to be more
40
+ # easily accessible to strategy implementations.
41
+ def method_missing(sym, *args, &block)
42
+ if configuration.respond_to?(sym)
43
+ configuration.send(sym, *args, &block)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ # A wrapper for Kernel#system that logs the command being executed.
50
+ def system(*args)
51
+ cmd = args.join(' ')
52
+ if RUBY_PLATFORM =~ /win32/
53
+ cmd.gsub!('/','\\') # Replace / with \\
54
+ cmd.gsub!(/^cd /,'cd /D ') # Replace cd with cd /D
55
+ cmd.gsub!(/&& cd /,'&& cd /D ') # Replace cd with cd /D
56
+ logger.trace "executing locally: #{cmd}"
57
+ super(cmd)
58
+ else
59
+ logger.trace "executing locally: #{cmd}"
60
+ super
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def logger
67
+ @logger ||= configuration[:logger] || Capistrano::Logger.new(:output => STDOUT)
68
+ end
69
+
70
+ # The revision to deploy. Must return a real revision identifier,
71
+ # and not a pseudo-id.
72
+ def revision
73
+ configuration[:real_revision]
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,20 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # Implements the deployment strategy which does an SCM checkout on each
8
+ # target host. This is the default deployment strategy for Capistrano.
9
+ class Checkout < Remote
10
+ protected
11
+
12
+ # Returns the SCM's checkout command for the revision to deploy.
13
+ def command
14
+ @command ||= source.checkout(revision, configuration[:release_path])
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,210 @@
1
+ require 'capistrano/recipes/deploy/strategy/base'
2
+ require 'fileutils'
3
+ require 'tempfile' # Dir.tmpdir
4
+
5
+ module Capistrano
6
+ module Deploy
7
+ module Strategy
8
+
9
+ # This class implements the strategy for deployments which work
10
+ # by preparing the source code locally, compressing it, copying the
11
+ # file to each target host, and uncompressing it to the deployment
12
+ # directory.
13
+ #
14
+ # By default, the SCM checkout command is used to obtain the local copy
15
+ # of the source code. If you would rather use the export operation,
16
+ # you can set the :copy_strategy variable to :export.
17
+ #
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,
38
+ # :copy_compression, which must be one of :gzip, :bz2, or
39
+ # :zip, and which specifies how the source should be compressed for
40
+ # transmission to each host.
41
+ class Copy < Base
42
+ # Obtains a copy of the source code locally (via the #command method),
43
+ # compresses it to a single file, copies that file to all target
44
+ # servers, and uncompresses it on each of them into the deployment
45
+ # directory.
46
+ def deploy!
47
+ if copy_cache
48
+ if File.exists?(copy_cache)
49
+ logger.debug "refreshing local cache to revision #{revision} at #{copy_cache}"
50
+ system(source.sync(revision, copy_cache))
51
+ else
52
+ logger.debug "preparing local cache at #{copy_cache}"
53
+ system(source.checkout(revision, copy_cache))
54
+ end
55
+
56
+ logger.debug "copying cache to deployment staging area #{destination}"
57
+ Dir.chdir(copy_cache) do
58
+ FileUtils.mkdir_p(destination)
59
+ queue = Dir.glob("*", File::FNM_DOTMATCH)
60
+ while queue.any?
61
+ item = queue.shift
62
+ name = File.basename(item)
63
+
64
+ next if name == "." || name == ".."
65
+ next if copy_exclude.any? { |pattern| File.fnmatch(pattern, item) }
66
+
67
+ if File.symlink?(item)
68
+ FileUtils.ln_s(File.readlink(File.join(copy_cache, item)), File.join(destination, item))
69
+ elsif File.directory?(item)
70
+ queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH)
71
+ FileUtils.mkdir(File.join(destination, item))
72
+ else
73
+ FileUtils.ln(File.join(copy_cache, item), File.join(destination, item))
74
+ end
75
+ end
76
+ end
77
+ else
78
+ logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}"
79
+ system(command)
80
+
81
+ if copy_exclude.any?
82
+ logger.debug "processing exclusions..."
83
+ if copy_exclude.any?
84
+ copy_exclude.each do |pattern|
85
+ delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH)
86
+ # avoid the /.. trap that deletes the parent directories
87
+ delete_list.delete_if { |dir| dir =~ /\/\.\.$/ }
88
+ FileUtils.rm_rf(delete_list.compact)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
95
+
96
+ logger.trace "compressing #{destination} to #{filename}"
97
+ Dir.chdir(tmpdir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
98
+
99
+ upload(filename, remote_filename)
100
+ run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
101
+ ensure
102
+ FileUtils.rm filename rescue nil
103
+ FileUtils.rm_rf destination rescue nil
104
+ end
105
+
106
+ def check!
107
+ super.check do |d|
108
+ d.local.command(source.local.command) if source.local.command
109
+ d.local.command(compress(nil, nil).first)
110
+ d.remote.command(decompress(nil).first)
111
+ end
112
+ end
113
+
114
+ # Returns the location of the local copy cache, if the strategy should
115
+ # use a local cache + copy instead of a new checkout/export every
116
+ # time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
117
+ # is +true+, a default cache location will be returned.
118
+ def copy_cache
119
+ @copy_cache ||= configuration[:copy_cache] == true ?
120
+ File.join(Dir.tmpdir, configuration[:application]) :
121
+ configuration[:copy_cache]
122
+ end
123
+
124
+ private
125
+
126
+ # Specify patterns to exclude from the copy. This is only valid
127
+ # when using a local cache.
128
+ def copy_exclude
129
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
130
+ end
131
+
132
+ # Returns the basename of the release_path, which will be used to
133
+ # name the local copy and archive file.
134
+ def destination
135
+ @destination ||= File.join(tmpdir, File.basename(configuration[:release_path]))
136
+ end
137
+
138
+ # Returns the value of the :copy_strategy variable, defaulting to
139
+ # :checkout if it has not been set.
140
+ def copy_strategy
141
+ @copy_strategy ||= configuration.fetch(:copy_strategy, :checkout)
142
+ end
143
+
144
+ # Should return the command(s) necessary to obtain the source code
145
+ # locally.
146
+ def command
147
+ @command ||= case copy_strategy
148
+ when :checkout
149
+ source.checkout(revision, destination)
150
+ when :export
151
+ source.export(revision, destination)
152
+ end
153
+ end
154
+
155
+ # Returns the name of the file that the source code will be
156
+ # compressed to.
157
+ def filename
158
+ @filename ||= File.join(tmpdir, "#{File.basename(destination)}.#{compression.extension}")
159
+ end
160
+
161
+ # The directory to which the copy should be checked out
162
+ def tmpdir
163
+ @tmpdir ||= configuration[:copy_dir] || Dir.tmpdir
164
+ end
165
+
166
+ # The directory on the remote server to which the archive should be
167
+ # copied
168
+ def remote_dir
169
+ @remote_dir ||= configuration[:copy_remote_dir] || "/tmp"
170
+ end
171
+
172
+ # The location on the remote server where the file should be
173
+ # temporarily stored.
174
+ def remote_filename
175
+ @remote_filename ||= File.join(remote_dir, File.basename(filename))
176
+ end
177
+
178
+ # A struct for representing the specifics of a compression type.
179
+ # Commands are arrays, where the first element is the utility to be
180
+ # used to perform the compression or decompression.
181
+ Compression = Struct.new(:extension, :compress_command, :decompress_command)
182
+
183
+ # The compression method to use, defaults to :gzip.
184
+ def compression
185
+ type = configuration[:copy_compression] || :gzip
186
+ case type
187
+ when :gzip, :gz then Compression.new("tar.gz", %w(tar czf), %w(tar xzf))
188
+ when :bzip2, :bz2 then Compression.new("tar.bz2", %w(tar cjf), %w(tar xjf))
189
+ when :zip then Compression.new("zip", %w(zip -qr), %w(unzip -q))
190
+ else raise ArgumentError, "invalid compression type #{type.inspect}"
191
+ end
192
+ end
193
+
194
+ # Returns the command necessary to compress the given directory
195
+ # into the given file.
196
+ def compress(directory, file)
197
+ compression.compress_command + [file, directory]
198
+ end
199
+
200
+ # Returns the command necessary to decompress the given file,
201
+ # relative to the current working directory. It must also
202
+ # preserve the directory structure in the file.
203
+ def decompress(file)
204
+ compression.decompress_command + [file]
205
+ end
206
+ end
207
+
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,20 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # Implements the deployment strategy which does an SCM export on each
8
+ # target host.
9
+ class Export < Remote
10
+ protected
11
+
12
+ # Returns the SCM's export command for the revision to deploy.
13
+ def command
14
+ @command ||= source.export(revision, configuration[:release_path])
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ require 'capistrano/recipes/deploy/strategy/base'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # An abstract superclass, which forms the base for all deployment
8
+ # strategies which work by grabbing the code from the repository directly
9
+ # from remote host. This includes deploying by checkout (the default),
10
+ # and deploying by export.
11
+ class Remote < Base
12
+ # Executes the SCM command for this strategy and writes the REVISION
13
+ # mark file to each host.
14
+ def deploy!
15
+ scm_run "#{command} && #{mark}"
16
+ end
17
+
18
+ def check!
19
+ super.check do |d|
20
+ d.remote.command(source.command)
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ # Runs the given command, filtering output back through the
27
+ # #handle_data filter of the SCM implementation.
28
+ def scm_run(command)
29
+ run(command) do |ch,stream,text|
30
+ ch[:state] ||= { :channel => ch }
31
+ output = source.handle_data(ch[:state], stream, text)
32
+ ch.send_data(output) if output
33
+ end
34
+ end
35
+
36
+ # An abstract method which must be overridden in subclasses, to
37
+ # return the actual SCM command(s) which must be executed on each
38
+ # target host in order to perform the deployment.
39
+ def command
40
+ raise NotImplementedError, "`command' is not implemented by #{self.class.name}"
41
+ end
42
+
43
+ # Returns the command which will write the identifier of the
44
+ # revision being deployed to the REVISION file on each host.
45
+ def mark
46
+ "(echo #{revision} > #{configuration[:release_path]}/REVISION)"
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+
7
+ # Implements the deployment strategy that keeps a cached checkout of
8
+ # the source code on each remote server. Each deploy simply updates the
9
+ # cached checkout, and then does a copy from the cached copy to the
10
+ # final deployment location.
11
+ class RemoteCache < Remote
12
+ # Executes the SCM command for this strategy and writes the REVISION
13
+ # mark file to each host.
14
+ def deploy!
15
+ update_repository_cache
16
+ copy_repository_cache
17
+ end
18
+
19
+ def check!
20
+ super.check do |d|
21
+ d.remote.writable(shared_path)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def repository_cache
28
+ File.join(shared_path, configuration[:repository_cache] || "cached-copy")
29
+ end
30
+
31
+ def update_repository_cache
32
+ logger.trace "updating the cached checkout on all servers"
33
+ command = "if [ -d #{repository_cache} ]; then " +
34
+ "#{source.sync(revision, repository_cache)}; " +
35
+ "else #{source.checkout(revision, repository_cache)}; fi"
36
+ scm_run(command)
37
+ end
38
+
39
+ def copy_repository_cache
40
+ logger.trace "copying the cached version to #{configuration[:release_path]}"
41
+ if copy_exclude.empty?
42
+ run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
43
+ else
44
+ exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
45
+ run "rsync -lrpt #{exclusions} #{repository_cache}/* #{configuration[:release_path]} && #{mark}"
46
+ end
47
+ end
48
+
49
+ def copy_exclude
50
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ module Capistrano
2
+ module Deploy
3
+ module Strategy
4
+ def self.new(strategy, config={})
5
+ strategy_file = "capistrano/recipes/deploy/strategy/#{strategy}"
6
+ require(strategy_file)
7
+
8
+ strategy_const = strategy.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
9
+ if const_defined?(strategy_const)
10
+ const_get(strategy_const).new(config)
11
+ else
12
+ raise Capistrano::Error, "could not find `#{name}::#{strategy_const}' in `#{strategy_file}'"
13
+ end
14
+ rescue LoadError
15
+ raise Capistrano::Error, "could not find any strategy named `#{strategy}'"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+
7
+ <head>
8
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
9
+ <title>System down for maintenance</title>
10
+
11
+ <style type="text/css">
12
+ div.outer {
13
+ position: absolute;
14
+ left: 50%;
15
+ top: 50%;
16
+ width: 500px;
17
+ height: 300px;
18
+ margin-left: -260px;
19
+ margin-top: -150px;
20
+ }
21
+
22
+ .DialogBody {
23
+ margin: 0;
24
+ padding: 10px;
25
+ text-align: left;
26
+ border: 1px solid #ccc;
27
+ border-right: 1px solid #999;
28
+ border-bottom: 1px solid #999;
29
+ background-color: #fff;
30
+ }
31
+
32
+ body { background-color: #fff; }
33
+ </style>
34
+ </head>
35
+
36
+ <body>
37
+
38
+ <div class="outer">
39
+ <div class="DialogBody" style="text-align: center;">
40
+ <div style="text-align: center; width: 200px; margin: 0 auto;">
41
+ <p style="color: red; font-size: 16px; line-height: 20px;">
42
+ The system is down for <%= reason ? reason : "maintenance" %>
43
+ as of <%= Time.now.strftime("%H:%M %Z") %>.
44
+ </p>
45
+ <p style="color: #666;">
46
+ It'll be back <%= deadline ? deadline : "shortly" %>.
47
+ </p>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ </body>
53
+ </html>