[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, experimental, updated. debian/2.6.8-1-844-g7ec39d5

Matt Robinson matt at puppetlabs.com
Tue May 10 08:03:53 UTC 2011


The following commit has been merged in the experimental branch:
commit 6d9cae2e9ca6a56506f679db02ba9abb30a4df91
Merge: 27abd84611564ac573c5fde8abb6b98e6bd3d9b7 517c6794606e9adde7f2912d3b949cfcc18a446a
Author: Matt Robinson <matt at puppetlabs.com>
Date:   Wed Jan 19 17:36:23 2011 -0800

    Merge branch '2.6.x' into next
    
    * 2.6.x: (21 commits)
      (#5900) Include ResourceStatus#failed in serialized reports
      (#5882) Added error-handling for bucketing files in puppet inspect
      (#5882) Added error-handling to puppet inspect when auditing
      (#5171) Made "puppet inspect" upload audited files to a file bucket
      Prep for #5171: Added a missing require to inspect application.
      Locked Puppet license to GPLv2
      (#5838) Support paths as part of file bucket requests.
      (#5838) Improve the quality of file bucket specs.
      (#5838) Make file bucket dipper efficient when saving a file that already exists
      (#5838) Implemented the "head" method for FileBucketFile::File terminus.
      (#5838) Reworked file dipper spec to perform less stubbing.
      (#5838) Added support for HEAD requests to the indirector.
      (#5838) Refactored error handling logic into find_in_cache.
      (#5838) Refactored Puppet::Network::Rights#fail_on_deny
      maint: Remove unused Rakefile in spec directory
      (#5171) Made filebucket able to perform diffs
      (#5710) Removed unnecessary calls to insync?
      Prep for fixing #5710: Refactor stub provider in resource harness spec
      Maint: test partial resource failure
      maint: Inspect reports should have audited = true on events
      ...
    
    Manually Resolved Conflicts:
    	lib/puppet/file_bucket/dipper.rb
    	lib/puppet/indirector.rb
    	lib/puppet/network/rest_authconfig.rb
    	spec/unit/file_bucket/dipper_spec.rb
    	spec/unit/file_bucket/file_spec.rb
    	spec/unit/indirector_spec.rb

diff --combined lib/puppet/application/inspect.rb
index 8c3a001,77e8476..19324e2
--- a/lib/puppet/application/inspect.rb
+++ b/lib/puppet/application/inspect.rb
@@@ -1,4 -1,6 +1,6 @@@
+ require 'puppet'
  require 'puppet/application'
+ require 'puppet/file_bucket/dipper'
  
  class Puppet::Application::Inspect < Puppet::Application
  
@@@ -38,14 -40,14 +40,14 @@@
        Puppet::Util::Log.level = :info
      end
  
 -    Puppet::Transaction::Report.terminus_class = :rest
 -    Puppet::Resource::Catalog.terminus_class = :yaml
 +    Puppet::Transaction::Report.indirection.terminus_class = :rest
 +    Puppet::Resource::Catalog.indirection.terminus_class = :yaml
    end
  
    def run_command
      retrieval_starttime = Time.now
  
 -    unless catalog = Puppet::Resource::Catalog.find(Puppet[:certname])
 +    unless catalog = Puppet::Resource::Catalog.indirection.find(Puppet[:certname])
        raise "Could not find catalog for #{Puppet[:certname]}"
      end
  
@@@ -54,20 -56,55 +56,55 @@@
      inspect_starttime = Time.now
      @report.add_times("config_retrieval", inspect_starttime - retrieval_starttime)
  
+     if Puppet[:archive_files]
+       dipper = Puppet::FileBucket::Dipper.new(:Server => Puppet[:archive_file_server])
+     end
+ 
      catalog.to_ral.resources.each do |ral_resource|
        audited_attributes = ral_resource[:audit]
        next unless audited_attributes
  
-       audited_resource = ral_resource.to_resource
- 
        status = Puppet::Resource::Status.new(ral_resource)
-       audited_attributes.each do |name|
-         next if audited_resource[name].nil?
-         # Skip :absent properties of :absent resources. Really, it would be nicer if the RAL returned nil for those, but it doesn't. ~JW
-         if name == :ensure or audited_resource[:ensure] != :absent or audited_resource[name] != :absent
-           event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}")
+ 
+       begin
+         audited_resource = ral_resource.to_resource
+       rescue StandardError => detail
+         puts detail.backtrace if Puppet[:trace]
+         ral_resource.err "Could not inspect #{ral_resource}; skipping: #{detail}"
+         audited_attributes.each do |name|
+           event = ral_resource.event(
+             :property => name,
+             :status   => "failure",
+             :audited  => true,
+             :message  => "failed to inspect #{name}"
+           )
            status.add_event(event)
          end
+       else
+         audited_attributes.each do |name|
+           next if audited_resource[name].nil?
+           # Skip :absent properties of :absent resources. Really, it would be nicer if the RAL returned nil for those, but it doesn't. ~JW
+           if name == :ensure or audited_resource[:ensure] != :absent or audited_resource[name] != :absent
+             event = ral_resource.event(
+               :previous_value => audited_resource[name],
+               :property       => name,
+               :status         => "audit",
+               :audited        => true,
+               :message        => "inspected value is #{audited_resource[name].inspect}"
+             )
+             status.add_event(event)
+           end
+         end
+       end
+       if Puppet[:archive_files] and ral_resource.type == :file and audited_attributes.include?(:content)
+         path = ral_resource[:path]
+         if File.readable?(path)
+           begin
+             dipper.backup(path)
+           rescue StandardError => detail
+             Puppet.warning detail
+           end
+         end
        end
        @report.add_resource_status(status)
      end
@@@ -77,7 -114,7 +114,7 @@@
      @report.finalize_report
  
      begin
 -      @report.save
 +      Puppet::Transaction::Report.indirection.save(@report)
      rescue => detail
        puts detail.backtrace if Puppet[:trace]
        Puppet.err "Could not send report: #{detail}"
diff --combined lib/puppet/defaults.rb
index 1e7229d,400d59f..e8b9846
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@@ -115,17 -115,7 +115,17 @@@ module Puppe
      :node_terminus => ["plain", "Where to find information about nodes."],
      :catalog_terminus => ["compiler", "Where to get node catalogs.  This is useful to change if, for instance,
        you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."],
 -    :facts_terminus => [Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', "The node facts terminus."],
 +    :facts_terminus => {
 +      :default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter',
 +      :desc => "The node facts terminus.",
 +      :hook => proc do |value|
 +        require 'puppet/node/facts'
 +        if value.to_s == "rest"
 +          Puppet::Node::Facts.indirection.cache_class = :yaml
 +        end
 +      end
 +    },
 +    :inventory_terminus => [ "$facts_terminus", "Should usually be the same as the facts terminus" ],
      :httplog => { :default => "$logdir/http.log",
        :owner => "root",
        :mode => 0640,
@@@ -152,7 -142,7 +152,7 @@@
            Puppet.settings[:storeconfigs] = true
  
            # But then we modify the configuration
 -          Puppet::Resource::Catalog.cache_class = :queue
 +          Puppet::Resource::Catalog.indirection.cache_class = :queue
          else
            raise "Cannot disable asynchronous storeconfigs in a running process"
          end
@@@ -588,28 -578,14 +588,28 @@@
        end
      },
      :report_server => ["$server",
 -      "The server to which to send transaction reports."
 +      "The server to send transaction reports to."
      ],
      :report_port => ["$masterport",
        "The port to communicate with the report_server."
      ],
 -    :report => [false,
 +    :inventory_server => ["$server",
 +      "The server to send facts to."
 +    ],
 +    :inventory_port => ["$masterport",
 +      "The port to communicate with the inventory_server."
 +    ],
 +    :report => [true,
        "Whether to send reports after every transaction."
      ],
 +    :lastrunfile =>  { :default => "$statedir/last_run_summary.yaml",
 +      :mode => 0660,
 +      :desc => "Where puppet agent stores the last run report summary in yaml format."
 +    },
 +    :lastrunreport =>  { :default => "$statedir/last_run_report.yaml",
 +      :mode => 0660,
 +      :desc => "Where puppet agent stores the last run report in yaml format."
 +    },
      :graph => [false, "Whether to create dot graph files for the different
        configuration graphs.  These dot files can be interpreted by tools
        like OmniGraffle or dot (which is part of ImageMagick)."],
@@@ -622,6 -598,11 +622,11 @@@
        compression, but if it supports it, this setting might reduce performance on high-speed LANs."]
    )
  
+   setdefaults(:inspect,
+       :archive_files => [false, "During an inspect run, whether to archive files whose contents are audited to a file bucket."],
+       :archive_file_server => ["$server", "During an inspect run, the file bucket server to archive files to if archive_files is set."]
+   )
+ 
    # Plugin information.
  
      setdefaults(
@@@ -794,9 -775,9 +799,9 @@@
          if value
            require 'puppet/rails'
            raise "StoreConfigs not supported without ActiveRecord 2.1 or higher" unless Puppet.features.rails?
 -          Puppet::Resource::Catalog.cache_class = :active_record unless Puppet.settings[:async_storeconfigs]
 -          Puppet::Node::Facts.cache_class = :active_record
 -          Puppet::Node.cache_class = :active_record
 +          Puppet::Resource::Catalog.indirection.cache_class = :active_record unless Puppet.settings[:async_storeconfigs]
 +          Puppet::Node::Facts.indirection.cache_class = :active_record
 +          Puppet::Node.indirection.cache_class = :active_record
          end
        end
      }
diff --combined lib/puppet/file_bucket/dipper.rb
index 367d6bf,f4bef28..be487d8
--- a/lib/puppet/file_bucket/dipper.rb
+++ b/lib/puppet/file_bucket/dipper.rb
@@@ -33,10 -33,15 +33,15 @@@ class Puppet::FileBucket::Dippe
      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, :path => absolutize_path(file) )
+       file_bucket_file = Puppet::FileBucket::File.new(contents, :bucket_path => @local_path)
        dest_path = "#{@rest_path}#{file_bucket_file.name}"
  
-       Puppet::FileBucket::File.indirection.save(file_bucket_file, 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)
++      unless Puppet::FileBucket::File.indirection.head("#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}")
++        Puppet::FileBucket::File.indirection.save(file_bucket_file, dest_path)
+       end
+ 
        return file_bucket_file.checksum_data
      rescue => detail
        puts detail.backtrace if Puppet[:trace]
@@@ -47,7 -52,7 +52,7 @@@
    # 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)
 +    file_bucket_file = Puppet::FileBucket::File.indirection.find(source_path, :bucket_path => @local_path)
  
      raise Puppet::Error, "File not found" unless file_bucket_file
      file_bucket_file.to_s
diff --combined lib/puppet/indirector/indirection.rb
index eb0aa8a,ec147ec..d958a82
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@@ -180,18 -180,13 +180,13 @@@ class Puppet::Indirector::Indirectio
      request = request(:find, key, *args)
      terminus = prepare(request)
  
-     begin
-       if result = find_in_cache(request)
-         return result
-       end
-     rescue => detail
-       puts detail.backtrace if Puppet[:trace]
-       Puppet.err "Cached #{self.name} for #{request.key} failed: #{detail}"
+     if result = find_in_cache(request)
+       return result
      end
  
      # Otherwise, return the result from the terminus, caching if appropriate.
      if ! request.ignore_terminus? and result = terminus.find(request)
-       result.expiration ||= self.expiration
+       result.expiration ||= self.expiration if result.respond_to?(:expiration)
        if cache? and request.use_cache?
          Puppet.info "Caching #{self.name} for #{request.key}"
          cache.save request(:save, result, *args)
@@@ -203,6 -198,17 +198,17 @@@
      nil
    end
  
+   # Search for an instance in the appropriate terminus, and return a
+   # boolean indicating whether the instance was found.
+   def head(key, *args)
+     request = request(:head, key, *args)
+     terminus = prepare(request)
+ 
+     # Look in the cache first, then in the terminus.  Force the result
+     # to be a boolean.
+     !!(find_in_cache(request) || terminus.head(request))
+   end
+ 
    def find_in_cache(request)
      # See if our instance is in the cache and up to date.
      return nil unless cache? and ! request.ignore_cache? and cached = cache.find(request)
@@@ -213,6 -219,10 +219,10 @@@
  
      Puppet.debug "Using cached #{self.name} for #{request.key}"
      cached
+   rescue => detail
+     puts detail.backtrace if Puppet[:trace]
+     Puppet.err "Cached #{self.name} for #{request.key} failed: #{detail}"
+     nil
    end
  
    # Remove something via the terminus.
@@@ -238,7 -248,6 +248,7 @@@
      if result = terminus.search(request)
        raise Puppet::DevError, "Search results from terminus #{terminus.name} are not an array" unless result.is_a?(Array)
        result.each do |instance|
 +        next unless instance.respond_to? :expiration
          instance.expiration ||= self.expiration
        end
        return result
@@@ -247,7 -256,7 +257,7 @@@
  
    # Save the instance in the appropriate terminus.  This method is
    # normally an instance method on the indirected class.
 -  def save(key, instance = nil)
 +  def save(instance, key = nil)
      request = request(:save, key, instance)
      terminus = prepare(request)
  
diff --combined lib/puppet/network/http/api/v1.rb
index abbb2df,8aa1f0e..4b7c15a
--- a/lib/puppet/network/http/api/v1.rb
+++ b/lib/puppet/network/http/api/v1.rb
@@@ -13,6 -13,9 +13,9 @@@ module Puppet::Network::HTTP::API::V
      },
      "DELETE" => {
        :singular => :destroy
+     },
+     "HEAD" => {
+       :singular => :head
      }
    }
  
@@@ -30,7 -33,7 +33,7 @@@
  
      key = URI.unescape(key)
  
 -    Puppet::Indirector::Request.new(indirection, method, key, params)
 +    [indirection, method, key, params]
    end
  
    def indirection2uri(request)
@@@ -57,8 -60,9 +60,8 @@@
      # fix to not need this, and our goal is to move away from the complication
      # that leads to the fix being too long.
      return :singular if indirection == "facts"
 -
 -    # "status" really is singular
      return :singular if indirection == "status"
 +    return :plural if indirection == "inventory"
  
      result = (indirection =~ /s$/) ? :plural : :singular
  
diff --combined lib/puppet/network/http/handler.rb
index 916f02c,9e9356b..2b9e81b
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@@ -61,11 -61,11 +61,11 @@@ module Puppet::Network::HTTP::Handle
  
    # handle an HTTP request
    def process(request, response)
 -    indirection_request = uri2indirection(http_method(request), path(request), params(request))
 +    indirection, method, key, params = uri2indirection(http_method(request), path(request), params(request))
  
 -    check_authorization(indirection_request)
 +    check_authorization(indirection, method, key, params)
  
 -    send("do_#{indirection_request.method}", indirection_request, request, response)
 +    send("do_#{method}", indirection, key, params, request, response)
    rescue SystemExit,NoMemoryError
      raise
    rescue Exception => e
@@@ -96,16 -96,11 +96,16 @@@
      set_response(response, exception.to_s, status)
    end
  
 +  def model(indirection_name)
 +    raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym)
 +    indirection.model
 +  end
 +
    # Execute our find.
 -  def do_find(indirection_request, request, response)
 -    unless result = indirection_request.model.find(indirection_request.key, indirection_request.to_hash)
 -      Puppet.info("Could not find #{indirection_request.indirection_name} for '#{indirection_request.key}'")
 -      return do_exception(response, "Could not find #{indirection_request.indirection_name} #{indirection_request.key}", 404)
 +  def do_find(indirection_name, key, params, request, response)
 +    unless result = model(indirection_name).indirection.find(key, params)
 +      Puppet.info("Could not find #{indirection_name} for '#{key}'")
 +      return do_exception(response, "Could not find #{indirection_name} #{key}", 404)
      end
  
      # The encoding of the result must include the format to use,
@@@ -114,39 -109,53 +114,54 @@@
      format = format_to_use(request)
      set_content_type(response, format)
  
-     set_response(response, result.render(format))
+     if result.respond_to?(:render)
+       set_response(response, result.render(format))
+     else
+       set_response(response, result)
+     end
+   end
+ 
+   # Execute our head.
+   def do_head(indirection_request, request, response)
+     unless indirection_request.model.head(indirection_request.key, indirection_request.to_hash)
+       Puppet.info("Could not find #{indirection_request.indirection_name} for '#{indirection_request.key}'")
+       return do_exception(response, "Could not find #{indirection_request.indirection_name} #{indirection_request.key}", 404)
+     end
+ 
+     # No need to set a response because no response is expected from a
+     # HEAD request.  All we need to do is not die.
    end
  
    # Execute our search.
 -  def do_search(indirection_request, request, response)
 -    result = indirection_request.model.search(indirection_request.key, indirection_request.to_hash)
 +  def do_search(indirection_name, key, params, request, response)
 +    model  = self.model(indirection_name)
 +    result = model.indirection.search(key, params)
  
 -    if result.nil? or (result.is_a?(Array) and result.empty?)
 -      return do_exception(response, "Could not find instances in #{indirection_request.indirection_name} with '#{indirection_request.key}'", 404)
 +    if result.nil?
 +      return do_exception(response, "Could not find instances in #{indirection_name} with '#{key}'", 404)
      end
  
      format = format_to_use(request)
      set_content_type(response, format)
  
 -    set_response(response, indirection_request.model.render_multiple(format, result))
 +    set_response(response, model.render_multiple(format, result))
    end
  
    # Execute our destroy.
 -  def do_destroy(indirection_request, request, response)
 -    result = indirection_request.model.destroy(indirection_request.key, indirection_request.to_hash)
 +  def do_destroy(indirection_name, key, params, request, response)
 +    result = model(indirection_name).indirection.destroy(key, params)
  
      return_yaml_response(response, result)
    end
  
    # Execute our save.
 -  def do_save(indirection_request, request, response)
 +  def do_save(indirection_name, key, params, request, response)
      data = body(request).to_s
      raise ArgumentError, "No data to save" if !data or data.empty?
  
      format = request_format(request)
 -    obj = indirection_request.model.convert_from(format, data)
 -    result = save_object(indirection_request, obj)
 +    obj = model(indirection_name).convert_from(format, data)
 +    result = model(indirection_name).indirection.save(obj, key)
      return_yaml_response(response, result)
    end
  
@@@ -168,6 -177,12 +183,6 @@@
      set_response(response, body.to_yaml)
    end
  
 -  # LAK:NOTE This has to be here for testing; it's a stub-point so
 -  # we keep infinite recursion from happening.
 -  def save_object(ind_request, object)
 -    object.save(ind_request.key)
 -  end
 -
    def get?(request)
      http_method(request) == 'GET'
    end
diff --combined lib/puppet/network/rest_authconfig.rb
index 850f921,7a6147a..9e36324
--- a/lib/puppet/network/rest_authconfig.rb
+++ b/lib/puppet/network/rest_authconfig.rb
@@@ -31,21 -31,17 +31,17 @@@ module Puppe
      # check wether this request is allowed in our ACL
      # raise an Puppet::Network::AuthorizedError if the request
      # is denied.
 -    def allowed?(request)
 +    def allowed?(indirection, method, key, params)
        read
  
        # we're splitting the request in part because
        # fail_on_deny could as well be called in the XMLRPC context
        # with a ClientRequest.
  
-       @rights.fail_on_deny(
-         build_uri(indirection, key),
-         :node => params[:node],
-         :ip => params[:ip],
-         :method => method,
-         :environment => params[:environment],
-         :authenticated => params[:authenticated]
-       )
 -      if authorization_failure_exception = @rights.is_request_forbidden_and_why?(request)
++      if authorization_failure_exception = @rights.is_request_forbidden_and_why?(indirection, method, key, params)
+         Puppet.warning("Denying access: #{authorization_failure_exception}")
+         raise authorization_failure_exception
+       end
      end
  
      def initialize(file = nil, parsenow = true)
@@@ -88,9 -84,5 +84,5 @@@
        end
        @rights.restrict_authenticated(acl[:acl], acl[:authenticated]) unless acl[:authenticated].nil?
      end
- 
-     def build_uri(indirection_name, key)
-       "/#{indirection_name}/#{key}"
-     end
    end
  end
diff --combined lib/puppet/network/rights.rb
index e3cd317,00ee04f..56af539
--- a/lib/puppet/network/rights.rb
+++ b/lib/puppet/network/rights.rb
@@@ -26,19 -26,34 +26,29 @@@ class Right
  
    # Check that name is allowed or not
    def allowed?(name, *args)
-     begin
-       fail_on_deny(name, :node => args[0], :ip => args[1])
-     rescue AuthorizationError
-       return false
-     rescue ArgumentError
-       # the namespace contract says we should raise this error
-       # if we didn't find the right acl
-       raise
+     !is_forbidden_and_why?(name, :node => args[0], :ip => args[1])
+   end
+ 
 -  def is_request_forbidden_and_why?(request)
 -    methods_to_check = if request.method == :head
++  def is_request_forbidden_and_why?(indirection, method, key, params)
++    methods_to_check = if method == :head
+                          # :head is ok if either :find or :save is ok.
+                          [:find, :save]
+                        else
 -                         [request.method]
++                         [method]
+                        end
+     authorization_failure_exceptions = methods_to_check.map do |method|
 -      is_forbidden_and_why?("/#{request.indirection_name}/#{request.key}",
 -                            :node => request.node,
 -                            :ip => request.ip,
 -                            :method => method,
 -                            :environment => request.environment,
 -                            :authenticated => request.authenticated)
++      is_forbidden_and_why?("/#{indirection}/#{key}", params.merge({:method => method}))
+     end
+     if authorization_failure_exceptions.include? nil
+       # One of the methods we checked is ok, therefore this request is ok.
+       nil
+     else
+       # Just need to return any of the failure exceptions.
+       authorization_failure_exceptions.first
      end
-     true
    end
  
-   def fail_on_deny(name, args = {})
+   def is_forbidden_and_why?(name, args = {})
      res = :nomatch
      right = @rights.find do |acl|
        found = false
@@@ -49,7 -64,7 +59,7 @@@
          args[:match] = match
          if (res = acl.allowed?(args[:node], args[:ip], args)) != :dunno
            # return early if we're allowed
-           return if res
+           return nil if res
            # we matched, select this acl
            found = true
          end
@@@ -70,13 -85,12 +80,12 @@@
          error.file = right.file
          error.line = right.line
        end
      else
        # there were no rights allowing/denying name
        # if name is not a path, let's throw
-       error = ArgumentError.new "Unknown namespace right '#{name}'"
+       raise ArgumentError.new "Unknown namespace right '#{name}'"
      end
-     raise error
+     error
    end
  
    def initialize
diff --combined spec/unit/file_bucket/dipper_spec.rb
index 0c3d00a,db40c62..189a3d8
--- a/spec/unit/file_bucket/dipper_spec.rb
+++ b/spec/unit/file_bucket/dipper_spec.rb
@@@ -1,119 -1,114 +1,114 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
+ require 'pathname'
+ 
  require 'puppet/file_bucket/dipper'
+ require 'puppet/indirector/file_bucket_file/rest'
+ 
  describe Puppet::FileBucket::Dipper do
-   before do
-     ['/my/file'].each do |x|
-       Puppet::FileBucket::Dipper.any_instance.stubs(:absolutize_path).with(x).returns(x)
-     end
+   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 backing up to the server" do
-     File.stubs(:exist?).returns true
-     File.stubs(:read).returns "content"
+   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
++    Puppet::FileBucket::File.indirection.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")
  
-     Puppet::FileBucket::File.any_instance.stubs(:validate_checksum!)
-     file = Puppet::FileBucket::File.new(nil)
+     file = make_tmp_file('contents')
 -    Puppet::FileBucket::File.expects(:head).returns false
 -    Puppet::FileBucket::File.any_instance.expects(:save).raises ArgumentError
++    Puppet::FileBucket::File.indirection.expects(:head).returns false
 +    Puppet::FileBucket::File.indirection.expects(:save).raises ArgumentError
  
-     lambda { @dipper.backup("/my/file") }.should raise_error(Puppet::Error)
+     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"
-     )
+     Puppet[:bucketdir] = "/non/existent/directory"
+     file_bucket = tmpdir("bucket")
  
-     File.stubs(:exist?).returns true
-     File.stubs(:read).with("/my/file").returns "my contents"
+     @dipper = Puppet::FileBucket::Dipper.new(:Path => file_bucket)
  
-     Puppet::FileBucket::File.any_instance.stubs(:validate_checksum!)
-     bucketfile = Puppet::FileBucket::File.new(nil, :checksum_type => "md5", :checksum => "{md5}DIGEST123")
-     Puppet::FileBucket::File.indirection.expects(:save).with(bucketfile, 'md5/DIGEST123')
+     file = make_tmp_file('my contents')
+     checksum = "2975f560750e71c478b8e3b39a956adb"
+     Digest::MD5.hexdigest('my contents').should == checksum
  
+     @dipper.backup(file).should == checksum
+     File.exists?("#{file_bucket}/2/9/7/5/f/5/6/0/2975f560750e71c478b8e3b39a956adb/contents").should == true
+   end
+ 
+   it "should not backup a file that is already in the bucket" do
+     @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket")
  
-           Puppet::FileBucket::File.stubs(:new).with(
-                 
-       "my contents",
-       :bucket_path => '/my/bucket',
-         
-       :path => '/my/file'
-     ).returns(bucketfile)
+     file = make_tmp_file('my contents')
+     checksum = Digest::MD5.hexdigest('my contents')
  
-     @dipper.backup("/my/file").should == "DIGEST123"
 -    Puppet::FileBucket::File.expects(:head).returns true
 -    Puppet::FileBucket::File.any_instance.expects(:save).never
++    Puppet::FileBucket::File.indirection.expects(:head).returns true
++    Puppet::FileBucket::File.indirection.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"
-     )
+     @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket")
+ 
+     checksum = Digest::MD5.hexdigest('my contents')
  
-     File.stubs(:exist?).returns true
-     File.stubs(:read).with("/my/file").returns "my contents"
+     request = nil
  
-     bucketfile = stub "bucketfile"
-     bucketfile.stubs(:to_s).returns "Content"
+     Puppet::FileBucketFile::File.any_instance.expects(:find).with{ |r| request = r }.once.returns(Puppet::FileBucket::File.new('my contents'))
  
-     Puppet::FileBucket::File.indirection.expects(:find).with{|x,opts|
-       x == 'md5/DIGEST123'
-     }.returns(bucketfile)
+     @dipper.getfile(checksum).should == 'my contents'
  
-     @dipper.getfile("DIGEST123").should == "Content"
+     request.key.should == "md5/#{checksum}"
    end
  
    it "should backup files to a remote server" do
+     @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337")
  
-           @dipper = Puppet::FileBucket::Dipper.new(
-                 
-       :Server => "puppetmaster",
-         
-       :Port   => "31337"
-     )
+     file = make_tmp_file('my contents')
+     checksum = Digest::MD5.hexdigest('my contents')
  
-     File.stubs(:exist?).returns true
-     File.stubs(:read).with("/my/file").returns "my contents"
+     real_path = Pathname.new(file).realpath
  
-     Puppet::FileBucket::File.any_instance.stubs(:validate_checksum!)
-     bucketfile = Puppet::FileBucket::File.new(nil, :checksum_type => "md5", :checksum => "{md5}DIGEST123")
-     Puppet::FileBucket::File.indirection.expects(:save).with(bucketfile, 'https://puppetmaster:31337/production/file_bucket_file/md5/DIGEST123')
+     request1 = nil
+     request2 = nil
  
+     Puppet::FileBucketFile::Rest.any_instance.expects(:head).with { |r| request1 = r }.once.returns(nil)
+     Puppet::FileBucketFile::Rest.any_instance.expects(:save).with { |r| request2 = r }.once
  
-           Puppet::FileBucket::File.stubs(:new).with(
-                 
-       "my contents",
-       :bucket_path => nil,
-         
-       :path => '/my/file'
-     ).returns(bucketfile)
- 
-     @dipper.backup("/my/file").should == "DIGEST123"
+     @dipper.backup(file).should == checksum
+     [request1, request2].each do |r|
+       r.server.should == 'puppetmaster'
+       r.port.should == 31337
+       r.key.should == "md5/#{checksum}"
+     end
    end
  
    it "should retrieve files from a remote server" do
+     @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337")
  
-           @dipper = Puppet::FileBucket::Dipper.new(
-                 
-       :Server => "puppetmaster",
-         
-       :Port   => "31337"
-     )
+     checksum = Digest::MD5.hexdigest('my contents')
  
-     File.stubs(:exist?).returns true
-     File.stubs(:read).with("/my/file").returns "my contents"
+     request = nil
  
-     bucketfile = stub "bucketfile"
-     bucketfile.stubs(:to_s).returns "Content"
+     Puppet::FileBucketFile::Rest.any_instance.expects(:find).with { |r| request = r }.returns(Puppet::FileBucket::File.new('my contents'))
  
-     Puppet::FileBucket::File.indirection.expects(:find).with{|x,opts|
-       x == 'https://puppetmaster:31337/production/file_bucket_file/md5/DIGEST123'
-     }.returns(bucketfile)
+     @dipper.getfile(checksum).should == "my contents"
  
-     @dipper.getfile("DIGEST123").should == "Content"
+     request.server.should == 'puppetmaster'
+     request.port.should == 31337
+     request.key.should == "md5/#{checksum}"
    end
- 
- 
  end
diff --combined spec/unit/file_bucket/file_spec.rb
index b9849b0,82063c2..6873264
--- a/spec/unit/file_bucket/file_spec.rb
+++ b/spec/unit/file_bucket/file_spec.rb
@@@ -7,13 -7,16 +7,16 @@@ require 'digest/md5
  require 'digest/sha1'
  
  describe Puppet::FileBucket::File do
+   include PuppetSpec::Files
+ 
    before do
      # this is the default from spec_helper, but it keeps getting reset at odd times
-     Puppet[:bucketdir] = "/dev/null/bucket"
+     @bucketdir = tmpdir('bucket')
+     Puppet[:bucketdir] = @bucketdir
  
      @digest = "4a8ec4fa5f01b4ab1a0ab8cbccb709f0"
      @checksum = "{md5}4a8ec4fa5f01b4ab1a0ab8cbccb709f0"
-     @dir = '/dev/null/bucket/4/a/8/e/c/4/f/a/4a8ec4fa5f01b4ab1a0ab8cbccb709f0'
+     @dir = File.join(@bucketdir, '4/a/8/e/c/4/f/a/4a8ec4fa5f01b4ab1a0ab8cbccb709f0')
  
      @contents = "file contents"
    end
@@@ -22,17 -25,6 +25,6 @@@
      Puppet::FileBucket::File.new(@contents).to_s.should == @contents
    end
  
-   it "should calculate the checksum type from the passed in checksum" do
-     Puppet::FileBucket::File.new(@contents, :checksum => @checksum).checksum_type.should == "md5"
-   end
- 
-   it "should allow contents to be specified in a block" do
-     bucket = Puppet::FileBucket::File.new(nil) do |fb|
-       fb.contents = "content"
-     end
-     bucket.contents.should == "content"
-   end
- 
    it "should raise an error if changing content" do
      x = Puppet::FileBucket::File.new("first")
      proc { x.contents = "new" }.should raise_error
@@@ -42,14 -34,6 +34,6 @@@
      proc { Puppet::FileBucket::File.new(5) }.should raise_error(ArgumentError)
    end
  
-   it "should raise an error if setting contents to a non-string" do
-     proc do
-       Puppet::FileBucket::File.new(nil) do |x|
-         x.contents = 5
-       end
-     end.should raise_error(ArgumentError)
-   end
- 
    it "should set the contents appropriately" do
      Puppet::FileBucket::File.new(@contents).contents.should == @contents
    end
@@@ -62,40 -46,21 +46,13 @@@
      Puppet::FileBucket::File.new(@contents).checksum.should == @checksum
    end
  
-   it "should remove the old checksum value if the algorithm is changed" do
-     sum = Puppet::FileBucket::File.new(@contents)
-     sum.checksum.should_not be_nil
- 
-     newsum = Digest::SHA1.hexdigest(@contents).to_s
-     sum.checksum_type = :sha1
-     sum.checksum.should == "{sha1}#{newsum}"
-   end
- 
-   it "should support specifying the checksum_type during initialization" do
-     sum = Puppet::FileBucket::File.new(@contents, :checksum_type => :sha1)
-     sum.checksum_type.should == :sha1
-   end
- 
-   it "should fail when an unsupported checksum_type is used" do
-     proc { Puppet::FileBucket::File.new(@contents, :checksum_type => :nope) }.should raise_error(ArgumentError)
-   end
- 
-   it "should fail if given an checksum at initialization that does not match the contents" do
-     proc { Puppet::FileBucket::File.new(@contents, :checksum => "{md5}00000000000000000000000000000000") }.should raise_error(RuntimeError)
-   end
- 
-   it "should fail if assigned a checksum that does not match the contents" do
-     bucket = Puppet::FileBucket::File.new(@contents)
-     proc { bucket.checksum = "{md5}00000000000000000000000000000000" }.should raise_error(RuntimeError)
-   end
- 
    describe "when using back-ends" do
      it "should redirect using Puppet::Indirector" do
        Puppet::Indirector::Indirection.instance(:file_bucket_file).model.should equal(Puppet::FileBucket::File)
      end
  
      it "should have a :save instance method" do
 -      Puppet::FileBucket::File.new("mysum").should respond_to(:save)
 -    end
 -
 -    it "should respond to :find" do
 -      Puppet::FileBucket::File.should respond_to(:find)
 -    end
 -
 -    it "should respond to :destroy" do
 -      Puppet::FileBucket::File.should respond_to(:destroy)
 +      Puppet::FileBucket::File.indirection.should respond_to(:save)
      end
    end
  
@@@ -108,7 -73,7 +65,7 @@@
        mockfile.expects(:print).with(@contents)
        ::File.expects(:open).with("#{@dir}/contents", ::File::WRONLY|::File::CREAT, 0440).yields(mockfile)
  
 -      Puppet::FileBucket::File.new(@contents).save
 +      Puppet::FileBucket::File.indirection.save(Puppet::FileBucket::File.new(@contents))
      end
  
      it "should make any directories necessary for storage" do
@@@ -119,28 -84,8 +76,8 @@@
        ::File.expects(:open).with("#{@dir}/contents", ::File::WRONLY|::File::CREAT, 0440)
        ::File.expects(:exist?).with("#{@dir}/contents").returns false
  
 -      Puppet::FileBucket::File.new(@contents).save
 +      Puppet::FileBucket::File.indirection.save(Puppet::FileBucket::File.new(@contents))
      end
- 
-     it "should append the path to the paths file" do
-       remote_path = '/path/on/the/remote/box'
- 
-       ::File.expects(:directory?).with(@dir).returns(true)
-       ::File.expects(:open).with("#{@dir}/contents", ::File::WRONLY|::File::CREAT, 0440)
-       ::File.expects(:exist?).with("#{@dir}/contents").returns false
- 
-       mockfile = mock "file"
-       mockfile.expects(:puts).with('/path/on/the/remote/box')
-       ::File.expects(:exist?).with("#{@dir}/paths").returns false
-       ::File.expects(:open).with("#{@dir}/paths", ::File::WRONLY|::File::CREAT|::File::APPEND).yields mockfile
-       Puppet::FileBucket::File.indirection.save(Puppet::FileBucket::File.new(@contents, :path => remote_path))
- 
-     end
-   end
- 
-   it "should accept a path" do
-     remote_path = '/path/on/the/remote/box'
-     Puppet::FileBucket::File.new(@contents, :path => remote_path).path.should == remote_path
    end
  
    it "should return a url-ish name" do
@@@ -152,18 -97,6 +89,6 @@@
      lambda { bucket.name = "sha1/4a8ec4fa5f01b4ab1a0ab8cbccb709f0/new/path" }.should raise_error
    end
  
-   it "should accept a url-ish name" do
-     bucket = Puppet::FileBucket::File.new(@contents)
-     lambda { bucket.name = "sha1/034fa2ed8e211e4d20f20e792d777f4a30af1a93/new/path" }.should_not raise_error
-     bucket.checksum_type.should == "sha1"
-     bucket.checksum_data.should == '034fa2ed8e211e4d20f20e792d777f4a30af1a93'
-     bucket.path.should == "new/path"
-   end
- 
-   it "should return a url-ish name with a path" do
-     Puppet::FileBucket::File.new(@contents, :path => 'my/path').name.should == "md5/4a8ec4fa5f01b4ab1a0ab8cbccb709f0/my/path"
-   end
- 
    it "should convert the contents to PSON" do
      Puppet::FileBucket::File.new(@contents).to_pson.should == '{"contents":"file contents"}'
    end
@@@ -179,41 -112,36 +104,36 @@@
      ::File.expects(:open).with("#{@dir}/contents",  ::File::WRONLY|::File::CREAT, 0440)
  
      bucketfile = Puppet::FileBucket::File.new(@contents)
 -    bucketfile.save
 +    Puppet::FileBucket::File.indirection.save(bucketfile)
  
    end
  
+   def make_bucketed_file
+     FileUtils.mkdir_p(@dir)
+     File.open("#{@dir}/contents", 'w') { |f| f.write @contents }
+   end
+ 
    describe "using the indirector's find method" do
      it "should return nil if a file doesn't exist" do
-       ::File.expects(:exist?).with("#{@dir}/contents").returns false
- 
-       bucketfile = Puppet::FileBucket::File.indirection.find("{md5}#{@digest}")
 -      bucketfile = Puppet::FileBucket::File.find("md5/#{@digest}")
++      bucketfile = Puppet::FileBucket::File.indirection.find("md5/#{@digest}")
        bucketfile.should == nil
      end
  
      it "should find a filebucket if the file exists" do
-       ::File.expects(:exist?).with("#{@dir}/contents").returns true
-       ::File.expects(:exist?).with("#{@dir}/paths").returns false
-       ::File.expects(:read).with("#{@dir}/contents").returns @contents
- 
-       bucketfile = Puppet::FileBucket::File.indirection.find("{md5}#{@digest}")
+       make_bucketed_file
 -      bucketfile = Puppet::FileBucket::File.find("md5/#{@digest}")
++      bucketfile = Puppet::FileBucket::File.indirection.find("md5/#{@digest}")
        bucketfile.should_not == nil
      end
  
      describe "using RESTish digest notation" do
        it "should return nil if a file doesn't exist" do
-         ::File.expects(:exist?).with("#{@dir}/contents").returns false
- 
 -        bucketfile = Puppet::FileBucket::File.find("md5/#{@digest}")
 +        bucketfile = Puppet::FileBucket::File.indirection.find("md5/#{@digest}")
          bucketfile.should == nil
        end
  
        it "should find a filebucket if the file exists" do
-         ::File.expects(:exist?).with("#{@dir}/contents").returns true
-         ::File.expects(:exist?).with("#{@dir}/paths").returns false
-         ::File.expects(:read).with("#{@dir}/contents").returns @contents
- 
+         make_bucketed_file
 -        bucketfile = Puppet::FileBucket::File.find("md5/#{@digest}")
 +        bucketfile = Puppet::FileBucket::File.indirection.find("md5/#{@digest}")
          bucketfile.should_not == nil
        end
  
diff --combined spec/unit/indirector/file_bucket_file/file_spec.rb
index aa3ade6,9187f4d..edf537a
--- a/spec/unit/indirector/file_bucket_file/file_spec.rb
+++ b/spec/unit/indirector/file_bucket_file/file_spec.rb
@@@ -5,6 -5,8 +5,8 @@@ require ::File.dirname(__FILE__) + '/..
  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
@@@ -13,194 -15,143 +15,143 @@@
      Puppet::FileBucketFile::File.doc.should be_instance_of(String)
    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
- 
+   describe "non-stubbing tests" do
+     include PuppetSpec::Files
  
-   describe "the find_by_checksum method" 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"
-     end
- 
-     it "should return nil if a file doesn't exist" do
-       ::File.expects(:exist?).with("#{@dir}/contents").returns false
- 
-       bucketfile = Puppet::FileBucketFile::File.new.send(:find_by_checksum, "{md5}#{@digest}", {})
-       bucketfile.should == nil
-     end
- 
-     it "should find a filebucket if the file exists" do
-       ::File.expects(:exist?).with("#{@dir}/contents").returns true
-       ::File.expects(:exist?).with("#{@dir}/paths").returns false
-       ::File.expects(:read).with("#{@dir}/contents").returns @contents
- 
-       bucketfile = Puppet::FileBucketFile::File.new.send(:find_by_checksum, "{md5}#{@digest}", {})
-       bucketfile.should_not == nil
-     end
- 
-     it "should load the paths" do
-       paths = ["path1", "path2"]
-       ::File.expects(:exist?).with("#{@dir}/contents").returns true
-       ::File.expects(:exist?).with("#{@dir}/paths").returns true
-       ::File.expects(:read).with("#{@dir}/contents").returns @contents
- 
-       mockfile = mock "file"
-       mockfile.expects(:readlines).returns( paths )
-       ::File.expects(:open).with("#{@dir}/paths").yields mockfile
- 
-       Puppet::FileBucketFile::File.new.send(:find_by_checksum, "{md5}#{@digest}", {}).paths.should == paths
-     end
- 
-   end
- 
-   describe "when retrieving files" do
-     before :each do
-       Puppet.settings.stubs(:use)
-       @store = Puppet::FileBucketFile::File.new
- 
-       @digest = "70924d6fa4b2d745185fa4660703a5c0"
-       @sum = stub 'sum', :name => @digest
- 
-       @dir = "/what/ever"
- 
-       Puppet.stubs(:[]).with(:bucketdir).returns(@dir)
- 
-       @contents_path = '/what/ever/7/0/9/2/4/d/6/f/70924d6fa4b2d745185fa4660703a5c0/contents'
-       @paths_path    = '/what/ever/7/0/9/2/4/d/6/f/70924d6fa4b2d745185fa4660703a5c0/paths'
- 
-       @request = stub 'request', :key => "md5/#{@digest}/remote/path", :options => {}
-     end
- 
-     it "should call find_by_checksum" do
-       @store.expects(:find_by_checksum).with{|x,opts| x == "{md5}#{@digest}"}.returns(false)
-       @store.find(@request)
+       Puppet[:bucketdir] = tmpdir('bucketdir')
      end
  
-     it "should look for the calculated path" do
-       ::File.expects(:exist?).with(@contents_path).returns(false)
-       @store.find(@request)
-     end
- 
-     it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do
-       content = "my content"
-       bucketfile = stub 'bucketfile'
-       bucketfile.stubs(:bucket_path)
-       bucketfile.stubs(:bucket_path=)
-       bucketfile.stubs(:checksum_data).returns(@digest)
-       bucketfile.stubs(:checksum).returns(@checksum)
- 
-       bucketfile.expects(:contents=).with(content)
-       Puppet::FileBucket::File.expects(:new).with(nil, {:checksum => "{md5}#{@digest}"}).yields(bucketfile).returns(bucketfile)
+     describe "when diffing files" do
+       def save_bucket_file(contents)
+         bucket_file = Puppet::FileBucket::File.new(contents)
 -        bucket_file.save
++        Puppet::FileBucket::File.indirection.save(bucket_file)
+         bucket_file.checksum_data
+       end
  
-       ::File.expects(:exist?).with(@contents_path).returns(true)
-       ::File.expects(:exist?).with(@paths_path).returns(false)
-       ::File.expects(:read).with(@contents_path).returns(content)
+       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 == ''
++        Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => checksum).should == ''
+       end
  
-       @store.find(@request).should equal(bucketfile)
-     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 = Puppet::FileBucket::File.indirection.find("md5/#{checksum1}", :diff_with => checksum2)
+         diff.should == <<HERE
+ 2c2
+ < bar
+ ---
+ > biz
+ HERE
+       end
  
-     it "should return nil if no file is found" do
-       ::File.expects(:exist?).with(@contents_path).returns(false)
-       @store.find(@request).should be_nil
-     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}"
++        lambda { Puppet::FileBucket::File.indirection.find("md5/#{checksum}", :diff_with => bogus_checksum) }.should raise_error "could not find diff_with #{bogus_checksum}"
+       end
  
-     it "should fail intelligently if a found file cannot be read" do
-       ::File.expects(:exist?).with(@contents_path).returns(true)
-       ::File.expects(:read).with(@contents_path).raises(RuntimeError)
-       proc { @store.find(@request) }.should raise_error(Puppet::Error)
+       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
++        Puppet::FileBucket::File.indirection.find("md5/#{bogus_checksum}", :diff_with => checksum).should == nil
+       end
      end
- 
    end
  
-   describe "when determining file paths" do
-     before do
-       Puppet[:bucketdir] = '/dev/null/bucketdir'
-       @digest = 'DEADBEEFC0FFEE'
-       @bucket = stub_everything "bucket"
-       @bucket.expects(:checksum_data).returns(@digest)
-     end
- 
-     it "should use the value of the :bucketdir setting as the root directory" do
-       path = Puppet::FileBucketFile::File.new.send(:contents_path_for, @bucket)
-       path.should =~ %r{^/dev/null/bucketdir}
-     end
- 
-     it "should choose a path 8 directories deep with each directory name being the respective character in the filebucket" do
-       path = Puppet::FileBucketFile::File.new.send(:contents_path_for, @bucket)
-       dirs = @digest[0..7].split("").join(File::SEPARATOR)
-       path.should be_include(dirs)
-     end
- 
-     it "should use the full filebucket as the final directory name" do
-       path = Puppet::FileBucketFile::File.new.send(:contents_path_for, @bucket)
-       ::File.basename(::File.dirname(path)).should == @digest
-     end
- 
-     it "should use 'contents' as the actual file name" do
-       path = Puppet::FileBucketFile::File.new.send(:contents_path_for, @bucket)
-       ::File.basename(path).should == "contents"
-     end
- 
-     it "should use the bucketdir, the 8 sum character directories, the full filebucket, and 'contents' as the full file name" do
-       path = Puppet::FileBucketFile::File.new.send(:contents_path_for, @bucket)
-       path.should == ['/dev/null/bucketdir', @digest[0..7].split(""), @digest, "contents"].flatten.join(::File::SEPARATOR)
+   describe "when initializing" do
+     it "should use the filebucket settings section" do
+       Puppet.settings.expects(:use).with(:filebucket)
+       Puppet::FileBucketFile::File.new
      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
+   [true, false].each do |override_bucket_path|
+     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
        end
      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
@@@ -231,60 -182,4 +182,4 @@@
      end
  
    end
- 
- 
-   describe "when writing to the paths file" do
-     before do
-       Puppet[:bucketdir] = '/dev/null/bucketdir'
-       @digest = '70924d6fa4b2d745185fa4660703a5c0'
-       @bucket = stub_everything "bucket"
- 
-       @paths_path    = '/dev/null/bucketdir/7/0/9/2/4/d/6/f/70924d6fa4b2d745185fa4660703a5c0/paths'
- 
-       @paths = []
-       @bucket.stubs(:paths).returns(@paths)
-       @bucket.stubs(:checksum_data).returns(@digest)
-     end
- 
-     it "should create a file if it doesn't exist" do
-       @bucket.expects(:path).returns('path/to/save').at_least_once
-       File.expects(:exist?).with(@paths_path).returns(false)
-       file = stub "file"
-       file.expects(:puts).with('path/to/save')
-       File.expects(:open).with(@paths_path, ::File::WRONLY|::File::CREAT|::File::APPEND).yields(file)
- 
-       Puppet::FileBucketFile::File.new.send(:save_path_to_paths_file, @bucket)
-     end
- 
-     it "should append to a file if it exists" do
-       @bucket.expects(:path).returns('path/to/save').at_least_once
-       File.expects(:exist?).with(@paths_path).returns(true)
-       old_file = stub "file"
-       old_file.stubs(:readlines).returns []
-       File.expects(:open).with(@paths_path).yields(old_file)
- 
-       file = stub "file"
-       file.expects(:puts).with('path/to/save')
-       File.expects(:open).with(@paths_path, ::File::WRONLY|::File::CREAT|::File::APPEND).yields(file)
- 
-       Puppet::FileBucketFile::File.new.send(:save_path_to_paths_file, @bucket)
-     end
- 
-     it "should not alter a file if it already contains the path" do
-       @bucket.expects(:path).returns('path/to/save').at_least_once
-       File.expects(:exist?).with(@paths_path).returns(true)
-       old_file = stub "file"
-       old_file.stubs(:readlines).returns ["path/to/save\n"]
-       File.expects(:open).with(@paths_path).yields(old_file)
- 
-       Puppet::FileBucketFile::File.new.send(:save_path_to_paths_file, @bucket)
-     end
- 
-     it "should do nothing if there is no path" do
-       @bucket.expects(:path).returns(nil).at_least_once
- 
-       Puppet::FileBucketFile::File.new.send(:save_path_to_paths_file, @bucket)
-     end
-   end
- 
  end
diff --combined spec/unit/indirector/file_server_spec.rb
index f3d325b,eafcf2b..a81d504
--- a/spec/unit/indirector/file_server_spec.rb
+++ b/spec/unit/indirector/file_server_spec.rb
@@@ -3,7 -3,7 +3,7 @@@
  #  Created by Luke Kanies on 2007-10-19.
  #  Copyright (c) 2007. All rights reserved.
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
  require 'puppet/indirector/file_server'
  require 'puppet/file_serving/configuration'
@@@ -229,6 -229,12 +229,12 @@@ describe Puppet::Indirector::FileServe
    describe "when checking authorization" do
      before do
        @request.method = :find
+ 
+       @mount = stub 'mount'
+       @configuration.stubs(:split_path).with(@request).returns([@mount, "rel/path"])
+       @request.stubs(:node).returns("mynode")
+       @request.stubs(:ip).returns("myip")
+       @mount.stubs(:allowed?).with("mynode", "myip").returns "something"
      end
  
      it "should return false when destroying" do
@@@ -254,13 -260,6 +260,6 @@@
      end
  
      it "should return the results of asking the mount whether the node and IP are authorized" do
-       @mount = stub 'mount'
-       @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"])
- 
-       @request.stubs(:node).returns("mynode")
-       @request.stubs(:ip).returns("myip")
-       @mount.expects(:allowed?).with("mynode", "myip").returns "something"
- 
        @file_server.authorized?(@request).should == "something"
      end
    end
diff --combined spec/unit/indirector/indirection_spec.rb
index 3f6e71f,a910cb6..8795ae7
--- a/spec/unit/indirector/indirection_spec.rb
+++ b/spec/unit/indirector/indirection_spec.rb
@@@ -1,6 -1,6 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
  require 'puppet/indirector/indirection'
  
@@@ -383,9 -383,81 +383,78 @@@ describe Puppet::Indirector::Indirectio
        end
      end
  
+     describe "and doing a head operation" do
+       before { @method = :head }
+ 
+       it_should_behave_like "Indirection Delegator"
+       it_should_behave_like "Delegation Authorizer"
+ 
+       it "should return true if the head method returned true" do
+         @terminus.expects(:head).returns(true)
+         @indirection.head("me").should == true
+       end
+ 
+       it "should return false if the head method returned false" do
+         @terminus.expects(:head).returns(false)
+         @indirection.head("me").should == false
+       end
+ 
+       describe "when caching is enabled" do
+         before do
+           @indirection.cache_class = :cache_terminus
+           @cache_class.stubs(:new).returns(@cache)
+ 
+           @instance.stubs(:expired?).returns false
+         end
+ 
+         it "should first look in the cache for an instance" do
+           @terminus.stubs(:find).never
+           @terminus.stubs(:head).never
+           @cache.expects(:find).returns @instance
+ 
+           @indirection.head("/my/key").should == true
+         end
+ 
+         it "should not save to the cache" do
+           @cache.expects(:find).returns nil
+           @cache.expects(:save).never
+           @terminus.expects(:head).returns true
+           @indirection.head("/my/key").should == true
+         end
+ 
+         it "should not fail if the cache fails" do
+           @terminus.stubs(:head).returns true
+ 
+           @cache.expects(:find).raises ArgumentError
+           lambda { @indirection.head("/my/key") }.should_not raise_error
+         end
+ 
+         it "should look in the main terminus if the cache fails" do
+           @terminus.expects(:head).returns true
+           @cache.expects(:find).raises ArgumentError
+           @indirection.head("/my/key").should == true
+         end
+ 
+         it "should send a debug log if it is using the cached object" do
+           Puppet.expects(:debug)
+           @cache.stubs(:find).returns @instance
+ 
+           @indirection.head("/my/key")
+         end
+ 
+         it "should not accept the cached object if it is expired" do
+           @instance.stubs(:expired?).returns true
+ 
+           @cache.stubs(:find).returns @instance
+           @terminus.stubs(:head).returns false
+           @indirection.head("/my/key").should == false
+         end
+       end
+     end
+ 
      describe "and storing a model instance" do
        before { @method = :save }
  
 -      it_should_behave_like "Indirection Delegator"
 -      it_should_behave_like "Delegation Authorizer"
 -
        it "should return the result of the save" do
          @terminus.stubs(:save).returns "foo"
          @indirection.save(@instance).should == "foo"
diff --combined spec/unit/indirector/rest_spec.rb
index 03bb2bb,7bc1cc5..547e68d
--- a/spec/unit/indirector/rest_spec.rb
+++ b/spec/unit/indirector/rest_spec.rb
@@@ -1,6 -1,6 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  require 'puppet/indirector/rest'
  
  shared_examples_for "a REST http call" do
@@@ -282,6 -282,42 +282,42 @@@ describe Puppet::Indirector::REST d
      end
    end
  
+   describe "when doing a head" do
+     before :each do
+       @connection = stub('mock http connection', :head => @response)
+       @searcher.stubs(:network).returns(@connection)
+ 
+       # Use a key with spaces, so we can test escaping
+       @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar")
+     end
+ 
+     it "should call the HEAD http method on a network connection" do
+       @searcher.expects(:network).returns @connection
+       @connection.expects(:head).returns @response
+       @searcher.head(@request)
+     end
+ 
+     it "should return true if there was a successful http response" do
+       @connection.expects(:head).returns @response
+       @response.stubs(:code).returns "200"
+ 
+       @searcher.head(@request).should == true
+     end
+ 
+     it "should return false if there was a successful http response" do
+       @connection.expects(:head).returns @response
+       @response.stubs(:code).returns "404"
+ 
+       @searcher.head(@request).should == false
+     end
+ 
+     it "should use the URI generated by the Handler module" do
+       @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
+       @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response)
+       @searcher.head(@request)
+     end
+   end
+ 
    describe "when doing a search" do
      before :each do
        @connection = stub('mock http connection', :get => @response)
diff --combined spec/unit/network/http/api/v1_spec.rb
index 84b98dd,23a291c..a1cb758
--- a/spec/unit/network/http/api/v1_spec.rb
+++ b/spec/unit/network/http/api/v1_spec.rb
@@@ -32,7 -32,7 +32,7 @@@ describe Puppet::Network::HTTP::API::V
      end
  
      it "should use the first field of the URI as the environment" do
 -      @tester.uri2indirection("GET", "/env/foo/bar", {}).environment.should == Puppet::Node::Environment.new("env")
 +      @tester.uri2indirection("GET", "/env/foo/bar", {})[3][:environment].should == "env"
      end
  
      it "should fail if the environment is not alphanumeric" do
@@@ -40,11 -40,11 +40,11 @@@
      end
  
      it "should use the environment from the URI even if one is specified in the parameters" do
 -      @tester.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"}).environment.should == Puppet::Node::Environment.new("env")
 +      @tester.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"})[3][:environment].should == "env"
      end
  
      it "should use the second field of the URI as the indirection name" do
 -      @tester.uri2indirection("GET", "/env/foo/bar", {}).indirection_name.should == :foo
 +      @tester.uri2indirection("GET", "/env/foo/bar", {})[0].should == "foo"
      end
  
      it "should fail if the indirection name is not alphanumeric" do
@@@ -52,11 -52,11 +52,11 @@@
      end
  
      it "should use the remainder of the URI as the indirection key" do
 -      @tester.uri2indirection("GET", "/env/foo/bar", {}).key.should == "bar"
 +      @tester.uri2indirection("GET", "/env/foo/bar", {})[2].should == "bar"
      end
  
      it "should support the indirection key being a /-separated file path" do
 -      @tester.uri2indirection("GET", "/env/foo/bee/baz/bomb", {}).key.should == "bee/baz/bomb"
 +      @tester.uri2indirection("GET", "/env/foo/bee/baz/bomb", {})[2].should == "bee/baz/bomb"
      end
  
      it "should fail if no indirection key is specified" do
@@@ -65,31 -65,23 +65,35 @@@
      end
  
      it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is singular" do
 -      @tester.uri2indirection("GET", "/env/foo/bar", {}).method.should == :find
 +      @tester.uri2indirection("GET", "/env/foo/bar", {})[1].should == :find
      end
  
+     it "should choose 'head' as the indirection method if the http method is a HEAD and the indirection name is singular" do
 -      @tester.uri2indirection("HEAD", "/env/foo/bar", {}).method.should == :head
++      @tester.uri2indirection("HEAD", "/env/foo/bar", {})[1].should == :head
+     end
+ 
      it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is plural" do
 -      @tester.uri2indirection("GET", "/env/foos/bar", {}).method.should == :search
 +      @tester.uri2indirection("GET", "/env/foos/bar", {})[1].should == :search
 +    end
 +
 +    it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is facts" do
 +      @tester.uri2indirection("GET", "/env/facts/bar", {})[1].should == :find
 +    end
 +
 +    it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do
 +      @tester.uri2indirection("PUT", "/env/facts/bar", {})[1].should == :save
 +    end
 +
 +    it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is inventory" do
 +      @tester.uri2indirection("GET", "/env/inventory/search", {})[1].should == :search
      end
  
      it "should choose 'delete' as the indirection method if the http method is a DELETE and the indirection name is singular" do
 -      @tester.uri2indirection("DELETE", "/env/foo/bar", {}).method.should == :destroy
 +      @tester.uri2indirection("DELETE", "/env/foo/bar", {})[1].should == :destroy
      end
  
      it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is singular" do
 -      @tester.uri2indirection("PUT", "/env/foo/bar", {}).method.should == :save
 +      @tester.uri2indirection("PUT", "/env/foo/bar", {})[1].should == :save
      end
  
      it "should fail if an indirection method cannot be picked" do
@@@ -98,8 -90,7 +102,8 @@@
  
      it "should URI unescape the indirection key" do
        escaped = URI.escape("foo bar")
 -      @tester.uri2indirection("GET", "/env/foo/#{escaped}", {}).key.should == "foo bar"
 +      indirection_name, method, key, params = @tester.uri2indirection("GET", "/env/foo/#{escaped}", {})
 +      key.should == "foo bar"
      end
    end
  
diff --combined spec/unit/network/http/handler_spec.rb
index ff60c78,8464ae6..97d17fc
--- a/spec/unit/network/http/handler_spec.rb
+++ b/spec/unit/network/http/handler_spec.rb
@@@ -1,6 -1,6 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
  require 'puppet/network/http/handler'
  require 'puppet/network/rest_authorization'
  
@@@ -46,8 -46,6 +46,8 @@@ describe Puppet::Network::HTTP::Handle
        @request.stubs(:[]).returns "foo"
        @response    = stub('http response')
        @model_class = stub('indirected model class')
 +      @indirection = stub('indirection')
 +      @model_class.stubs(:indirection).returns(@indirection)
  
        @result = stub 'result', :render => "mytext"
  
@@@ -81,22 -79,37 +81,22 @@@
        @handler.process(@request, @response)
      end
  
 -    it "should call the 'do' method associated with the indirection method" do
 -      request = stub 'request'
 -      @handler.expects(:uri2indirection).returns request
 +    it "should call the 'do' method and delegate authorization to the RestAuthorization layer" do
 +      @handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}])
  
 -      request.expects(:method).returns "mymethod"
 +      @handler.expects(:do_mymethod).with("facts", "key", {:node => "name"}, @request, @response)
  
 -      @handler.expects(:do_mymethod).with(request, @request, @response)
 -
 -      @handler.process(@request, @response)
 -    end
 -
 -    it "should delegate authorization to the RestAuthorization layer" do
 -      request = stub 'request'
 -      @handler.expects(:uri2indirection).returns request
 -
 -      request.expects(:method).returns "mymethod"
 -
 -      @handler.expects(:do_mymethod).with(request, @request, @response)
 -
 -      @handler.expects(:check_authorization).with(request)
 +      @handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"})
  
        @handler.process(@request, @response)
      end
  
      it "should return 403 if the request is not authorized" do
 -      request = stub 'request'
 -      @handler.expects(:uri2indirection).returns request
 +      @handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}])
  
        @handler.expects(:do_mymethod).never
  
 -      @handler.expects(:check_authorization).with(request).raises(Puppet::Network::AuthorizationError.new("forbindden"))
 +      @handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden"))
  
        @handler.expects(:set_response).with { |response, body, status| status == 403 }
  
@@@ -104,7 -117,7 +104,7 @@@
      end
  
      it "should serialize a controller exception when an exception is thrown while finding the model instance" do
 -      @handler.expects(:uri2indirection).returns stub("request", :method => :find)
 +      @handler.expects(:uri2indirection).returns(["facts", :find, "key", {:node => "name"}])
  
        @handler.expects(:do_find).raises(ArgumentError, "The exception")
        @handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 }
@@@ -128,8 -141,9 +128,8 @@@
  
      describe "when finding a model instance" do
        before do
 -        @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :to_hash => {}, :key => "my_result", :model => @model_class
 -
 -        @model_class.stubs(:find).returns @result
 +        @indirection.stubs(:find).returns @result
 +        Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
  
          @format = stub 'format', :suitable? => true, :mime => "text/format", :name => "format"
          Puppet::Network::FormatHandler.stubs(:format).returns @format
@@@ -139,37 -153,40 +139,37 @@@
        end
  
        it "should use the indirection request to find the model class" do
 -        @irequest.expects(:model).returns @model_class
 -
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use the escaped request key" do
 -        @model_class.expects(:find).with do |key, args|
 +        @indirection.expects(:find).with do |key, args|
            key == "my_result"
          end.returns @result
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use a common method for determining the request parameters" do
 -        @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy)
 -        @model_class.expects(:find).with do |key, args|
 +        @indirection.expects(:find).with do |key, args|
            args[:foo] == :baz and args[:bar] == :xyzzy
          end.returns @result
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
        end
  
        it "should set the content type to the first format specified in the accept header" do
          @handler.expects(:accept_header).with(@request).returns "one,two"
          @handler.expects(:set_content_type).with(@response, @oneformat)
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should fail if no accept header is provided" do
          @handler.expects(:accept_header).with(@request).returns nil
 -        lambda { @handler.do_find(@irequest, @request, @response) }.should raise_error(ArgumentError)
 +        lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError)
        end
  
        it "should fail if the accept header does not contain a valid format" do
          @handler.expects(:accept_header).with(@request).returns ""
 -        lambda { @handler.do_find(@irequest, @request, @response) }.should raise_error(RuntimeError)
 +        lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(RuntimeError)
        end
  
        it "should not use an unsuitable format" do
