diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb index 38e0be6e9..8bea2d767 100644 --- a/lib/puppet/indirector/file_bucket_file/file.rb +++ b/lib/puppet/indirector/file_bucket_file/file.rb @@ -1,106 +1,106 @@ require 'puppet/indirector/code' require 'puppet/file_bucket/file' require 'puppet/util/checksums' require 'fileutils' module Puppet::FileBucketFile class File < Puppet::Indirector::Code include Puppet::Util::Checksums desc "Store files in a directory set based on their checksums." def initialize Puppet.settings.use(:filebucket) end def find( request ) checksum = request_to_checksum( request ) file_path = path_for(request.options[:bucket_path], checksum, 'contents') return nil unless ::File.exists?(file_path) if request.options[:diff_with] hash_protocol = sumtype(checksum) file2_path = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') raise "could not find diff_with #{request.options[:diff_with]}" unless ::File.exists?(file2_path) return `diff #{file_path.inspect} #{file2_path.inspect}` else contents = ::File.read file_path Puppet.info "FileBucket read #{checksum}" model.new(contents) end end def head(request) checksum = request_to_checksum(request) file_path = path_for(request.options[:bucket_path], checksum, 'contents') ::File.exists?(file_path) end def save( request ) instance = request.instance save_to_disk(instance) instance.to_s end private def save_to_disk( bucket_file ) filename = path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents') dirname = path_for(bucket_file.bucket_path, bucket_file.checksum_data) # If the file already exists, do nothing. if ::File.exist?(filename) verify_identical_file!(bucket_file) else # Make the directories if necessary. unless ::File.directory?(dirname) Puppet::Util.withumask(0007) do ::FileUtils.mkdir_p(dirname) end end Puppet.info "FileBucket adding #{bucket_file.checksum}" # Write the file to disk. Puppet::Util.withumask(0007) do ::File.open(filename, ::File::WRONLY|::File::CREAT, 0440) do |of| of.print bucket_file.contents end end end end def request_to_checksum( request ) - checksum_type, checksum = request.key.split(/\//, 2) + checksum_type, checksum, path = request.key.split(/\//, 3) # Note: we ignore path if present. raise "Unsupported checksum type #{checksum_type.inspect}" if checksum_type != 'md5' raise "Invalid checksum #{checksum.inspect}" if checksum !~ /^[0-9a-f]{32}$/ checksum end def path_for(bucket_path, digest, subfile = nil) bucket_path ||= Puppet[:bucketdir] dir = ::File.join(digest[0..7].split("")) basedir = ::File.join(bucket_path, dir, digest) return basedir unless subfile ::File.join(basedir, subfile) end # If conflict_check is enabled, verify that the passed text is # the same as the text in our file. def verify_identical_file!(bucket_file) disk_contents = ::File.read(path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents')) # If the contents don't match, then we've found a conflict. # Unlikely, but quite bad. if disk_contents != bucket_file.contents raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}" else Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}" end end end end diff --git a/spec/unit/indirector/file_bucket_file/file_spec.rb b/spec/unit/indirector/file_bucket_file/file_spec.rb index cb614f36e..9187f4da0 100755 --- a/spec/unit/indirector/file_bucket_file/file_spec.rb +++ b/spec/unit/indirector/file_bucket_file/file_spec.rb @@ -1,191 +1,185 @@ #!/usr/bin/env ruby require ::File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/file_bucket_file/file' describe Puppet::FileBucketFile::File do include PuppetSpec::Files it "should be a subclass of the Code terminus class" do Puppet::FileBucketFile::File.superclass.should equal(Puppet::Indirector::Code) end it "should have documentation" do Puppet::FileBucketFile::File.doc.should be_instance_of(String) end describe "non-stubbing tests" do include PuppetSpec::Files before do Puppet[:bucketdir] = tmpdir('bucketdir') end describe "when diffing files" do def save_bucket_file(contents) bucket_file = Puppet::FileBucket::File.new(contents) bucket_file.save bucket_file.checksum_data end it "should generate an empty string if there is no diff" do checksum = save_bucket_file("I'm the contents of a file") Puppet::FileBucket::File.find("md5/#{checksum}", :diff_with => checksum).should == '' end it "should generate a proper diff if there is a diff" do checksum1 = save_bucket_file("foo\nbar\nbaz") checksum2 = save_bucket_file("foo\nbiz\nbaz") diff = Puppet::FileBucket::File.find("md5/#{checksum1}", :diff_with => checksum2) diff.should == < biz HERE end it "should raise an exception if the hash to diff against isn't found" do checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" lambda { Puppet::FileBucket::File.find("md5/#{checksum}", :diff_with => bogus_checksum) }.should raise_error "could not find diff_with #{bogus_checksum}" end it "should return nil if the hash to diff from isn't found" do checksum = save_bucket_file("whatever") bogus_checksum = "d1bf072d0e2c6e20e3fbd23f022089a1" Puppet::FileBucket::File.find("md5/#{bogus_checksum}", :diff_with => checksum).should == nil end end end describe "when initializing" do it "should use the filebucket settings section" do Puppet.settings.expects(:use).with(:filebucket) Puppet::FileBucketFile::File.new end end [true, false].each do |override_bucket_path| - describe "when retrieving files and bucket path #{if override_bucket_path then 'is' else 'is not' end} overridden" do - before :each do - Puppet.settings.stubs(:use) - @store = Puppet::FileBucketFile::File.new - - @digest = "70924d6fa4b2d745185fa4660703a5c0" - - @bucket_dir = tmpdir("bucket") - - if override_bucket_path - Puppet[:bucketdir] = "/bogus/path" # should not be used - else - Puppet[:bucketdir] = @bucket_dir - end - - @dir = "#{@bucket_dir}/7/0/9/2/4/d/6/f/70924d6fa4b2d745185fa4660703a5c0" - @contents_path = "#{@dir}/contents" - - request_options = {} - if override_bucket_path - request_options[:bucket_path] = @bucket_dir + describe "when bucket path #{if override_bucket_path then 'is' else 'is not' end} overridden" do + [true, false].each do |supply_path| + describe "when #{supply_path ? 'supplying' : 'not supplying'} a path" do + before :each do + Puppet.settings.stubs(:use) + @store = Puppet::FileBucketFile::File.new + @contents = "my content" + + @digest = "f2bfa7fc155c4f42cb91404198dda01f" + @digest.should == Digest::MD5.hexdigest(@contents) + + @bucket_dir = tmpdir("bucket") + + if override_bucket_path + Puppet[:bucketdir] = "/bogus/path" # should not be used + else + Puppet[:bucketdir] = @bucket_dir + end + + @dir = "#{@bucket_dir}/f/2/b/f/a/7/f/c/f2bfa7fc155c4f42cb91404198dda01f" + @contents_path = "#{@dir}/contents" + end + + describe "when retrieving files" do + before :each do + + request_options = {} + if override_bucket_path + request_options[:bucket_path] = @bucket_dir + end + + key = "md5/#{@digest}" + if supply_path + key += "//path/to/file" + end + + @request = Puppet::Indirector::Request.new(:indirection_name, :find, key, request_options) + end + + def make_bucketed_file + FileUtils.mkdir_p(@dir) + File.open(@contents_path, 'w') { |f| f.write @contents } + end + + it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do + make_bucketed_file + + bucketfile = @store.find(@request) + bucketfile.should be_a(Puppet::FileBucket::File) + bucketfile.contents.should == @contents + @store.head(@request).should == true + end + + it "should return nil if no file is found" do + @store.find(@request).should be_nil + @store.head(@request).should == false + end + end + + describe "when saving files" do + it "should save the contents to the calculated path" do + options = {} + if override_bucket_path + options[:bucket_path] = @bucket_dir + end + + key = "md5/#{@digest}" + if supply_path + key += "//path/to/file" + end + + file_instance = Puppet::FileBucket::File.new(@contents, options) + request = Puppet::Indirector::Request.new(:indirection_name, :save, key, file_instance) + + @store.save(request) + File.read("#{@dir}/contents").should == @contents + end + end end - - @request = Puppet::Indirector::Request.new(:indirection_name, :find, "md5/#{@digest}", request_options) - end - - def make_bucketed_file - FileUtils.mkdir_p(@dir) - File.open(@contents_path, 'w') { |f| f.write @contents } - end - - it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do - @contents = "my content" - make_bucketed_file - - bucketfile = @store.find(@request) - bucketfile.should be_a(Puppet::FileBucket::File) - bucketfile.contents.should == @contents - end - - it "should return nil if no file is found" do - @store.find(@request).should be_nil end end end - describe "when saving files" do - before do - # this is the default from spec_helper, but it keeps getting reset at odd times - Puppet[:bucketdir] = "/dev/null/bucket" - - @digest = "4a8ec4fa5f01b4ab1a0ab8cbccb709f0" - @checksum = "{md5}4a8ec4fa5f01b4ab1a0ab8cbccb709f0" - @dir = '/dev/null/bucket/4/a/8/e/c/4/f/a/4a8ec4fa5f01b4ab1a0ab8cbccb709f0' - - @contents = "file contents" - - @bucket = stub "bucket file" - @bucket.stubs(:bucket_path) - @bucket.stubs(:checksum_data).returns(@digest) - @bucket.stubs(:path).returns(nil) - @bucket.stubs(:checksum).returns(nil) - @bucket.stubs(:contents).returns("file contents") - end - - it "should save the contents to the calculated path" do - ::File.stubs(:directory?).with(@dir).returns(true) - ::File.expects(:exist?).with("#{@dir}/contents").returns false - - mockfile = mock "file" - mockfile.expects(:print).with(@contents) - ::File.expects(:open).with("#{@dir}/contents", ::File::WRONLY|::File::CREAT, 0440).yields(mockfile) - - Puppet::FileBucketFile::File.new.send(:save_to_disk, @bucket) - end - - it "should make any directories necessary for storage" do - FileUtils.expects(:mkdir_p).with do |arg| - ::File.umask == 0007 and arg == @dir - end - ::File.expects(:directory?).with(@dir).returns(false) - ::File.expects(:open).with("#{@dir}/contents", ::File::WRONLY|::File::CREAT, 0440) - ::File.expects(:exist?).with("#{@dir}/contents").returns false - - Puppet::FileBucketFile::File.new.send(:save_to_disk, @bucket) - end - end - - describe "when verifying identical files" do before do # this is the default from spec_helper, but it keeps getting reset at odd times Puppet[:bucketdir] = "/dev/null/bucket" @digest = "4a8ec4fa5f01b4ab1a0ab8cbccb709f0" @checksum = "{md5}4a8ec4fa5f01b4ab1a0ab8cbccb709f0" @dir = '/dev/null/bucket/4/a/8/e/c/4/f/a/4a8ec4fa5f01b4ab1a0ab8cbccb709f0' @contents = "file contents" @bucket = stub "bucket file" @bucket.stubs(:bucket_path) @bucket.stubs(:checksum).returns(@checksum) @bucket.stubs(:checksum_data).returns(@digest) @bucket.stubs(:path).returns(nil) @bucket.stubs(:contents).returns("file contents") end it "should raise an error if the files don't match" do File.expects(:read).with("#{@dir}/contents").returns("corrupt contents") lambda{ Puppet::FileBucketFile::File.new.send(:verify_identical_file!, @bucket) }.should raise_error(Puppet::FileBucket::BucketError) end it "should do nothing if the files match" do File.expects(:read).with("#{@dir}/contents").returns("file contents") Puppet::FileBucketFile::File.new.send(:verify_identical_file!, @bucket) end end end