diff --git a/switchtower/CHANGELOG b/switchtower/CHANGELOG index 7b3a24a42d..27f69cef2b 100644 --- a/switchtower/CHANGELOG +++ b/switchtower/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Changed behavior of checkout to use the timestamp as the release name, instead of the revision number + * Added CVS module (very very experimental!) * Works with public keys now, for passwordless deployment diff --git a/switchtower/README b/switchtower/README index 8c7b0fae62..45bad8bbe3 100644 --- a/switchtower/README +++ b/switchtower/README @@ -21,7 +21,9 @@ As with the rest of Rails, if you can abide by these assumptions, you can use Sw == 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. * Use the +switchtower+ script to execute your recipe (see below). diff --git a/switchtower/lib/switchtower/actor.rb b/switchtower/lib/switchtower/actor.rb index 012f4a1f55..a74ea95a24 100644 --- a/switchtower/lib/switchtower/actor.rb +++ b/switchtower/lib/switchtower/actor.rb @@ -248,7 +248,7 @@ module SwitchTower buffer << out if str == :out raise "could not determine releases #{out.inspect}" if str == :err end - @releases = buffer.split.map { |i| i.to_i }.sort + @releases = buffer.split.sort end @releases diff --git a/switchtower/lib/switchtower/configuration.rb b/switchtower/lib/switchtower/configuration.rb index 937618a727..c6ddff5568 100644 --- a/switchtower/lib/switchtower/configuration.rb +++ b/switchtower/lib/switchtower/configuration.rb @@ -26,12 +26,17 @@ module SwitchTower # The load paths used for locating recipe files. 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: @roles = Hash.new { |h,k| h[k] = [] } @actor = actor_class.new(self) @logger = Logger.new @load_paths = [".", File.join(File.dirname(__FILE__), "recipes")] @variables = {} + @now = Time.now.utc set :application, nil set :repository, nil @@ -165,10 +170,10 @@ module SwitchTower File.join(deploy_to, shared_dir) end - # Return the full path to the named revision (defaults to the most current - # revision in the repository). - def release_path(revision=source.latest_revision) - File.join(releases_path, revision) + # Return the full path to the named release. If a release is not specified, + # +now+ is used (the time at which the configuration was created). + def release_path(release=now.strftime("%Y%m%d%H%M%S")) + File.join(releases_path, release) end def respond_to?(sym) #:nodoc: diff --git a/switchtower/lib/switchtower/scm/base.rb b/switchtower/lib/switchtower/scm/base.rb new file mode 100644 index 0000000000..4e079d2758 --- /dev/null +++ b/switchtower/lib/switchtower/scm/base.rb @@ -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 diff --git a/switchtower/lib/switchtower/scm/cvs.rb b/switchtower/lib/switchtower/scm/cvs.rb index a4c588e611..db3eae9261 100644 --- a/switchtower/lib/switchtower/scm/cvs.rb +++ b/switchtower/lib/switchtower/scm/cvs.rb @@ -1,4 +1,5 @@ require 'time' +require 'switchtower/scm/base' module SwitchTower module SCM @@ -21,13 +22,7 @@ module SwitchTower # Also, you can specify the CVS_RSH variable to use on the remote machine(s) # via the :cvs_rsh variable. This defaults to the value of the # CVS_RSH environment variable locally, or if it is not set, to "ssh". - class Cvs - attr_reader :configuration - - def initialize(configuration) #:nodoc: - @configuration = configuration - end - + class Cvs < Base # 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 # 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..." @latest_revision = cvs_log(configuration.local). split(/\r?\n/). - grep(/^date: (.*?);/) { Time.parse($1).strftime("%FT%T") }. + grep(/^date: (.*?);/) { Time.parse($1).strftime("%F %T") }. sort. last end @@ -49,12 +44,9 @@ module SwitchTower cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh" command = <<-CMD - if [[ -d #{actor.release_path} ]]; then - cd #{actor.release_path}; - CVS_RSH="#{cvs_rsh}" #{cvs} -q up -d#{latest_revision}; - else + if [[ ! -d #{actor.release_path} ]]; then 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 CMD actor.run(command) do |ch, stream, out| diff --git a/switchtower/lib/switchtower/scm/darcs.rb b/switchtower/lib/switchtower/scm/darcs.rb index 93735446ed..acaa006951 100644 --- a/switchtower/lib/switchtower/scm/darcs.rb +++ b/switchtower/lib/switchtower/scm/darcs.rb @@ -1,4 +1,4 @@ -require 'time' +require 'switchtower/scm/base' module SwitchTower module SCM @@ -13,26 +13,7 @@ module SwitchTower # executable on the remote machine: # # set :darcs, "/opt/local/bin/darcs" - class Darcs - 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 - + class Darcs < Base # Check out (on all servers associated with the current task) the latest # revision. Uses the given actor instance to execute the command. def checkout(actor) @@ -40,7 +21,7 @@ module SwitchTower command = <<-CMD 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 CMD actor.run(command) diff --git a/switchtower/lib/switchtower/scm/subversion.rb b/switchtower/lib/switchtower/scm/subversion.rb index cc5d78c3e6..368c965dec 100644 --- a/switchtower/lib/switchtower/scm/subversion.rb +++ b/switchtower/lib/switchtower/scm/subversion.rb @@ -1,3 +1,5 @@ +require 'switchtower/scm/base' + module SwitchTower module SCM @@ -12,13 +14,7 @@ module SwitchTower # executable on the remote machine: # # set :svn, "/opt/local/bin/svn" - class Subversion - attr_reader :configuration - - def initialize(configuration) #:nodoc: - @configuration = configuration - end - + class Subversion < Base # Return an integer identifying the last known revision in the svn # repository. (This integer is currently the revision number.) If latest # revision does not exist in the given repository, this routine will @@ -64,9 +60,9 @@ module SwitchTower prefix ch.send_data "yes\n" 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 - raise message + ch.send_data "\n" 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." actor.logger.info message, prefix diff --git a/switchtower/test/configuration_test.rb b/switchtower/test/configuration_test.rb index 2ec299ab6d..ecbc0a5607 100644 --- a/switchtower/test/configuration_test.rb +++ b/switchtower/test/configuration_test.rb @@ -18,7 +18,6 @@ class ConfigurationTest < Test::Unit::TestCase class MockSCM attr_reader :configuration - attr_accessor :latest_revision def initialize(config) @configuration = config @@ -184,8 +183,7 @@ class ConfigurationTest < Test::Unit::TestCase def test_release_path_implicit @config.set :deploy_to, "/start/of/path" - @config.source.latest_revision = 2257 - assert_equal "/start/of/path/releases/2257", @config.release_path + assert_equal "/start/of/path/releases/#{@config.now.strftime("%Y%m%d%H%M%S")}", @config.release_path end def test_release_path_explicit diff --git a/switchtower/test/scm/cvs_test.rb b/switchtower/test/scm/cvs_test.rb index 3df64cce0e..4532870826 100644 --- a/switchtower/test/scm/cvs_test.rb +++ b/switchtower/test/scm/cvs_test.rb @@ -44,6 +44,10 @@ class ScmCvsTest < Test::Unit::TestCase story.each { |stream, line| yield @channels.last, stream, line } end + def release_path + (@config[:now] || Time.now.utc).strftime("%Y%m%d%H%M%S") + end + def method_missing(sym, *args) @config.send(sym, *args) end @@ -55,6 +59,7 @@ class ScmCvsTest < Test::Unit::TestCase @config[:local] = "/hello/world" @config[:cvs] = "/path/to/cvs" @config[:password] = "chocolatebrownies" + @config[:now] = Time.utc(2005,8,24,12,0,0) @scm = CvsTest.new(@config) @actor = MockActor.new(@config) @log_msg = <