@@@ -181,7 -198,7 +181,7 @@@
  
          @handler.expects(:set_content_type).with(@response, bar) # the suitable one
  
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should render the result using the first format specified in the accept header" do
@@@ -189,12 -206,18 +189,18 @@@
          @handler.expects(:accept_header).with(@request).returns "one,two"
          @result.expects(:render).with(@oneformat)
  
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
+       it "should pass the result through without rendering it if the result is a string" do
 -        @model_class.stubs(:find).returns "foo"
++        @indirection.stubs(:find).returns "foo"
+         @handler.expects(:set_response).with(@response, "foo")
 -        @handler.do_find(@irequest, @request, @response)
++        @handler.do_find("my_handler", "my_result", {}, @request, @response)
+       end
+ 
        it "should use the default status when a model find call succeeds" do
          @handler.expects(:set_response).with { |response, body, status| status.nil? }
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return a serialized object when a model find call succeeds" do
@@@ -202,24 -225,24 +208,24 @@@
          @model_instance.expects(:render).returns "my_rendered_object"
  
          @handler.expects(:set_response).with { |response, body, status| body == "my_rendered_object" }
 -        @model_class.stubs(:find).returns(@model_instance)
 -        @handler.do_find(@irequest, @request, @response)
 +        @indirection.stubs(:find).returns(@model_instance)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return a 404 when no model instance can be found" do
          @model_class.stubs(:name).returns "my name"
          @handler.expects(:set_response).with { |response, body, status| status == 404 }
 -        @model_class.stubs(:find).returns(nil)
 -        @handler.do_find(@irequest, @request, @response)
 +        @indirection.stubs(:find).returns(nil)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should write a log message when no model instance can be found" do
          @model_class.stubs(:name).returns "my name"
 -        @model_class.stubs(:find).returns(nil)
 +        @indirection.stubs(:find).returns(nil)
  
          Puppet.expects(:info).with("Could not find my_handler for 'my_result'")
  
 -        @handler.do_find(@irequest, @request, @response)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
  
  
