Permalink
Browse files

Add deploy:check test for verifying that dependencies are in order fo…

…r deploying

git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6487 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent 4331e3b commit aab2ea25f305fd2da2b762bd7e894bdffbd3342b @jamis jamis committed Mar 29, 2007
@@ -148,8 +148,8 @@
be processed recursively, with all files being pushed to the deployment \
servers. Any file or directory starting with a '.' character will be ignored.
- $ cap deploy:update_files FILES=templates,controller.rb"
- task :update_files, :except => { :no_release => true } do
+ $ cap deploy:upload FILES=templates,controller.rb"
+ task :upload, :except => { :no_release => true } do
files = (ENV["FILES"] || "").
split(",").
map { |f| f.strip!; File.directory?(f) ? Dir["#{f}/**/*"] : f }.
@@ -255,6 +255,22 @@
end
end
+desc "Test deployment dependencies. Checks things like directory permissions, \
+necessary utilities, and so forth, reporting on the things that appear to be \
+incorrect or missing. This is good for making sure a deploy has a chance of \
+working before you actually run `cap deploy'!"
+ task :check, :except => { :no_release => true } do
+ dependencies = strategy.check!
+ if dependencies.pass?
+ puts "You appear to have all necessary dependencies installed"
+ else
+ puts "The following dependencies failed. Please check them and try again:"
+ dependencies.reject { |d| d.pass? }.each do |d|
+ puts "--> #{d.message}"
+ end
+ end
+ end
+
namespace :pending do
desc "Displays the `diff' since your last deploy. This is useful if you want \
to examine what changes are about to be deployed. Note that this might not be \
@@ -0,0 +1,44 @@
+require 'capistrano/recipes/deploy/local_dependency'
+require 'capistrano/recipes/deploy/remote_dependency'
+
+module Capistrano
+ module Deploy
+ class Dependencies
+ include Enumerable
+
+ attr_reader :configuration
+
+ def initialize(configuration)
+ @configuration = configuration
+ @dependencies = []
+ yield self if block_given?
+ end
+
+ def check
+ yield self
+ self
+ end
+
+ def remote
+ dep = RemoteDependency.new(configuration)
+ @dependencies << dep
+ dep
+ end
+
+ def local
+ dep = LocalDependency.new(configuration)
+ @dependencies << dep
+ dep
+ end
+
+ def each
+ @dependencies.each { |d| yield d }
+ self
+ end
+
+ def pass?
+ all? { |d| d.pass? }
+ end
+ end
+ end
+end
@@ -0,0 +1,46 @@
+module Capistrano
+ module Deploy
+ class LocalDependency
+ attr_reader :configuration
+ attr_reader :message
+
+ def initialize(configuration)
+ @configuration = configuration
+ @success = true
+ end
+
+ def expects_in_path(command)
+ @message ||= "`#{command}' could not be found in the path on the local host"
+ @success = find_in_path(command)
+ self
+ end
+
+ def or(message)
+ @message = message
+ self
+ end
+
+ def pass?
+ @success
+ end
+
+ private
+
+ # Searches the path, looking for the given utility. If an executable
+ # file is found that matches the parameter, this returns true.
+ def find_in_path(utility)
+ path = (ENV['PATH'] || "").split(File::PATH_SEPARATOR)
+ suffixes = RUBY_PLATFORM =~ /mswin/ ? %w(.bat .exe .com .cmd) : [""]
+
+ path.each do |dir|
+ suffixes.each do |sfx|
+ file = File.join(dir, utility + sfx)
+ return true if File.executable?(file)
+ end
+ end
+
+ false
+ end
+ end
+ end
+end
@@ -0,0 +1,58 @@
+module Capistrano
+ module Deploy
+ class RemoteDependency
+ attr_reader :configuration
+ attr_reader :hosts
+
+ def initialize(configuration)
+ @configuration = configuration
+ @success = true
+ end
+
+ def expect_directory(path)
+ @message ||= "`#{path}' is not a directory"
+ try("test -d #{path}")
+ self
+ end
+
+ def expect_writable(path)
+ @message ||= "`#{path}' is not writable"
+ try("test -w #{path}")
+ self
+ end
+
+ def expects_in_path(command)
+ @message ||= "`#{command}' could not be found in the path"
+ try("type -p #{command}")
+ self
+ end
+
+ def or(message)
+ @message = message
+ self
+ end
+
+ def pass?
+ @success
+ end
+
+ def message
+ s = @message.dup
+ s << " (#{@hosts})" if @hosts && @hosts.any?
+ s
+ end
+
+ private
+
+ def try(command)
+ return unless @success # short-circuit evaluation
+ configuration.run(command) do |ch,stream,out|
+ warn "#{ch[:host]}: #{out}" if stream == :err
+ end
+ rescue Capistrano::CommandError => e
+ @success = false
+ @hosts = e.hosts.join(', ')
+ end
+ end
+ end
+end
@@ -84,6 +84,13 @@ def handle_data(state, stream, text)
nil
end
+ # Returns the name of the command-line utility for this SCM. It first
+ # looks at the :scm_command variable, and if it does not exist, it
+ # then falls back to whatever was defined by +default_command+.
+ def command
+ configuration[:scm_command] || default_command
+ end
+
private
# A reference to a Logger instance that the SCM can use to log
@@ -105,13 +112,6 @@ def scm(*args)
[command, *args].compact.join(" ")
end
- # Returns the name of the command-line utility for this SCM. It first
- # looks at the :scm_command variable, and if it does not exist, it
- # then falls back to whatever was defined by +default_command+.
- def command
- configuration[:scm_command] || default_command
- end
-
# A convenience method for accessing the declared repository value.
def repository
configuration[:repository]
@@ -1,3 +1,5 @@
+require 'capistrano/recipes/deploy/dependencies'
+
module Capistrano
module Deploy
module Strategy
@@ -22,6 +24,16 @@ def deploy!
raise NotImplementedError, "`deploy!' is not implemented by #{self.class.name}"
end
+ # Performs a check on the remote hosts to determine whether everything
+ # is setup such that a deploy could succeed.
+ def check!
+ Dependencies.new(configuration) do |d|
+ d.remote.expect_directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap deploy:setup'.")
+ d.remote.expect_writable(configuration[:deploy_to]).or("You do not have permissions to write to `#{configuration[:deploy_to]}'.")
+ d.remote.expect_writable(configuration[:releases_path]).or("You do not have permissions to write to `#{configuration[:releases_path]}'.")
+ end
+ end
+
protected
# This is to allow helper methods like "run" and "put" to be more
@@ -38,6 +38,14 @@ def deploy!
FileUtils.rm_rf destination rescue nil
end
+ def check!
+ super.check do |d|
+ d.local.expects_in_path(source.command)
+ d.local.expects_in_path(compress(nil, nil).first)
+ d.remote.expects_in_path(decompress(nil).first)
+ end
+ end
+
private
# Returns the basename of the release_path, which will be used to
@@ -92,23 +100,28 @@ def compression_extension
end
# Returns the command necessary to compress the given directory
- # into the given file.
+ # into the given file. The command is returned as an array, where
+ # the first element is the utility to be used to perform the compression.
def compress(directory, file)
case compression
- when :gzip, :gz then "tar czf #{file} #{directory}"
- when :bzip2, :bz2 then "tar cjf #{file} #{directory}"
- when :zip then "zip -qr #{file} #{directory}"
+ when :gzip, :gz then ["tar", "czf", file, directory]
+ when :bzip2, :bz2 then ["tar", "cjf", file, directory]
+ when :zip then ["zip", "-qr", file, directory]
+ else raise ArgumentError, "invalid compression type #{compression.inspect}"
end
end
# Returns the command necessary to decompress the given file,
# relative to the current working directory. It must also
- # preserve the directory structure in the file.
+ # preserve the directory structure in the file. The command is returned
+ # as an array, where the first element is the utility to be used to
+ # perform the decompression.
def decompress(file)
case compression
- when :gzip, :gz then "tar xzf #{file}"
- when :bzip2, :bz2 then "tar xjf #{file}"
- when :zip then "unzip -q #{file}"
+ when :gzip, :gz then ["tar", "xzf", file]
+ when :bzip2, :bz2 then ["tar", "xjf", file]
+ when :zip then ["unzip", "-q", file]
+ else raise ArgumentError, "invalid compression type #{compression.inspect}"
end
end
end
@@ -15,6 +15,11 @@ def deploy!
scm_run "#{command} && #{mark}"
end
+ def check!
+ result = super
+ test("type -p #{source.command}", "could not find `#{source.command}'") && result
+ end
+
protected
# Runs the given command, filtering output back through the
View
@@ -1178,7 +1178,7 @@ def install_files(list, dest, mode)
end
def ruby_scripts
- collect_filenames_auto().select {|n| /\.r(b|html)\z/ =~ n}
+ collect_filenames_auto().select {|n| /\.(r(b|html)|txt)\z/ =~ n}
end
# picked up many entries from cvs-1.11.1/src/ignore.c

0 comments on commit aab2ea2

Please sign in to comment.