summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/checksum.rb37
-rw-r--r--lib/puppet/indirector/file.rb34
-rw-r--r--lib/puppet/indirector/file/checksum.rb33
-rwxr-xr-xspec/unit/indirector/file.rb50
-rwxr-xr-xspec/unit/indirector/file/checksum.rb154
-rwxr-xr-xspec/unit/other/checksum.rb60
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