@@@ -228,21 -251,54 +234,54 @@@
  
          @handler.expects(:format_to_use).returns(@oneformat)
          @model_instance.expects(:render).with(@oneformat).returns "my_rendered_object"
 -        @model_class.stubs(:find).returns(@model_instance)
 -        @handler.do_find(@irequest, @request, @response)
 +        @indirection.stubs(:find).returns(@model_instance)
 +        @handler.do_find("my_handler", "my_result", {}, @request, @response)
        end
      end
  
+     describe "when performing head operation" do
+       before do
+         @irequest = stub 'indirection_request', :method => :head, :indirection_name => "my_handler", :to_hash => {}, :key => "my_result", :model => @model_class
+ 
+         @model_class.stubs(:head).returns true
+       end
+ 
+       it "should use the indirection request to find the model class" do
+         @irequest.expects(:model).returns @model_class
+ 
+         @handler.do_head(@irequest, @request, @response)
+       end
+ 
+       it "should use the escaped request key" do
+         @model_class.expects(:head).with do |key, args|
+           key == "my_result"
+         end.returns true
+         @handler.do_head(@irequest, @request, @response)
+       end
+ 
+       it "should not generate a response when a model head call succeeds" do
+         @handler.expects(:set_response).never
+         @handler.do_head(@irequest, @request, @response)
+       end
+ 
+       it "should return a 404 when the model head call returns false" do
+         @model_class.stubs(:name).returns "my name"
+         @handler.expects(:set_response).with { |response, body, status| status == 404 }
+         @model_class.stubs(:head).returns(false)
+         @handler.do_head(@irequest, @request, @response)
+       end
+     end
+ 
      describe "when searching for model instances" do
        before do
 -        @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class
 +        Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
  
          @result1 = mock 'result1'
          @result2 = mock 'results'
  
          @result = [@result1, @result2]
          @model_class.stubs(:render_multiple).returns "my rendered instances"
 -        @model_class.stubs(:search).returns(@result)
 +        @indirection.stubs(:search).returns(@result)
  
          @format = stub 'format', :suitable? => true, :mime => "text/format", :name => "format"
          Puppet::Network::FormatHandler.stubs(:format).returns @format
