Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 8993

Show
Ignore:
Timestamp:
03/08/08 19:07:46 (5 months ago)
Author:
minam
Message:

Improved "copy" strategy, including support for local caching and pattern exclusion

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • tools/capistrano/CHANGELOG

    r8939 r8993  
     1*SVN* 
     2 
     3* Improved "copy" strategy supports local caching and pattern exclusion (via :copy_cache and :copy_exclude variables) [Jamis Buck] 
     4 
     5 
    16*2.2.0* February 27, 2008 
    27 
  • tools/capistrano/lib/capistrano/recipes/deploy/strategy/base.rb

    r7136 r8993  
    4747          end 
    4848 
     49          # A wrapper for Kernel#system that logs the command being executed. 
     50          def system(*args) 
     51            logger.trace "executing locally: #{args.join(' ')}" 
     52            super 
     53          end 
     54 
    4955        private 
    5056 
  • tools/capistrano/lib/capistrano/recipes/deploy/strategy/copy.rb

    r7376 r8993  
    1616      # you can set the :copy_strategy variable to :export. 
    1717      # 
    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 an array of file globs or regexps. 
     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, 
    1938      # :copy_compression, which must be one of :gzip, :bz2, or 
    2039      # :zip, and which specifies how the source should be compressed for 
     
    2645        # directory. 
    2746        def deploy! 
    28           logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}" 
    29           system(command) 
     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                next if name == "." || name == ".." 
     64                next if copy_exclude.any? { |pattern| pattern.is_a?(Regexp) ? item =~ pattern : File.fnmatch?(pattern, item, File::FNM_DOTMATCH) } 
     65                if File.directory?(item) 
     66                  queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH) 
     67                  FileUtils.mkdir(File.join(destination, item)) 
     68                else 
     69                  FileUtils.ln(File.join(copy_cache, item), File.join(destination, item)) 
     70                end 
     71              end 
     72            end 
     73          else 
     74            logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}" 
     75            system(command) 
     76          end 
     77 
    3078          File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) } 
    3179 
     
    4997        end 
    5098 
     99        # Returns the location of the local copy cache, if the strategy should 
     100        # use a local cache + copy instead of a new checkout/export every 
     101        # time. Returns +nil+ unless :copy_cache has been set. If :copy_cache 
     102        # is +true+, a default cache location will be returned. 
     103        def copy_cache 
     104          @copy_cache ||= configuration[:copy_cache] == true ? 
     105            File.join(Dir.tmpdir, configuration[:application]) : 
     106            configuration[:copy_cache] 
     107        end 
     108 
    51109        private 
     110 
     111          # Specify patterns to exclude from the copy. This is only valid 
     112          # when using a local cache. 
     113          def copy_exclude 
     114            @copy_exclude ||= Array(configuration.fetch(:copy_exclude, [])) 
     115          end 
    52116 
    53117          # Returns the basename of the release_path, which will be used to 
  • tools/capistrano/test/deploy/strategy/copy_test.rb

    r7859 r8993  
    66class DeployStrategyCopyTest < Test::Unit::TestCase 
    77  def setup 
    8     @config = { :logger => Capistrano::Logger.new(:output => StringIO.new), 
     8    @config = { :application => "captest", 
     9                :logger => Capistrano::Logger.new(:output => StringIO.new), 
    910                :releases_path => "/u/apps/test/releases", 
    1011                :release_path => "/u/apps/test/releases/1234567890", 
     
    1718  def test_deploy_with_defaults_should_use_tar_gz_and_checkout 
    1819    Dir.expects(:tmpdir).returns("/temp/dir") 
    19     Dir.expects(:chdir).with("/temp/dir").yields 
    2020    @source.expects(:checkout).with("154", "/temp/dir/1234567890").returns(:local_checkout) 
    21  
    22     @strategy.expects(:system).with(:local_checkout) 
    23     @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") 
    24     @strategy.expects(:put).with(:mock_file_contents, "/tmp/1234567890.tar.gz") 
    25     @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") 
    26  
    27     mock_file = mock("file") 
    28     mock_file.expects(:puts).with("154") 
    29     File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) 
    30     File.expects(:open).with("/temp/dir/1234567890.tar.gz", "rb").yields(StringIO.new).returns(:mock_file_contents) 
    31  
    32     FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") 
    33     FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") 
    34  
     21    @strategy.expects(:system).with(:local_checkout) 
     22 
     23    prepare_standard_compress_and_copy! 
    3524    @strategy.deploy! 
    3625  end 
     
    3827  def test_deploy_with_export_should_use_tar_gz_and_export 
    3928    Dir.expects(:tmpdir).returns("/temp/dir") 
    40     Dir.expects(:chdir).with("/temp/dir").yields 
    4129    @config[:copy_strategy] = :export 
    4230    @source.expects(:export).with("154", "/temp/dir/1234567890").returns(:local_export) 
    43  
    4431    @strategy.expects(:system).with(:local_export) 
    45     @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") 
    46     @strategy.expects(:put).with(:mock_file_contents, "/tmp/1234567890.tar.gz") 
    47     @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") 
    48  
    49     mock_file = mock("file") 
    50     mock_file.expects(:puts).with("154") 
    51     File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) 
    52     File.expects(:open).with("/temp/dir/1234567890.tar.gz", "rb").yields(StringIO.new).returns(:mock_file_contents) 
    53  
    54     FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") 
    55     FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") 
    56  
     32 
     33    prepare_standard_compress_and_copy! 
    5734    @strategy.deploy! 
    5835  end 
     
    8057  end 
    8158 
    82   def test_deploy_with_bzip2_should_use_zip_and_checkout 
     59  def test_deploy_with_bzip2_should_use_bz2_and_checkout 
    8360    Dir.expects(:tmpdir).returns("/temp/dir") 
    8461    Dir.expects(:chdir).with("/temp/dir").yields 
     
    145122    @strategy.deploy! 
    146123  end 
     124 
     125  def test_with_copy_cache_should_checkout_to_cache_if_cache_does_not_exist_and_then_copy 
     126    @config[:copy_cache] = true 
     127 
     128    Dir.stubs(:tmpdir).returns("/temp/dir") 
     129    File.expects(:exists?).with("/temp/dir/captest").returns(false) 
     130    Dir.expects(:chdir).with("/temp/dir/captest").yields 
     131 
     132    @source.expects(:checkout).with("154", "/temp/dir/captest").returns(:local_checkout) 
     133    @strategy.expects(:system).with(:local_checkout) 
     134 
     135    FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") 
     136 
     137    prepare_directory_tree!("/temp/dir/captest") 
     138 
     139    prepare_standard_compress_and_copy! 
     140    @strategy.deploy! 
     141  end 
     142 
     143  def test_with_copy_cache_should_update_cache_if_cache_exists_and_then_copy 
     144    @config[:copy_cache] = true 
     145 
     146    Dir.stubs(:tmpdir).returns("/temp/dir") 
     147    File.expects(:exists?).with("/temp/dir/captest").returns(true) 
     148    Dir.expects(:chdir).with("/temp/dir/captest").yields 
     149 
     150    @source.expects(:sync).with("154", "/temp/dir/captest").returns(:local_sync) 
     151    @strategy.expects(:system).with(:local_sync) 
     152 
     153    FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") 
     154 
     155    prepare_directory_tree!("/temp/dir/captest") 
     156 
     157    prepare_standard_compress_and_copy! 
     158    @strategy.deploy! 
     159  end 
     160 
     161  def test_with_copy_cache_with_custom_cache_dir_should_use_specified_cache_dir 
     162    @config[:copy_cache] = "/u/caches/captest" 
     163 
     164    Dir.stubs(:tmpdir).returns("/temp/dir") 
     165    File.expects(:exists?).with("/u/caches/captest").returns(true) 
     166    Dir.expects(:chdir).with("/u/caches/captest").yields 
     167 
     168    @source.expects(:sync).with("154", "/u/caches/captest").returns(:local_sync) 
     169    @strategy.expects(:system).with(:local_sync) 
     170 
     171    FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") 
     172 
     173    prepare_directory_tree!("/u/caches/captest") 
     174 
     175    prepare_standard_compress_and_copy! 
     176    @strategy.deploy! 
     177  end 
     178 
     179  def test_with_copy_cache_with_excludes_should_not_copy_excluded_files 
     180    @config[:copy_cache] = true 
     181    @config[:copy_exclude] = "*/bar.txt" 
     182 
     183    Dir.stubs(:tmpdir).returns("/temp/dir") 
     184    File.expects(:exists?).with("/temp/dir/captest").returns(true) 
     185    Dir.expects(:chdir).with("/temp/dir/captest").yields 
     186 
     187    @source.expects(:sync).with("154", "/temp/dir/captest").returns(:local_sync) 
     188    @strategy.expects(:system).with(:local_sync) 
     189 
     190    FileUtils.expects(:mkdir_p).with("/temp/dir/1234567890") 
     191 
     192    prepare_directory_tree!("/temp/dir/captest", true) 
     193 
     194    prepare_standard_compress_and_copy! 
     195    @strategy.deploy! 
     196  end 
     197 
     198  private 
     199 
     200    def prepare_directory_tree!(cache, exclude=false) 
     201      Dir.expects(:glob).with("*", File::FNM_DOTMATCH).returns([".", "..", "app", "foo.txt"]) 
     202      File.expects(:directory?).with("app").returns(true) 
     203      FileUtils.expects(:mkdir).with("/temp/dir/1234567890/app") 
     204      File.expects(:directory?).with("foo.txt").returns(false) 
     205      FileUtils.expects(:ln).with("#{cache}/foo.txt", "/temp/dir/1234567890/foo.txt") 
     206 
     207      Dir.expects(:glob).with("app/*", File::FNM_DOTMATCH).returns(["app/.", "app/..", "app/bar.txt"]) 
     208      unless exclude 
     209        File.expects(:directory?).with("app/bar.txt").returns(false) 
     210        FileUtils.expects(:ln).with("#{cache}/app/bar.txt", "/temp/dir/1234567890/app/bar.txt") 
     211      end 
     212    end 
     213 
     214    def prepare_standard_compress_and_copy! 
     215      Dir.expects(:chdir).with("/temp/dir").yields 
     216      @strategy.expects(:system).with("tar czf 1234567890.tar.gz 1234567890") 
     217      @strategy.expects(:put).with(:mock_file_contents, "/tmp/1234567890.tar.gz") 
     218      @strategy.expects(:run).with("cd /u/apps/test/releases && tar xzf /tmp/1234567890.tar.gz && rm /tmp/1234567890.tar.gz") 
     219 
     220      mock_file = mock("file") 
     221      mock_file.expects(:puts).with("154") 
     222      File.expects(:open).with("/temp/dir/1234567890/REVISION", "w").yields(mock_file) 
     223      File.expects(:open).with("/temp/dir/1234567890.tar.gz", "rb").yields(StringIO.new).returns(:mock_file_contents) 
     224 
     225      FileUtils.expects(:rm).with("/temp/dir/1234567890.tar.gz") 
     226      FileUtils.expects(:rm_rf).with("/temp/dir/1234567890") 
     227    end 
    147228end