diff options
-rw-r--r-- | lib/puppet/checksum.rb | 37 | ||||
-rw-r--r-- | lib/puppet/indirector/file.rb | 34 | ||||
-rw-r--r-- | lib/puppet/indirector/file/checksum.rb | 33 | ||||
-rwxr-xr-x | spec/unit/indirector/file.rb | 50 | ||||
-rwxr-xr-x | spec/unit/indirector/file/checksum.rb | 154 | ||||
-rwxr-xr-x | spec/unit/other/checksum.rb | 60 |
6 files changed, 358 insertions, 10 deletions
diff --git a/lib/puppet/checksum.rb b/lib/puppet/checksum.rb new file mode 100644 index 000000000..65fb7ef76 --- /dev/null +++ b/lib/puppet/checksum.rb @@ -0,0 +1,37 @@ +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet' +require 'puppet/indirector' + +# A checksum class to model translating checksums to file paths. This +# is the new filebucket. +class Puppet::Checksum + extend Puppet::Indirector + + indirects :checksum + + attr_accessor :name, :content + attr_reader :algorithm + + def algorithm=(value) + value = value.intern if value.respond_to?(:intern) + @algorithm = value + end + + def initialize(name) + raise ArgumentError.new("You must specify the checksum") unless name + + if name =~ /^\{(\w+)\}(.+$)$/ + @algorithm, @name = $1.intern, $2 + else + @name = name + @algorithm = :md5 + end + end + + def to_s + "Checksum<{%s}%s>" % [algorithm, name] + end +end diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb index 75fcf1ddf..4f231e9ec 100644 --- a/lib/puppet/indirector/file.rb +++ b/lib/puppet/indirector/file.rb @@ -3,16 +3,27 @@ require 'puppet/indirector/terminus' # An empty terminus type, meant to just return empty objects. class Puppet::Indirector::File < Puppet::Indirector::Terminus def destroy(file) - raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file.name]) unless File.exist?(file.path) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file]) unless File.exist?(path) begin - File.unlink(file.path) + File.unlink(path) rescue => detail - raise Puppet::Error, "Could not remove %s: %s" % [file.path, detail] + raise Puppet::Error, "Could not remove %s: %s" % [file, detail] end end - def find(path) + def find(name) + if respond_to?(:path) + path = path(name) + else + path = name + end + return nil unless File.exist?(path) begin @@ -21,20 +32,25 @@ class Puppet::Indirector::File < Puppet::Indirector::Terminus raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] end - file = model.new(path) + file = model.new(name) file.content = content return file end def save(file) - dir = File.dirname(file.path) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + dir = File.dirname(path) - raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file.name, dir]) unless File.directory?(dir) + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file, dir]) unless File.directory?(dir) begin - File.open(file.path, "w") { |f| f.print file.content } + File.open(path, "w") { |f| f.print file.content } rescue => detail - raise Puppet::Error, "Could not write %s: %s" % [file.path, detail] + raise Puppet::Error, "Could not write %s: %s" % [file, detail] end end end diff --git a/lib/puppet/indirector/file/checksum.rb b/lib/puppet/indirector/file/checksum.rb new file mode 100644 index 000000000..2f0974ced --- /dev/null +++ b/lib/puppet/indirector/file/checksum.rb @@ -0,0 +1,33 @@ +require 'puppet/checksum' +require 'puppet/indirector/file' + +class Puppet::Indirector::File::Checksum < Puppet::Indirector::File + desc "Store files in a directory set based on their checksums." + + def initialize + Puppet.settings.use(:filebucket) + end + + def path(checksum) + path = [] + path << Puppet[:bucketdir] # Start with the base directory + path << checksum[0..7].split("").join(File::SEPARATOR) # Add sets of directories based on the checksum + path << checksum # And the full checksum name itself + path << "contents" # And the actual file name + + path.join(File::SEPARATOR) + end + + def save(file) + path = File.dirname(path(file.name)) + + # Make the directories if necessary. + unless FileTest.directory?(path) + Puppet::Util.withumask(0007) do + FileUtils.mkdir_p(path) + end + end + + super + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb index d08ff6455..176832880 100755 --- a/spec/unit/indirector/file.rb +++ b/spec/unit/indirector/file.rb @@ -59,11 +59,36 @@ describe Puppet::Indirector::File, " when finding files" do end it "should fail intelligently if a found file cannot be read" do - content = "my content" File.expects(:exist?).with(@path).returns(true) File.expects(:read).with(@path).raises(RuntimeError) proc { @searcher.find(@path) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + File.expects(:exist?).with(@path.upcase).returns(false) + @searcher.find(@path) + end + + it "should use the passed-in name for the model instance even if a path() method exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + content = "something" + file = mock 'file' + file.expects(:content=).with(content) + + # The passed-in path, rather than the upcased version + @model.expects(:new).with(@path).returns(file) + + File.expects(:exist?).with(@path.upcase).returns(true) + File.expects(:read).with(@path.upcase).returns(content) + @searcher.find(@path).should equal(file) + end end describe Puppet::Indirector::File, " when saving files" do @@ -100,6 +125,17 @@ describe Puppet::Indirector::File, " when saving files" do proc { @searcher.save(file) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + + File.expects(:open).with("/YAY", "w") + @searcher.save(file) + end end describe Puppet::Indirector::File, " when removing files" do @@ -127,4 +163,16 @@ describe Puppet::Indirector::File, " when removing files" do proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + File.expects(:exist?).with("/YAY").returns(true) + File.expects(:unlink).with("/YAY") + + @searcher.destroy(file) + end end diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb new file mode 100755 index 000000000..950a377a7 --- /dev/null +++ b/spec/unit/indirector/file/checksum.rb @@ -0,0 +1,154 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/file/checksum' + +describe Puppet::Indirector::File::Checksum do + it "should be a subclass of the File terminus class" do + Puppet::Indirector::File::Checksum.superclass.should equal(Puppet::Indirector::File) + end + + it "should have documentation" do + Puppet::Indirector::File::Checksum.doc.should be_instance_of(String) + end +end + +describe Puppet::Indirector::File::Checksum, " when initializing" do + it "should use the filebucket settings section" do + Puppet.settings.expects(:use).with(:filebucket) + Puppet::Indirector::File::Checksum.new + end +end + +describe Puppet::Indirector::File::Checksum, " when determining file paths" do + before do + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end + + # I was previously passing the object in. + it "should use the value passed in to path() as the checksum" do + @value.expects(:name).never + @store.path(@value) + end + + it "should use the value of the :bucketdir setting as the root directory" do + @path.should =~ %r{^#{@dir}} + end + + it "should choose a path 8 directories deep with each directory name being the respective character in the checksum" do + dirs = @value[0..7].split("").join(File::SEPARATOR) + @path.should be_include(dirs) + end + + it "should use the full checksum as the final directory name" do + File.basename(File.dirname(@path)).should == @value + end + + it "should use 'contents' as the actual file name" do + File.basename(@path).should == "contents" + end + + it "should use the bucketdir, the 8 sum character directories, the full checksum, and 'contents' as the full file name" do + @path.should == [@dir, @value[0..7].split(""), @value, "contents"].flatten.join(File::SEPARATOR) + end +end + +module FileChecksumTesting + def setup + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + @sum = stub 'sum', :name => @value + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end +end + +describe Puppet::Indirector::File::Checksum, " when retrieving files" do + include FileChecksumTesting + + # The smallest test that will use the calculated path + it "should look for the calculated path" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value) + end + + it "should return an instance of Puppet::Checksum with the name and content set correctly if the file exists" do + content = "my content" + sum = stub 'file' + sum.expects(:content=).with(content) + Puppet::Checksum.expects(:new).with(@value).returns(sum) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + + @store.find(@value).should equal(sum) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @store.find(@value) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File::Checksum, " when saving files" do + include FileChecksumTesting + + # LAK:FIXME I don't know how to include in the spec the fact that we're + # using the superclass's save() method and thus are acquiring all of + # it's behaviours. + it "should save the content to the calculated path" do + File.stubs(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end + + it "should make any directories necessary for storage" do + FileUtils.expects(:mkdir_p).with do |arg| + File.umask == 0007 and arg == File.dirname(@path) + end + File.expects(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end +end + +describe Puppet::Indirector::File::Checksum, " when deleting files" do + include FileChecksumTesting + + it "should remove the file at the calculated path" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + file = stub 'file', :name => @value + @store.destroy(file) + end +end diff --git a/spec/unit/other/checksum.rb b/spec/unit/other/checksum.rb new file mode 100755 index 000000000..5610870a6 --- /dev/null +++ b/spec/unit/other/checksum.rb @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum do + it "should have 'Checksum' and the checksum algorithm when converted to a string" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "yay" + sum.to_s.should == "Checksum<{yay}whatever>" + end + + it "should convert algorithm names to symbols when they are set after checksum creation" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "yay" + sum.algorithm.should == :yay + end +end + +describe Puppet::Checksum, " when initializing" do + it "should require a name" do + proc { Puppet::Checksum.new(nil) }.should raise_error(ArgumentError) + end + + it "should set the name appropriately" do + Puppet::Checksum.new("whatever").name.should == "whatever" + end + + it "should parse checksum algorithms out of the name if they are there" do + sum = Puppet::Checksum.new("{other}whatever") + sum.algorithm.should == :other + sum.name.should == "whatever" + end + + it "should default to 'md5' as the checksum algorithm if the algorithm is not in the name" do + Puppet::Checksum.new("whatever").algorithm.should == :md5 + end +end + +describe Puppet::Checksum, " when using back-ends" do + it "should redirect using Puppet::Indirector" do + Puppet::Indirector::Indirection.instance(:checksum).model.should equal(Puppet::Checksum) + end + + it "should have a :save instance method" do + Puppet::Checksum.new("mysum").should respond_to(:save) + end + + it "should respond to :find" do + Puppet::Checksum.should respond_to(:find) + end + + it "should respond to :destroy" do + Puppet::Checksum.should respond_to(:destroy) + end +end |