@@@ -252,109 -308,112 +291,109 @@@
        end
  
        it "should use the indirection request to find the model" do
 -        @irequest.expects(:model).returns @model_class
 -
 -        @handler.do_search(@irequest, @request, @response)
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use a common method for determining the request parameters" do
 -        @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy)
 -        @model_class.expects(:search).with do |key, args|
 +        @indirection.expects(:search).with do |key, args|
            args[:foo] == :baz and args[:bar] == :xyzzy
          end.returns @result
 -        @handler.do_search(@irequest, @request, @response)
 +        @handler.do_search("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
        end
  
        it "should use the default status when a model search call succeeds" do
 -        @model_class.stubs(:search).returns(@result)
 -        @handler.do_search(@irequest, @request, @response)
 +        @indirection.stubs(:search).returns(@result)
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should set the content type to the first format returned by the accept header" do
          @handler.expects(:accept_header).with(@request).returns "one,two"
          @handler.expects(:set_content_type).with(@response, @oneformat)
  
 -        @handler.do_search(@irequest, @request, @response)
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return a list of serialized objects when a model search call succeeds" do
          @handler.expects(:accept_header).with(@request).returns "one,two"
  
 -        @model_class.stubs(:search).returns(@result)
 +        @indirection.stubs(:search).returns(@result)
  
          @model_class.expects(:render_multiple).with(@oneformat, @result).returns "my rendered instances"
  
          @handler.expects(:set_response).with { |response, data| data == "my rendered instances" }
 -        @handler.do_search(@irequest, @request, @response)
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
  
 -      it "should return a 404 when searching returns an empty array" do
 -        @model_class.stubs(:name).returns "my name"
 -        @handler.expects(:set_response).with { |response, body, status| status == 404 }
 -        @model_class.stubs(:search).returns([])
 -        @handler.do_search(@irequest, @request, @response)
 +      it "should return [] when searching returns an empty array" do
 +        @handler.expects(:accept_header).with(@request).returns "one,two"
 +        @indirection.stubs(:search).returns([])
 +        @model_class.expects(:render_multiple).with(@oneformat, []).returns "[]"
 +
 +
 +        @handler.expects(:set_response).with { |response, data| data == "[]" }
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return a 404 when searching returns nil" do
          @model_class.stubs(:name).returns "my name"
          @handler.expects(:set_response).with { |response, body, status| status == 404 }
 -        @model_class.stubs(:search).returns([])
 -        @handler.do_search(@irequest, @request, @response)
 +        @indirection.stubs(:search).returns(nil)
 +        @handler.do_search("my_handler", "my_result", {}, @request, @response)
        end
      end
  
      describe "when destroying a model instance" do
        before do
 -        @irequest = stub 'indirection_request', :method => :destroy, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class
 +        Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
  
          @result = stub 'result', :render => "the result"
 -        @model_class.stubs(:destroy).returns @result
 +        @indirection.stubs(:destroy).returns @result
        end
  
        it "should use the indirection request to find the model" do
 -        @irequest.expects(:model).returns @model_class
 -
 -        @handler.do_destroy(@irequest, @request, @response)
 +        @handler.do_destroy("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use the escaped request key to destroy the instance in the model" do
 -        @irequest.expects(:key).returns "foo bar"
 -        @model_class.expects(:destroy).with do |key, args|
 +        @indirection.expects(:destroy).with do |key, args|
            key == "foo bar"
          end
 -        @handler.do_destroy(@irequest, @request, @response)
 +        @handler.do_destroy("my_handler", "foo bar", {}, @request, @response)
        end
  
        it "should use a common method for determining the request parameters" do
 -        @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy)
 -        @model_class.expects(:destroy).with do |key, args|
 +        @indirection.expects(:destroy).with do |key, args|
            args[:foo] == :baz and args[:bar] == :xyzzy
          end
 -        @handler.do_destroy(@irequest, @request, @response)
 +        @handler.do_destroy("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
        end
  
        it "should use the default status code a model destroy call succeeds" do
          @handler.expects(:set_response).with { |response, body, status| status.nil? }
 -        @handler.do_destroy(@irequest, @request, @response)
 +        @handler.do_destroy("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return a yaml-encoded result when a model destroy call succeeds" do
          @result = stub 'result', :to_yaml => "the result"
 -        @model_class.expects(:destroy).returns(@result)
 +        @indirection.expects(:destroy).returns(@result)
  
          @handler.expects(:set_response).with { |response, body, status| body == "the result" }
  
 -        @handler.do_destroy(@irequest, @request, @response)
 +        @handler.do_destroy("my_handler", "my_result", {}, @request, @response)
        end
      end
  
      describe "when saving a model instance" do
        before do
 -        @irequest = stub 'indirection_request', :method => :save, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class
 +        Puppet::Indirector::Indirection.stubs(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
          @handler.stubs(:body).returns('my stuff')
          @handler.stubs(:content_type_header).returns("text/yaml")
  
          @result = stub 'result', :render => "the result"
  
 -        @model_instance = stub('indirected model instance', :save => true)
 +        @model_instance = stub('indirected model instance')
          @model_class.stubs(:convert_from).returns(@model_instance)
 +        @indirection.stubs(:save)
  
          @format = stub 'format', :suitable? => true, :name => "format", :mime => "text/format"
          Puppet::Network::FormatHandler.stubs(:format).returns @format
@@@ -363,41 -422,43 +402,41 @@@
        end
  
        it "should use the indirection request to find the model" do
 -        @irequest.expects(:model).returns @model_class
 -
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use the 'body' hook to retrieve the body of the request" do
          @handler.expects(:body).returns "my body"
          @model_class.expects(:convert_from).with { |format, body| body == "my body" }.returns @model_instance
  
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should fail to save model if data is not specified" do
          @handler.stubs(:body).returns('')
  
 -        lambda { @handler.do_save(@irequest, @request, @response) }.should raise_error(ArgumentError)
 +        lambda { @handler.do_save("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError)
        end
  
        it "should use a common method for determining the request parameters" do
 -        @model_instance.expects(:save).with('key').once
 -        @handler.do_save(@irequest, @request, @response)
 +        @indirection.expects(:save).with(@model_instance, 'key').once
 +        @handler.do_save("my_handler", "key", {}, @request, @response)
        end
  
        it "should use the default status when a model save call succeeds" do
          @handler.expects(:set_response).with { |response, body, status| status.nil? }
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should return the yaml-serialized result when a model save call succeeds" do
 -        @model_instance.stubs(:save).returns(@model_instance)
 +        @indirection.stubs(:save).returns(@model_instance)
          @model_instance.expects(:to_yaml).returns('foo')
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should set the content to yaml" do
          @handler.expects(:set_content_type).with(@response, @yamlformat)
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
  
        it "should use the content-type header to know the body format" do
@@@ -406,7 -467,7 +445,7 @@@
  
          @model_class.expects(:convert_from).with { |format, body| format == "format" }.returns @model_instance
  
 -        @handler.do_save(@irequest, @request, @response)
 +        @handler.do_save("my_handler", "my_result", {}, @request, @response)
        end
      end
    end
diff --combined spec/unit/network/rest_authconfig_spec.rb
index 0479c4e,d629f86..270d1d0
--- a/spec/unit/network/rest_authconfig_spec.rb
+++ b/spec/unit/network/rest_authconfig_spec.rb
@@@ -1,6 -1,6 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
  require 'puppet/network/rest_authconfig'
  
@@@ -29,6 -29,9 +29,6 @@@ describe Puppet::Network::RestAuthConfi
  
      @acl = stub_everything 'rights'
      @authconfig.rights = @acl
 -
 -    @request = stub 'request', :indirection_name => "path", :key => "to/resource", :ip => "127.0.0.1",
 -      :node => "me", :method => :save, :environment => :env, :authenticated => true
    end
  
    it "should use the puppet default rest authorization file" do
@@@ -37,10 -40,16 +37,11 @@@
      Puppet::Network::RestAuthConfig.new(nil, false)
    end
  
 -  it "should read the config file when needed" do
 -    @authconfig.expects(:read)
 -
 -    @authconfig.allowed?(@request)
 -  end
 -
    it "should ask for authorization to the ACL subsystem" do
-     @acl.expects(:fail_on_deny).with("/path/to/resource", :node => "me", :ip => "127.0.0.1", :method => :save, :environment => :env, :authenticated => true)
 -    @acl.expects(:is_request_forbidden_and_why?).with(@request).returns(nil)
++    params = {:ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true}
++    @acl.expects(:is_request_forbidden_and_why?).with("path", :save, "to/resource", params).returns(nil)
  
-     @authconfig.allowed?("path", :save, "to/resource", :ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true)
 -    @authconfig.allowed?(@request)
++    @authconfig.allowed?("path", :save, "to/resource", params)
    end
  
    describe "when defining an acl with mk_acl" do
diff --combined spec/unit/network/rights_spec.rb
index fedaae2,3b9e483..8ae03c5
--- a/spec/unit/network/rights_spec.rb
+++ b/spec/unit/network/rights_spec.rb
@@@ -1,6 -1,6 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
  require 'puppet/network/rights'
  
@@@ -9,6 -9,26 +9,26 @@@ describe Puppet::Network::Rights d
      @right = Puppet::Network::Rights.new
    end
  
+   describe "when validating a :head request" do
+     [:find, :save].each do |allowed_method|
+       it "should allow the request if only #{allowed_method} is allowed" do
+         rights = Puppet::Network::Rights.new
+         rights.newright("/")
+         rights.allow("/", "*")
+         rights.restrict_method("/", allowed_method)
+         rights.restrict_authenticated("/", :any)
 -        request = Puppet::Indirector::Request.new(:indirection_name, :head, "key")
 -        rights.is_request_forbidden_and_why?(request).should == nil
++        rights.is_request_forbidden_and_why?(:indirection_name, :head, "key", {}).should == nil
+       end
+     end
+ 
+     it "should disallow the request if neither :find nor :save is allowed" do
+       rights = Puppet::Network::Rights.new
 -      request = Puppet::Indirector::Request.new(:indirection_name, :head, "key")
 -      rights.is_request_forbidden_and_why?(request).should be_instance_of(Puppet::Network::AuthorizationError)
++      why_forbidden = rights.is_request_forbidden_and_why?(:indirection_name, :head, "key", {})
++      why_forbidden.should be_instance_of(Puppet::Network::AuthorizationError)
++      why_forbidden.to_s.should == "Forbidden request:  access to /indirection_name/key [find]"
+     end
+   end
+ 
    [:allow, :deny, :restrict_method, :restrict_environment, :restrict_authenticated].each do |m|
      it "should have a #{m} method" do
        @right.should respond_to(m)
@@@ -155,19 -175,19 +175,19 @@@
        Puppet::Network::Rights::Right.stubs(:new).returns(@pathacl)
      end
  
-     it "should delegate to fail_on_deny" do
-       @right.expects(:fail_on_deny).with("namespace", :node => "host.domain.com", :ip => "127.0.0.1")
+     it "should delegate to is_forbidden_and_why?" do
+       @right.expects(:is_forbidden_and_why?).with("namespace", :node => "host.domain.com", :ip => "127.0.0.1").returns(nil)
  
        @right.allowed?("namespace", "host.domain.com", "127.0.0.1")
      end
  
-     it "should return true if fail_on_deny doesn't fail" do
-       @right.stubs(:fail_on_deny)
+     it "should return true if is_forbidden_and_why? returns nil" do
+       @right.stubs(:is_forbidden_and_why?).returns(nil)
        @right.allowed?("namespace", :args).should be_true
      end
  
-     it "should return false if fail_on_deny raises an AuthorizationError" do
-       @right.stubs(:fail_on_deny).raises(Puppet::Network::AuthorizationError.new("forbidden"))
+     it "should return false if is_forbidden_and_why? returns an AuthorizationError" do
+       @right.stubs(:is_forbidden_and_why?).returns(Puppet::Network::AuthorizationError.new("forbidden"))
        @right.allowed?("namespace", :args1, :args2).should be_false
      end
  
@@@ -179,7 -199,7 +199,7 @@@
        acl.expects(:match?).returns(true)
        acl.expects(:allowed?).with { |node,ip,h| node == "node" and ip == "ip" }.returns(true)
  
-       @right.fail_on_deny("namespace", { :node => "node", :ip => "ip" } )
+       @right.is_forbidden_and_why?("namespace", { :node => "node", :ip => "ip" } ).should == nil
      end
  
      it "should then check for path rights if no namespace match" do
@@@ -195,7 -215,7 +215,7 @@@
        acl.expects(:allowed?).never
        @pathacl.expects(:allowed?).returns(true)
  
-       @right.fail_on_deny("/path/to/there", {})
+       @right.is_forbidden_and_why?("/path/to/there", {}).should == nil
      end
  
      it "should pass the match? return to allowed?" do
@@@ -204,12 -224,12 +224,12 @@@
        @pathacl.expects(:match?).returns(:match)
        @pathacl.expects(:allowed?).with { |node,ip,h| h[:match] == :match }.returns(true)
  
-       @right.fail_on_deny("/path/to/there", {})
+       @right.is_forbidden_and_why?("/path/to/there", {}).should == nil
      end
  
      describe "with namespace acls" do
-       it "should raise an error if this namespace right doesn't exist" do
-         lambda{ @right.fail_on_deny("namespace") }.should raise_error
+       it "should return an ArgumentError if this namespace right doesn't exist" do
+         lambda { @right.is_forbidden_and_why?("namespace") }.should raise_error(ArgumentError)
        end
      end
  
@@@ -235,7 -255,7 +255,7 @@@
          @long_acl.expects(:allowed?).returns(true)
          @short_acl.expects(:allowed?).never
  
-         @right.fail_on_deny("/path/to/there/and/there", {})
+         @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil
        end
  
        it "should select the first match that doesn't return :dunno" do
@@@ -248,7 -268,7 +268,7 @@@
          @long_acl.expects(:allowed?).returns(:dunno)
          @short_acl.expects(:allowed?).returns(true)
  
-         @right.fail_on_deny("/path/to/there/and/there", {})
+         @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil
        end
  
        it "should not select an ACL that doesn't match" do
@@@ -261,7 -281,7 +281,7 @@@
          @long_acl.expects(:allowed?).never
          @short_acl.expects(:allowed?).returns(true)
  
-         @right.fail_on_deny("/path/to/there/and/there", {})
+         @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil
        end
  
        it "should not raise an AuthorizationError if allowed" do
@@@ -270,7 -290,7 +290,7 @@@
          @long_acl.stubs(:match?).returns(true)
          @long_acl.stubs(:allowed?).returns(true)
  
-         lambda { @right.fail_on_deny("/path/to/there/and/there", {}) }.should_not raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/path/to/there/and/there", {}).should == nil
        end
  
        it "should raise an AuthorizationError if the match is denied" do
@@@ -279,11 -299,11 +299,11 @@@
          @long_acl.stubs(:match?).returns(true)
          @long_acl.stubs(:allowed?).returns(false)
  
-         lambda{ @right.fail_on_deny("/path/to/there", {}) }.should raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/path/to/there", {}).should be_instance_of(Puppet::Network::AuthorizationError)
        end
  
        it "should raise an AuthorizationError if no path match" do
-         lambda { @right.fail_on_deny("/nomatch", {}) }.should raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/nomatch", {}).should be_instance_of(Puppet::Network::AuthorizationError)
        end
      end
  
@@@ -309,7 -329,7 +329,7 @@@
          @regex_acl1.expects(:allowed?).returns(true)
          @regex_acl2.expects(:allowed?).never
  
-         @right.fail_on_deny("/files/repository/myfile/other", {})
+         @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil
        end
  
        it "should select the first match that doesn't return :dunno" do
@@@ -322,7 -342,7 +342,7 @@@
          @regex_acl1.expects(:allowed?).returns(:dunno)
          @regex_acl2.expects(:allowed?).returns(true)
  
-         @right.fail_on_deny("/files/repository/myfile/other", {})
+         @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil
        end
  
        it "should not select an ACL that doesn't match" do
@@@ -335,7 -355,7 +355,7 @@@
          @regex_acl1.expects(:allowed?).never
          @regex_acl2.expects(:allowed?).returns(true)
  
-         @right.fail_on_deny("/files/repository/myfile/other", {})
+         @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil
        end
  
        it "should not raise an AuthorizationError if allowed" do
@@@ -344,15 -364,15 +364,15 @@@
          @regex_acl1.stubs(:match?).returns(true)
          @regex_acl1.stubs(:allowed?).returns(true)
  
-         lambda { @right.fail_on_deny("/files/repository/myfile/other", {}) }.should_not raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/files/repository/myfile/other", {}).should == nil
        end
  
        it "should raise an error if no regex acl match" do
-         lambda{ @right.fail_on_deny("/path", {}) }.should raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/path", {}).should be_instance_of(Puppet::Network::AuthorizationError)
        end
  
        it "should raise an AuthorizedError on deny" do
-         lambda { @right.fail_on_deny("/path", {}) }.should raise_error(Puppet::Network::AuthorizationError)
+         @right.is_forbidden_and_why?("/path", {}).should be_instance_of(Puppet::Network::AuthorizationError)
        end
  
      end
diff --combined spec/unit/transaction/resource_harness_spec.rb
index 0ec31be,104c19e..168b502
--- a/spec/unit/transaction/resource_harness_spec.rb
+++ b/spec/unit/transaction/resource_harness_spec.rb
@@@ -1,6 -1,7 +1,6 @@@
  #!/usr/bin/env ruby
  
 -require File.dirname(__FILE__) + '/../../spec_helper'
 -require 'puppet_spec/files'
 +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
  
  require 'puppet/transaction/resource_harness'
  
@@@ -63,6 -64,90 +63,90 @@@ describe Puppet::Transaction::ResourceH
      end
    end
  
+   def make_stub_provider
+     stubProvider = Class.new(Puppet::Type)
+     stubProvider.instance_eval do
+       initvars
+ 
+       newparam(:name) do
+         desc "The name var"
+         isnamevar
+       end
+ 
+       newproperty(:foo) do
+         desc "A property that can be changed successfully"
+         def sync
+         end
+ 
+         def retrieve
+           :absent
+         end
+ 
+         def insync?(reference_value)
+           false
+         end
+       end
+ 
+       newproperty(:bar) do
+         desc "A property that raises an exception when you try to change it"
+         def sync
+           raise ZeroDivisionError.new('bar')
+         end
+ 
+         def retrieve
+           :absent
+         end
+ 
+         def insync?(reference_value)
+           false
+         end
+       end
+     end
+     stubProvider
+   end
+ 
+   describe "when an error occurs" do
+     before :each do
+       stub_provider = make_stub_provider
+       resource = stub_provider.new :name => 'name', :foo => 1, :bar => 2
+       resource.expects(:err).never
+       @status = @harness.evaluate(resource)
+     end
+ 
+     it "should record previous successful events" do
+       @status.events[0].property.should == 'foo'
+       @status.events[0].status.should == 'success'
+     end
+ 
+     it "should record a failure event" do
+       @status.events[1].property.should == 'bar'
+       @status.events[1].status.should == 'failure'
+     end
+   end
+ 
+   describe "when auditing" do
+     it "should not call insync? on parameters that are merely audited" do
+       stub_provider = make_stub_provider
+       resource = stub_provider.new :name => 'name', :audit => ['foo']
+       resource.property(:foo).expects(:insync?).never
+       status = @harness.evaluate(resource)
+       status.events.each do |event|
+         event.status.should != 'failure'
+       end
+     end
+ 
+     it "should be able to audit a file's group" do # see bug #5710
+       test_file = tmpfile('foo')
+       File.open(test_file, 'w').close
+       resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['group'], :backup => false
+       resource.expects(:err).never # make sure no exceptions get swallowed
+       status = @harness.evaluate(resource)
+       status.events.each do |event|
+         event.status.should != 'failure'
+       end
+     end
+   end
+ 
    describe "when applying changes" do
      [false, true].each do |noop_mode|; describe (noop_mode ? "in noop mode" : "in normal mode") do
        [nil, '750'].each do |machine_state|; describe (machine_state ? "with a file initially present" : "with no file initially present") do

-- 
Puppet packaging for Debian



More information about the Pkg-puppet-devel mailing list