mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
SwitchTower: use a timestamp as the release name, instead of the revision number. Various tweaks and fixes to the supported scm modules.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2046 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
6a4d4460ea
commit
affb615a8a
10 changed files with 53 additions and 54 deletions
|
@ -1,5 +1,7 @@
|
||||||
*SVN*
|
*SVN*
|
||||||
|
|
||||||
|
* Changed behavior of checkout to use the timestamp as the release name, instead of the revision number
|
||||||
|
|
||||||
* Added CVS module (very very experimental!)
|
* Added CVS module (very very experimental!)
|
||||||
|
|
||||||
* Works with public keys now, for passwordless deployment
|
* Works with public keys now, for passwordless deployment
|
||||||
|
|
|
@ -21,7 +21,9 @@ As with the rest of Rails, if you can abide by these assumptions, you can use Sw
|
||||||
|
|
||||||
== Usage
|
== Usage
|
||||||
|
|
||||||
More documentation is always pending, but you'll want to see the user manual for detailed usage instructions. In general, you'll use SwitchTower as follows:
|
More documentation is always pending, but you'll want to see the user manual for detailed usage instructions. (The manual is online at http://manuals.rubyonrails.com/read/book/17).
|
||||||
|
|
||||||
|
In general, you'll use SwitchTower as follows:
|
||||||
|
|
||||||
* Create a deployment recipe ("deploy.rb") for your application. You can use the sample recipe in examples/sample.rb as a starting point.
|
* Create a deployment recipe ("deploy.rb") for your application. You can use the sample recipe in examples/sample.rb as a starting point.
|
||||||
* Use the +switchtower+ script to execute your recipe (see below).
|
* Use the +switchtower+ script to execute your recipe (see below).
|
||||||
|
|
|
@ -248,7 +248,7 @@ module SwitchTower
|
||||||
buffer << out if str == :out
|
buffer << out if str == :out
|
||||||
raise "could not determine releases #{out.inspect}" if str == :err
|
raise "could not determine releases #{out.inspect}" if str == :err
|
||||||
end
|
end
|
||||||
@releases = buffer.split.map { |i| i.to_i }.sort
|
@releases = buffer.split.sort
|
||||||
end
|
end
|
||||||
|
|
||||||
@releases
|
@releases
|
||||||
|
|
|
@ -26,12 +26,17 @@ module SwitchTower
|
||||||
# The load paths used for locating recipe files.
|
# The load paths used for locating recipe files.
|
||||||
attr_reader :load_paths
|
attr_reader :load_paths
|
||||||
|
|
||||||
|
# The time (in UTC) at which this configuration was created, used for
|
||||||
|
# determining the release path.
|
||||||
|
attr_reader :now
|
||||||
|
|
||||||
def initialize(actor_class=Actor) #:nodoc:
|
def initialize(actor_class=Actor) #:nodoc:
|
||||||
@roles = Hash.new { |h,k| h[k] = [] }
|
@roles = Hash.new { |h,k| h[k] = [] }
|
||||||
@actor = actor_class.new(self)
|
@actor = actor_class.new(self)
|
||||||
@logger = Logger.new
|
@logger = Logger.new
|
||||||
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
|
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
|
||||||
@variables = {}
|
@variables = {}
|
||||||
|
@now = Time.now.utc
|
||||||
|
|
||||||
set :application, nil
|
set :application, nil
|
||||||
set :repository, nil
|
set :repository, nil
|
||||||
|
@ -165,10 +170,10 @@ module SwitchTower
|
||||||
File.join(deploy_to, shared_dir)
|
File.join(deploy_to, shared_dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the full path to the named revision (defaults to the most current
|
# Return the full path to the named release. If a release is not specified,
|
||||||
# revision in the repository).
|
# +now+ is used (the time at which the configuration was created).
|
||||||
def release_path(revision=source.latest_revision)
|
def release_path(release=now.strftime("%Y%m%d%H%M%S"))
|
||||||
File.join(releases_path, revision)
|
File.join(releases_path, release)
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to?(sym) #:nodoc:
|
def respond_to?(sym) #:nodoc:
|
||||||
|
|
18
switchtower/lib/switchtower/scm/base.rb
Normal file
18
switchtower/lib/switchtower/scm/base.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module SwitchTower
|
||||||
|
module SCM
|
||||||
|
|
||||||
|
# The ancestor class of the various SCM module implementations.
|
||||||
|
class Base
|
||||||
|
attr_reader :configuration
|
||||||
|
|
||||||
|
def initialize(configuration) #:nodoc:
|
||||||
|
@configuration = configuration
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkout(actor)
|
||||||
|
raise NotImplementedError, "subclasses must implement checkout"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,5 @@
|
||||||
require 'time'
|
require 'time'
|
||||||
|
require 'switchtower/scm/base'
|
||||||
|
|
||||||
module SwitchTower
|
module SwitchTower
|
||||||
module SCM
|
module SCM
|
||||||
|
@ -21,13 +22,7 @@ module SwitchTower
|
||||||
# Also, you can specify the CVS_RSH variable to use on the remote machine(s)
|
# Also, you can specify the CVS_RSH variable to use on the remote machine(s)
|
||||||
# via the <tt>:cvs_rsh</tt> variable. This defaults to the value of the
|
# via the <tt>:cvs_rsh</tt> variable. This defaults to the value of the
|
||||||
# CVS_RSH environment variable locally, or if it is not set, to "ssh".
|
# CVS_RSH environment variable locally, or if it is not set, to "ssh".
|
||||||
class Cvs
|
class Cvs < Base
|
||||||
attr_reader :configuration
|
|
||||||
|
|
||||||
def initialize(configuration) #:nodoc:
|
|
||||||
@configuration = configuration
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return a string representing the date of the last revision (CVS is
|
# Return a string representing the date of the last revision (CVS is
|
||||||
# seriously retarded, in that it does not give you a way to query when
|
# seriously retarded, in that it does not give you a way to query when
|
||||||
# the last revision was made to the repository, so this is a fairly
|
# the last revision was made to the repository, so this is a fairly
|
||||||
|
@ -37,7 +32,7 @@ module SwitchTower
|
||||||
configuration.logger.debug "querying latest revision..."
|
configuration.logger.debug "querying latest revision..."
|
||||||
@latest_revision = cvs_log(configuration.local).
|
@latest_revision = cvs_log(configuration.local).
|
||||||
split(/\r?\n/).
|
split(/\r?\n/).
|
||||||
grep(/^date: (.*?);/) { Time.parse($1).strftime("%FT%T") }.
|
grep(/^date: (.*?);/) { Time.parse($1).strftime("%F %T") }.
|
||||||
sort.
|
sort.
|
||||||
last
|
last
|
||||||
end
|
end
|
||||||
|
@ -49,12 +44,9 @@ module SwitchTower
|
||||||
cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"
|
cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"
|
||||||
|
|
||||||
command = <<-CMD
|
command = <<-CMD
|
||||||
if [[ -d #{actor.release_path} ]]; then
|
if [[ ! -d #{actor.release_path} ]]; then
|
||||||
cd #{actor.release_path};
|
|
||||||
CVS_RSH="#{cvs_rsh}" #{cvs} -q up -d#{latest_revision};
|
|
||||||
else
|
|
||||||
cd #{configuration.releases_path};
|
cd #{configuration.releases_path};
|
||||||
CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -q co -D #{latest_revision} -d #{latest_revision} #{actor.application};
|
CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q co -D "#{latest_revision}" -d #{File.basename(actor.release_path)} #{actor.application};
|
||||||
fi
|
fi
|
||||||
CMD
|
CMD
|
||||||
actor.run(command) do |ch, stream, out|
|
actor.run(command) do |ch, stream, out|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
require 'time'
|
require 'switchtower/scm/base'
|
||||||
|
|
||||||
module SwitchTower
|
module SwitchTower
|
||||||
module SCM
|
module SCM
|
||||||
|
@ -13,26 +13,7 @@ module SwitchTower
|
||||||
# executable on the remote machine:
|
# executable on the remote machine:
|
||||||
#
|
#
|
||||||
# set :darcs, "/opt/local/bin/darcs"
|
# set :darcs, "/opt/local/bin/darcs"
|
||||||
class Darcs
|
class Darcs < Base
|
||||||
attr_reader :configuration
|
|
||||||
|
|
||||||
def initialize(configuration) #:nodoc:
|
|
||||||
@configuration = configuration
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return an integer identifying the last known revision (patch) in the
|
|
||||||
# darcs repository. (This integer is currently the 14-digit timestamp
|
|
||||||
# of the last known patch.)
|
|
||||||
def latest_revision
|
|
||||||
unless @latest_revision
|
|
||||||
configuration.logger.debug "querying latest revision..."
|
|
||||||
@latest_revision = Time.
|
|
||||||
parse(`darcs changes --last 1 --repo #{configuration.repository}`).
|
|
||||||
strftime("%Y%m%d%H%M%S").to_i
|
|
||||||
end
|
|
||||||
@latest_revision
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check out (on all servers associated with the current task) the latest
|
# Check out (on all servers associated with the current task) the latest
|
||||||
# revision. Uses the given actor instance to execute the command.
|
# revision. Uses the given actor instance to execute the command.
|
||||||
def checkout(actor)
|
def checkout(actor)
|
||||||
|
@ -40,7 +21,7 @@ module SwitchTower
|
||||||
|
|
||||||
command = <<-CMD
|
command = <<-CMD
|
||||||
if [[ ! -d #{actor.release_path} ]]; then
|
if [[ ! -d #{actor.release_path} ]]; then
|
||||||
#{darcs} get --set-scripts-executable #{configuration.repository} #{actor.release_path};
|
#{darcs} get -q --set-scripts-executable #{configuration.repository} #{actor.release_path};
|
||||||
fi
|
fi
|
||||||
CMD
|
CMD
|
||||||
actor.run(command)
|
actor.run(command)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'switchtower/scm/base'
|
||||||
|
|
||||||
module SwitchTower
|
module SwitchTower
|
||||||
module SCM
|
module SCM
|
||||||
|
|
||||||
|
@ -12,13 +14,7 @@ module SwitchTower
|
||||||
# executable on the remote machine:
|
# executable on the remote machine:
|
||||||
#
|
#
|
||||||
# set :svn, "/opt/local/bin/svn"
|
# set :svn, "/opt/local/bin/svn"
|
||||||
class Subversion
|
class Subversion < Base
|
||||||
attr_reader :configuration
|
|
||||||
|
|
||||||
def initialize(configuration) #:nodoc:
|
|
||||||
@configuration = configuration
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return an integer identifying the last known revision in the svn
|
# Return an integer identifying the last known revision in the svn
|
||||||
# repository. (This integer is currently the revision number.) If latest
|
# repository. (This integer is currently the revision number.) If latest
|
||||||
# revision does not exist in the given repository, this routine will
|
# revision does not exist in the given repository, this routine will
|
||||||
|
@ -64,9 +60,9 @@ module SwitchTower
|
||||||
prefix
|
prefix
|
||||||
ch.send_data "yes\n"
|
ch.send_data "yes\n"
|
||||||
elsif out =~ %r{passphrase}
|
elsif out =~ %r{passphrase}
|
||||||
message = "subversion needs your key's passphrase and cannot proceed"
|
message = "subversion needs your key's passphrase, sending empty string"
|
||||||
actor.logger.info message, prefix
|
actor.logger.info message, prefix
|
||||||
raise message
|
ch.send_data "\n"
|
||||||
elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
|
elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
|
||||||
message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
|
message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
|
||||||
actor.logger.info message, prefix
|
actor.logger.info message, prefix
|
||||||
|
|
|
@ -18,7 +18,6 @@ class ConfigurationTest < Test::Unit::TestCase
|
||||||
|
|
||||||
class MockSCM
|
class MockSCM
|
||||||
attr_reader :configuration
|
attr_reader :configuration
|
||||||
attr_accessor :latest_revision
|
|
||||||
|
|
||||||
def initialize(config)
|
def initialize(config)
|
||||||
@configuration = config
|
@configuration = config
|
||||||
|
@ -184,8 +183,7 @@ class ConfigurationTest < Test::Unit::TestCase
|
||||||
|
|
||||||
def test_release_path_implicit
|
def test_release_path_implicit
|
||||||
@config.set :deploy_to, "/start/of/path"
|
@config.set :deploy_to, "/start/of/path"
|
||||||
@config.source.latest_revision = 2257
|
assert_equal "/start/of/path/releases/#{@config.now.strftime("%Y%m%d%H%M%S")}", @config.release_path
|
||||||
assert_equal "/start/of/path/releases/2257", @config.release_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_release_path_explicit
|
def test_release_path_explicit
|
||||||
|
|
|
@ -44,6 +44,10 @@ class ScmCvsTest < Test::Unit::TestCase
|
||||||
story.each { |stream, line| yield @channels.last, stream, line }
|
story.each { |stream, line| yield @channels.last, stream, line }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def release_path
|
||||||
|
(@config[:now] || Time.now.utc).strftime("%Y%m%d%H%M%S")
|
||||||
|
end
|
||||||
|
|
||||||
def method_missing(sym, *args)
|
def method_missing(sym, *args)
|
||||||
@config.send(sym, *args)
|
@config.send(sym, *args)
|
||||||
end
|
end
|
||||||
|
@ -55,6 +59,7 @@ class ScmCvsTest < Test::Unit::TestCase
|
||||||
@config[:local] = "/hello/world"
|
@config[:local] = "/hello/world"
|
||||||
@config[:cvs] = "/path/to/cvs"
|
@config[:cvs] = "/path/to/cvs"
|
||||||
@config[:password] = "chocolatebrownies"
|
@config[:password] = "chocolatebrownies"
|
||||||
|
@config[:now] = Time.utc(2005,8,24,12,0,0)
|
||||||
@scm = CvsTest.new(@config)
|
@scm = CvsTest.new(@config)
|
||||||
@actor = MockActor.new(@config)
|
@actor = MockActor.new(@config)
|
||||||
@log_msg = <<MSG.strip
|
@log_msg = <<MSG.strip
|
||||||
|
@ -140,7 +145,7 @@ MSG
|
||||||
|
|
||||||
def test_latest_revision
|
def test_latest_revision
|
||||||
@scm.story = [ @log_msg ]
|
@scm.story = [ @log_msg ]
|
||||||
assert_equal "2004-10-12T02:21:02", @scm.latest_revision
|
assert_equal "2004-10-12 02:21:02", @scm.latest_revision
|
||||||
assert_equal "/hello/world", @scm.last_path
|
assert_equal "/hello/world", @scm.last_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue