diff --git a/lib/puppet/file_bucket/dipper.rb b/lib/puppet/file_bucket/dipper.rb index b012a8681..f4bef28a8 100644 --- a/lib/puppet/file_bucket/dipper.rb +++ b/lib/puppet/file_bucket/dipper.rb @@ -1,99 +1,104 @@ require 'puppet/file_bucket' require 'puppet/file_bucket/file' require 'puppet/indirector/request' class Puppet::FileBucket::Dipper # This is a transitional implementation that uses REST # to access remote filebucket files. attr_accessor :name # Create our bucket client def initialize(hash = {}) # Emulate the XMLRPC client server = hash[:Server] port = hash[:Port] || Puppet[:masterport] environment = Puppet[:environment] if hash.include?(:Path) @local_path = hash[:Path] @rest_path = nil else @local_path = nil @rest_path = "https://#{server}:#{port}/#{environment}/file_bucket_file/" end end def local? !! @local_path end # Back up a file to our bucket def backup(file) raise(ArgumentError, "File #{file} does not exist") unless ::File.exist?(file) contents = ::File.read(file) begin file_bucket_file = Puppet::FileBucket::File.new(contents, :bucket_path => @local_path) dest_path = "#{@rest_path}#{file_bucket_file.name}" - file_bucket_file.save(dest_path) + # Make a HEAD request for the file so that we don't waste time + # uploading it if it already exists in the bucket. + unless Puppet::FileBucket::File.head("#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}") + file_bucket_file.save(dest_path) + end + return file_bucket_file.checksum_data rescue => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Could not back up #{file}: #{detail}" end end # Retrieve a file by sum. def getfile(sum) source_path = "#{@rest_path}md5/#{sum}" file_bucket_file = Puppet::FileBucket::File.find(source_path, :bucket_path => @local_path) raise Puppet::Error, "File not found" unless file_bucket_file file_bucket_file.to_s end # Restore the file def restore(file,sum) restore = true if FileTest.exists?(file) cursum = Digest::MD5.hexdigest(::File.read(file)) # if the checksum has changed... # this might be extra effort if cursum == sum restore = false end end if restore if newcontents = getfile(sum) tmp = "" newsum = Digest::MD5.hexdigest(newcontents) changed = nil if FileTest.exists?(file) and ! FileTest.writable?(file) changed = ::File.stat(file).mode ::File.chmod(changed | 0200, file) end ::File.open(file, ::File::WRONLY|::File::TRUNC|::File::CREAT) { |of| of.print(newcontents) } ::File.chmod(changed, file) if changed else Puppet.err "Could not find file with checksum #{sum}" return nil end return newsum else return nil end end private def absolutize_path( path ) require 'pathname' Pathname.new(path).realpath end end diff --git a/spec/unit/file_bucket/dipper_spec.rb b/spec/unit/file_bucket/dipper_spec.rb index 730e10792..3e9e8b145 100755 --- a/spec/unit/file_bucket/dipper_spec.rb +++ b/spec/unit/file_bucket/dipper_spec.rb @@ -1,72 +1,95 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'pathname' require 'puppet/file_bucket/dipper' describe Puppet::FileBucket::Dipper do include PuppetSpec::Files def make_tmp_file(contents) file = tmpfile("file_bucket_file") File.open(file, 'w') { |f| f.write(contents) } file end + it "should fail in an informative way when there are failures checking for the file on the server" do + @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") + + file = make_tmp_file('contents') + Puppet::FileBucket::File.expects(:head).raises ArgumentError + + lambda { @dipper.backup(file) }.should raise_error(Puppet::Error) + end + it "should fail in an informative way when there are failures backing up to the server" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") file = make_tmp_file('contents') + Puppet::FileBucket::File.expects(:head).returns false Puppet::FileBucket::File.any_instance.expects(:save).raises ArgumentError lambda { @dipper.backup(file) }.should raise_error(Puppet::Error) end it "should backup files to a local bucket" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") file = make_tmp_file('my contents') checksum = Digest::MD5.hexdigest('my contents') + Puppet::FileBucket::File.expects(:head).returns false Puppet::FileBucket::File.any_instance.expects(:save) @dipper.backup(file).should == checksum end + it "should not backup a file that is already in the bucket" do + @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") + + file = make_tmp_file('my contents') + checksum = Digest::MD5.hexdigest('my contents') + + Puppet::FileBucket::File.expects(:head).returns true + Puppet::FileBucket::File.any_instance.expects(:save).never + @dipper.backup(file).should == checksum + end + it "should retrieve files from a local bucket" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") checksum = Digest::MD5.hexdigest('my contents') Puppet::FileBucket::File.expects(:find).with{|x,opts| x == "md5/#{checksum}" }.returns(Puppet::FileBucket::File.new('my contents')) @dipper.getfile(checksum).should == 'my contents' end it "should backup files to a remote server" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") file = make_tmp_file('my contents') checksum = Digest::MD5.hexdigest('my contents') real_path = Pathname.new(file).realpath + Puppet::FileBucket::File.expects(:head).with("https://puppetmaster:31337/production/file_bucket_file/md5/#{checksum}").returns false Puppet::FileBucket::File.any_instance.expects(:save).with("https://puppetmaster:31337/production/file_bucket_file/md5/#{checksum}") @dipper.backup(file).should == checksum end it "should retrieve files from a remote server" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") checksum = Digest::MD5.hexdigest('my contents') Puppet::FileBucket::File.expects(:find).with{|x,opts| x == "https://puppetmaster:31337/production/file_bucket_file/md5/#{checksum}" }.returns(Puppet::FileBucket::File.new('my contents')) @dipper.getfile(checksum).should == "my contents" end end