diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index 907186ac3..de5995d7e 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -1,128 +1,122 @@ # # Created by Luke Kanies on 2007-10-16. # Copyright (c) 2007. All rights reserved. require 'puppet' require 'puppet/file_serving' require 'puppet/file_serving/mount' +require 'puppet/file_serving/mount/file' +require 'puppet/file_serving/mount/modules' +require 'puppet/file_serving/mount/plugins' require 'puppet/util/cacher' +require 'puppet/util/uri_helper' class Puppet::FileServing::Configuration + include Puppet::Util::URIHelper require 'puppet/file_serving/configuration/parser' class << self include Puppet::Util::Cacher cached_attr(:configuration) { new() } end - @config_fileuration = nil - Mount = Puppet::FileServing::Mount # Create our singleton configuration. def self.create configuration end private_class_method :new - # Verify that the client is allowed access to this file. - def authorized?(file, options = {}) - mount, file_path = split_path(file, options[:node]) - # If we're not serving this mount, then access is denied. - return false unless mount - return mount.allowed?(options[:node], options[:ipaddress]) - end + attr_reader :mounts + #private :mounts + + # Find the right mount. Does some shenanigans to support old-style module + # mounts. + def find_mount(mount_name, node) + # Reparse the configuration if necessary. + readconfig - # Search for a file. - def file_path(key, options = {}) - mount, file_path = split_path(key, options[:node]) + if mount = mounts[mount_name] + return mount + end - return nil unless mount + if mounts["modules"].environment(node).module(mount_name) + Puppet.warning "DEPRECATION NOTICE: Found module '%s' without using the 'modules' mount; please prefix path with 'modules/'" % mount_name + return mounts["modules"] + end - # The mount checks to see if the file exists, and returns nil - # if not. - return mount.file(file_path, options) + # This can be nil. + mounts[mount_name] end def initialize @mounts = {} @config_file = nil # We don't check to see if the file is modified the first time, # because we always want to parse at first. readconfig(false) end # Is a given mount available? def mounted?(name) @mounts.include?(name) end + # Split the path into the separate mount point and path. + def split_path(request) + # Reparse the configuration if necessary. + readconfig + + uri = key2uri(request.key) + + mount_name, path = uri.path.sub(/^\//, '').split(File::Separator, 2) + + raise(ArgumentError, "Cannot find file: Invalid path '%s'" % mount_name) unless mount_name =~ %r{^[-\w]+$} + + return nil unless mount = find_mount(mount_name, request.options[:node]) + if mount.name == "modules" and mount_name != "modules" + # yay backward-compatibility + path = "%s/%s" % [mount_name, path] + end + + if path == "" + path = nil + elsif path + # Remove any double slashes that might have occurred + path = path.gsub(/\/+/, "/") + end + + return mount, path + end + def umount(name) @mounts.delete(name) if @mounts.include? name end private - # Deal with ignore parameters. - def handleignore(children, path, ignore) - ignore.each { |ignore| - Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| - children.delete(File.basename(match)) - } - } - return children - end - # Read the configuration file. def readconfig(check = true) config = Puppet[:fileserverconfig] return unless FileTest.exists?(config) @parser ||= Puppet::FileServing::Configuration::Parser.new(config) if check and ! @parser.changed? return end # Don't assign the mounts hash until we're sure the parsing succeeded. begin newmounts = @parser.parse @mounts = newmounts rescue => detail + puts detail.backtrace if Puppet[:trace] Puppet.err "Error parsing fileserver configuration: %s; using old configuration" % detail end end - - # Split the path into the separate mount point and path. - def split_path(uri, node) - # Reparse the configuration if necessary. - readconfig - - raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{^([-\w]+)(/|$)} - - # the dir is based on one of the mounts - # so first retrieve the mount path - mount = path = nil - - # Strip off the mount name. - mount_name, path = uri.split(File::Separator, 2) - - return nil unless mount = @mounts[mount_name] - - if path == "" - path = nil - elsif path - # Remove any double slashes that might have occurred - path = URI.unescape(path.gsub(/\/\//, "/")) - end - - return mount, path - end - - def to_s - "fileserver" - end end diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb index cda6889d4..f36bef639 100644 --- a/lib/puppet/file_serving/configuration/parser.rb +++ b/lib/puppet/file_serving/configuration/parser.rb @@ -1,126 +1,129 @@ require 'puppet/file_serving/configuration' require 'puppet/util/loadedfile' class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile Mount = Puppet::FileServing::Mount MODULES = 'modules' # Parse our configuration file. def parse raise("File server configuration %s does not exist" % self.file) unless FileTest.exists?(self.file) raise("Cannot read file server configuration %s" % self.file) unless FileTest.readable?(self.file) @mounts = {} @count = 0 File.open(self.file) { |f| mount = nil f.each { |line| # Have the count increment at the top, in case we throw exceptions. @count += 1 case line when /^\s*#/: next # skip comments when /^\s*$/: next # skip blank lines when /\[([-\w]+)\]/: mount = newmount($1) when /^\s*(\w+)\s+(.+)$/: var = $1 value = $2 raise(ArgumentError, "Fileserver configuration file does not use '=' as a separator") if value =~ /^=/ case var when "path": path(mount, value) when "allow": allow(mount, value) when "deny": deny(mount, value) else raise ArgumentError.new("Invalid argument '%s'" % var, @count, file) end else raise ArgumentError.new("Invalid line '%s'" % line.chomp, @count, file) end } } + mk_default_mounts + + validate() + return @mounts end private - # Add the mount for getting files from modules. - def add_module_mount - unless @mounts[MODULES] - mount = Mount.new(MODULES) - mount.allow("*") - @mounts[MODULES] = mount - end - end - # Allow a given pattern access to a mount. def allow(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "allowing %s access" % val mount.allow(val) rescue AuthStoreError => detail raise ArgumentError.new(detail.to_s, @count, file) end } end # Deny a given pattern access to a mount. def deny(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "denying %s access" % val mount.deny(val) rescue AuthStoreError => detail raise ArgumentError.new(detail.to_s, @count, file) end } end + def mk_default_mounts + ["plugins", "modules"].each do |name| + newmount(name) unless @mounts[name] + end + end + # Create a new mount. def newmount(name) if @mounts.include?(name) raise ArgumentError, "%s is already mounted at %s" % [@mounts[name], name], @count, file end - mount = Mount.new(name) + case name + when "modules" + mount = Mount::Modules.new(name) + when "plugins" + mount = Mount::Plugins.new(name) + else + mount = Mount::File.new(name) + end @mounts[name] = mount return mount end # Set the path for a mount. def path(mount, value) - if mount.name == MODULES - Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it" - else + if mount.respond_to?(:path=) begin mount.path = value rescue ArgumentError => detail - Puppet.err "Removing mount %s: %s" % - [mount.name, detail] + Puppet.err "Removing mount %s: %s" % [mount.name, detail] @mounts.delete(mount.name) end + else + Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" end end # Make sure all of our mounts are valid. We have to do this after the fact # because details are added over time as the file is parsed. def validate - @mounts.each { |name, mount| - unless mount.valid? - raise ArgumentError, "No path specified for mount %s" % name - end - } + @mounts.each { |name, mount| mount.validate } end end diff --git a/lib/puppet/file_serving/indirection_hooks.rb b/lib/puppet/file_serving/indirection_hooks.rb index 15564cf3d..5fd7985e7 100644 --- a/lib/puppet/file_serving/indirection_hooks.rb +++ b/lib/puppet/file_serving/indirection_hooks.rb @@ -1,47 +1,34 @@ # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. require 'uri' require 'puppet/file_serving' # This module is used to pick the appropriate terminus # in file-serving indirections. This is necessary because # the terminus varies based on the URI asked for. module Puppet::FileServing::IndirectionHooks PROTOCOL_MAP = {"puppet" => :rest, "file" => :file} # Pick an appropriate terminus based on the protocol. def select_terminus(request) # We rely on the request's parsing of the URI. # Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol. return PROTOCOL_MAP["file"] if request.key =~ /^#{::File::SEPARATOR}/ return PROTOCOL_MAP["file"] if request.protocol == "file" # We're heading over the wire the protocol is 'puppet' and we've got a server name or we're not named 'puppet' if request.protocol == "puppet" and (request.server or Puppet.settings[:name] != "puppet") return PROTOCOL_MAP["puppet"] end if request.protocol and PROTOCOL_MAP[request.protocol].nil? raise(ArgumentError, "URI protocol '%s' is not currently supported for file serving" % request.protocol) end # If we're still here, we're using the file_server or modules. - - # This is the backward-compatible module terminus. - modname = request.key.split("/")[0] - - if modname == "modules" - terminus = :modules - elsif terminus(:modules).find_module(modname, request.options[:node]) - Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with 'modules/'" % request.key - terminus = :modules - else - terminus = :file_server - end - - return terminus + return :file_server end end diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb index 552cf33f2..5b74ad218 100644 --- a/lib/puppet/file_serving/mount.rb +++ b/lib/puppet/file_serving/mount.rb @@ -1,184 +1,53 @@ # # Created by Luke Kanies on 2007-10-16. # Copyright (c) 2007. All rights reserved. require 'puppet/network/authstore' require 'puppet/util/logging' require 'puppet/util/cacher' require 'puppet/file_serving' require 'puppet/file_serving/metadata' require 'puppet/file_serving/content' # Broker access to the filesystem, converting local URIs into metadata # or content objects. class Puppet::FileServing::Mount < Puppet::Network::AuthStore include Puppet::Util::Logging - class << self - include Puppet::Util::Cacher - - cached_attr(:localmap) do - { "h" => Facter.value("hostname"), - "H" => [Facter.value("hostname"), - Facter.value("domain")].join("."), - "d" => Facter.value("domain") - } - end - end - attr_reader :name - - # Return a new mount with the same properties as +self+, except - # with a different name and path. - def copy(name, path) - result = self.clone - result.path = path - result.instance_variable_set(:@name, name) - return result - end - - # Return an instance of the appropriate class. - def file(short_file, options = {}) - file = file_path(short_file, options[:node]) - - return nil unless FileTest.exists?(file) - - return file + + # Determine the environment to use, if any. + def environment(node_name) + if node_name and node = Puppet::Node.find(node_name) + Puppet::Node::Environment.new(node.environment) + else + Puppet::Node::Environment.new + end end - # Return a fully qualified path, given a short path and - # possibly a client name. - def file_path(relative_path, node = nil) - full_path = path(node) - raise ArgumentError.new("Mounts without paths are not usable") unless full_path - - # If there's no relative path name, then we're serving the mount itself. - return full_path unless relative_path - - return File.join(full_path, relative_path) + def find(path, options) + raise NotImplementedError end - # Create out object. It must have a name. - def initialize(name, path = nil) + # Create our object. It must have a name. + def initialize(name) unless name =~ %r{^[-\w]+$} raise ArgumentError, "Invalid mount name format '%s'" % name end @name = name - if path - self.path = path - else - @path = nil - end - super() end - # Return the path as appropriate, expanding as necessary. - def path(node = nil) - if expandable? - return expand(@path, node) - else - return @path - end - end - - # Set the path. - def path=(path) - # FIXME: For now, just don't validate paths with replacement - # patterns in them. - if path =~ /%./ - # Mark that we're expandable. - @expandable = true - else - unless FileTest.directory?(path) - raise ArgumentError, "%s does not exist or is not a directory" % path - end - unless FileTest.readable?(path) - raise ArgumentError, "%s is not readable" % path - end - @expandable = false - end - @path = path - end - - def sync(path) - @@syncs[path] ||= Sync.new - @@syncs[path] + def search(path, options) + raise NotImplementedError end def to_s "mount[%s]" % @name end - # Verify our configuration is valid. This should really check to - # make sure at least someone will be allowed, but, eh. - def valid? - return ! @path.nil? - end - - private - - # LAK:FIXME Move this method to the REST terminus hook. - def authcheck(file, client, clientip) - raise "This method should be replaced by a REST/terminus hook" - # If we're local, don't bother passing in information. - if local? - client = nil - clientip = nil - end - unless mount.allowed?(client, clientip) - mount.warning "%s cannot access %s" % - [client, file] - raise Puppet::AuthorizationError, "Cannot access %s" % mount - end - end - - # Create a map for a specific node. - def clientmap(node) - { - "h" => node.sub(/\..*$/, ""), - "H" => node, - "d" => node.sub(/[^.]+\./, "") # domain name - } - end - - # Replace % patterns as appropriate. - def expand(path, node = nil) - # This map should probably be moved into a method. - map = nil - - if node - map = clientmap(node) - else - Puppet.notice "No client; expanding '%s' with local host" % - path - # Else, use the local information - map = localmap() - end - - path.gsub(/%(.)/) do |v| - key = $1 - if key == "%" - "%" - else - map[key] || v - end - end - end - - # Do we have any patterns in our path, yo? - def expandable? - if defined? @expandable - @expandable - else - false - end - end - - # Cache this manufactured map, since if it's used it's likely - # to get used a lot. - def localmap - self.class.localmap + # A noop. + def validate end end diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount/file.rb similarity index 56% copy from lib/puppet/file_serving/mount.rb copy to lib/puppet/file_serving/mount/file.rb index 552cf33f2..15e343c57 100644 --- a/lib/puppet/file_serving/mount.rb +++ b/lib/puppet/file_serving/mount/file.rb @@ -1,184 +1,126 @@ -# -# Created by Luke Kanies on 2007-10-16. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/network/authstore' -require 'puppet/util/logging' require 'puppet/util/cacher' -require 'puppet/file_serving' -require 'puppet/file_serving/metadata' -require 'puppet/file_serving/content' -# Broker access to the filesystem, converting local URIs into metadata -# or content objects. -class Puppet::FileServing::Mount < Puppet::Network::AuthStore - include Puppet::Util::Logging +require 'puppet/file_serving/mount' +class Puppet::FileServing::Mount::File < Puppet::FileServing::Mount class << self include Puppet::Util::Cacher cached_attr(:localmap) do { "h" => Facter.value("hostname"), "H" => [Facter.value("hostname"), Facter.value("domain")].join("."), "d" => Facter.value("domain") } end end - attr_reader :name - - # Return a new mount with the same properties as +self+, except - # with a different name and path. - def copy(name, path) - result = self.clone - result.path = path - result.instance_variable_set(:@name, name) - return result - end - - # Return an instance of the appropriate class. - def file(short_file, options = {}) - file = file_path(short_file, options[:node]) - - return nil unless FileTest.exists?(file) - - return file - end - - # Return a fully qualified path, given a short path and - # possibly a client name. - def file_path(relative_path, node = nil) + def complete_path(relative_path, node = nil) full_path = path(node) + raise ArgumentError.new("Mounts without paths are not usable") unless full_path # If there's no relative path name, then we're serving the mount itself. return full_path unless relative_path - return File.join(full_path, relative_path) - end + file = ::File.join(full_path, relative_path) - # Create out object. It must have a name. - def initialize(name, path = nil) - unless name =~ %r{^[-\w]+$} - raise ArgumentError, "Invalid mount name format '%s'" % name - end - @name = name + return nil unless FileTest.exist?(file) - if path - self.path = path - else - @path = nil - end + return file + end - super() + # Return an instance of the appropriate class. + def find(short_file, options = {}) + complete_path(short_file, options[:node]) end # Return the path as appropriate, expanding as necessary. def path(node = nil) if expandable? return expand(@path, node) else return @path end end # Set the path. def path=(path) # FIXME: For now, just don't validate paths with replacement # patterns in them. if path =~ /%./ # Mark that we're expandable. @expandable = true else unless FileTest.directory?(path) raise ArgumentError, "%s does not exist or is not a directory" % path end unless FileTest.readable?(path) raise ArgumentError, "%s is not readable" % path end @expandable = false end @path = path end - def sync(path) - @@syncs[path] ||= Sync.new - @@syncs[path] - end - - def to_s - "mount[%s]" % @name + def search(path, options = {}) + return nil unless path = complete_path(path, options[:node]) + return [path] end # Verify our configuration is valid. This should really check to # make sure at least someone will be allowed, but, eh. - def valid? - return ! @path.nil? + def validate + raise ArgumentError.new("Mounts without paths are not usable") if @path.nil? end private - # LAK:FIXME Move this method to the REST terminus hook. - def authcheck(file, client, clientip) - raise "This method should be replaced by a REST/terminus hook" - # If we're local, don't bother passing in information. - if local? - client = nil - clientip = nil - end - unless mount.allowed?(client, clientip) - mount.warning "%s cannot access %s" % - [client, file] - raise Puppet::AuthorizationError, "Cannot access %s" % mount - end - end - # Create a map for a specific node. def clientmap(node) { "h" => node.sub(/\..*$/, ""), "H" => node, "d" => node.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. def expand(path, node = nil) # This map should probably be moved into a method. map = nil if node map = clientmap(node) else Puppet.notice "No client; expanding '%s' with local host" % path # Else, use the local information map = localmap() end path.gsub(/%(.)/) do |v| key = $1 if key == "%" "%" else map[key] || v end end end # Do we have any patterns in our path, yo? def expandable? if defined? @expandable @expandable else false end end # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap self.class.localmap end end diff --git a/lib/puppet/file_serving/mount/modules.rb b/lib/puppet/file_serving/mount/modules.rb new file mode 100644 index 000000000..bf0bad02b --- /dev/null +++ b/lib/puppet/file_serving/mount/modules.rb @@ -0,0 +1,25 @@ +require 'puppet/file_serving/mount' + +# This is the modules-specific mount: it knows how to search through +# modules for files. Yay. +class Puppet::FileServing::Mount::Modules < Puppet::FileServing::Mount + # Return an instance of the appropriate class. + def find(path, options = {}) + module_name, relative_path = path.split("/", 2) + return nil unless mod = environment(options[:node]).module(module_name) + + mod.file(relative_path) + end + + def search(path, options = {}) + module_name, relative_path = path.split("/", 2) + return nil unless mod = environment(options[:node]).module(module_name) + + return nil unless path = mod.file(relative_path) + return [path] + end + + def valid? + true + end +end diff --git a/lib/puppet/file_serving/mount/plugins.rb b/lib/puppet/file_serving/mount/plugins.rb new file mode 100644 index 000000000..487bd043b --- /dev/null +++ b/lib/puppet/file_serving/mount/plugins.rb @@ -0,0 +1,27 @@ +require 'puppet/file_serving/mount' + +# Find files in the modules' plugins directories. +# This is a very strange mount because it merges +# many directories into one. +class Puppet::FileServing::Mount::Plugins < Puppet::FileServing::Mount + # Return an instance of the appropriate class. + def find(relative_path, options = {}) + return nil unless mod = environment(options[:node]).modules.find { |mod| mod.plugin(relative_path) } + + path = mod.plugin(relative_path) + + return path + end + + def search(relative_path, options = {}) + # We currently only support one kind of search on plugins - return + # them all. + paths = environment(options[:node]).modules.find_all { |mod| mod.plugins? }.collect { |mod| mod.plugins } + return nil if paths.empty? + return paths + end + + def valid? + true + end +end diff --git a/lib/puppet/indirector/file_content/modules.rb b/lib/puppet/indirector/file_content/modules.rb deleted file mode 100644 index 8563dacb4..000000000 --- a/lib/puppet/indirector/file_content/modules.rb +++ /dev/null @@ -1,11 +0,0 @@ -# -# Created by Luke Kanies on 2007-10-18. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/file_serving/content' -require 'puppet/indirector/file_content' -require 'puppet/indirector/module_files' - -class Puppet::Indirector::FileContent::Modules < Puppet::Indirector::ModuleFiles - desc "Retrieve file contents from modules." -end diff --git a/lib/puppet/indirector/file_metadata/modules.rb b/lib/puppet/indirector/file_metadata/modules.rb deleted file mode 100644 index 4598c2175..000000000 --- a/lib/puppet/indirector/file_metadata/modules.rb +++ /dev/null @@ -1,17 +0,0 @@ -# -# Created by Luke Kanies on 2007-10-18. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/file_serving/metadata' -require 'puppet/indirector/file_metadata' -require 'puppet/indirector/module_files' - -class Puppet::Indirector::FileMetadata::Modules < Puppet::Indirector::ModuleFiles - desc "Retrieve file metadata from modules." - - def find(*args) - return unless instance = super - instance.collect - instance - end -end diff --git a/lib/puppet/indirector/file_server.rb b/lib/puppet/indirector/file_server.rb index 46a590f9c..e3bde1540 100644 --- a/lib/puppet/indirector/file_server.rb +++ b/lib/puppet/indirector/file_server.rb @@ -1,57 +1,68 @@ # # Created by Luke Kanies on 2007-10-19. # Copyright (c) 2007. All rights reserved. -require 'puppet/util/uri_helper' require 'puppet/file_serving/configuration' require 'puppet/file_serving/fileset' require 'puppet/file_serving/terminus_helper' require 'puppet/indirector/terminus' # Look files up using the file server. class Puppet::Indirector::FileServer < Puppet::Indirector::Terminus - include Puppet::Util::URIHelper include Puppet::FileServing::TerminusHelper # Is the client authorized to perform this action? def authorized?(request) return false unless [:find, :search].include?(request.method) - uri = key2uri(request.key) + mount, file_path = configuration.split_path(request) - configuration.authorized?(uri.path, :node => request.node, :ipaddress => request.ip) + # If we're not serving this mount, then access is denied. + return false unless mount + return mount.allowed?(request.options[:node], request.options[:ipaddress]) end # Find our key using the fileserver. def find(request) - return nil unless path = find_path(request) - result = model.new(path) + mount, relative_path = configuration.split_path(request) + + return nil unless mount + + # The mount checks to see if the file exists, and returns nil + # if not. + return nil unless path = mount.find(relative_path, request.options) + result = model.new(path) result.links = request.options[:links] if request.options[:links] result.collect - return result + result end # Search for files. This returns an array rather than a single # file. def search(request) - return nil unless path = find_path(request) + mount, relative_path = configuration.split_path(request) + + return nil unless mount + + return nil unless paths = mount.search(relative_path, :node => request.node) + + filesets = paths.collect do |path| + # Filesets support indirector requests as an options collection + Puppet::FileServing::Fileset.new(path, request) + end - path2instances(request, path) + Puppet::FileServing::Fileset.merge(*filesets).collect do |file, base_path| + inst = model.new(base_path, :relative_path => file) + inst.links = request.options[:links] if request.options[:links] + inst.collect + inst + end end private # Our fileserver configuration, if needed. def configuration Puppet::FileServing::Configuration.create end - - # Find our path; used by :find and :search. - def find_path(request) - uri = key2uri(request.key) - - return nil unless path = configuration.file_path(uri.path, :node => request.node) - - return path - end end diff --git a/lib/puppet/indirector/module_files.rb b/lib/puppet/indirector/module_files.rb deleted file mode 100644 index fbf40aa70..000000000 --- a/lib/puppet/indirector/module_files.rb +++ /dev/null @@ -1,81 +0,0 @@ -# -# Created by Luke Kanies on 2007-10-19. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/util/uri_helper' -require 'puppet/indirector/terminus' -require 'puppet/file_serving/configuration' -require 'puppet/file_serving/fileset' -require 'puppet/file_serving/terminus_helper' - -# Look files up in Puppet modules. -class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus - include Puppet::Util::URIHelper - include Puppet::FileServing::TerminusHelper - - # Is the client allowed access to this key with this method? - def authorized?(request) - return false unless [:find, :search].include?(request.method) - - uri = key2uri(request.key) - - # Make sure our file path starts with /modules, so that we authorize - # against the 'modules' mount. - path = uri.path =~ /^modules\// ? uri.path : "modules/" + uri.path - - configuration.authorized?(path, :node => request.node, :ipaddress => request.ip) - end - - # Find our key in a module. - def find(request) - return nil unless path = find_path(request) - - result = model.new(path) - result.links = request.options[:links] if request.options[:links] - return result - end - - # Try to find our module. - def find_module(module_name, node_name) - environment(node_name).module(module_name) - end - - # Search for a list of files. - def search(request) - return nil unless path = find_path(request) - path2instances(request, path) - end - - private - - # Our fileserver configuration, if needed. - def configuration - Puppet::FileServing::Configuration.create - end - - # Determine the environment to use, if any. - def environment(node_name) - if node_name and node = Puppet::Node.find(node_name) - Puppet::Node::Environment.new(node.environment) - else - Puppet::Node::Environment.new - end - end - - # The abstracted method for turning a key into a path; used by both :find and :search. - def find_path(request) - uri = key2uri(request.key) - - # Strip off modules/ if it's there -- that's how requests get routed to this terminus. - module_name, relative_path = uri.path.sub(/^modules\//, '').sub(%r{^/}, '').split(File::Separator, 2) - - # And use the environment to look up the module. - return nil unless mod = find_module(module_name, request.node) - - path = File.join(mod.files, relative_path) - - return nil unless FileTest.exists?(path) - - return path - end -end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 54eb4bd97..45b40698b 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -1,172 +1,176 @@ # Support for modules class Puppet::Module TEMPLATES = "templates" FILES = "files" MANIFESTS = "manifests" PLUGINS = "plugins" FILETYPES = [MANIFESTS, FILES, TEMPLATES, PLUGINS] # Search through a list of paths, yielding each found module in turn. def self.each_module(*paths) paths = paths.flatten.collect { |p| p.split(File::PATH_SEPARATOR) }.flatten yielded = {} paths.each do |dir| next unless FileTest.directory?(dir) Dir.entries(dir).each do |name| next if name =~ /^\./ next if yielded.include?(name) module_path = File.join(dir, name) next unless FileTest.directory?(module_path) yielded[name] = true yield Puppet::Module.new(name, module_path) end end end # Return an array of paths by splitting the +modulepath+ config # parameter. Only consider paths that are absolute and existing # directories def self.modulepath(environment = nil) Puppet::Node::Environment.new(environment).modulepath end # Return an array of paths by splitting the +templatedir+ config # parameter. def self.templatepath(environment = nil) dirs = Puppet.settings.value(:templatedir, environment).split(":") dirs.select do |p| File::directory?(p) end end # Find and return the +module+ that +path+ belongs to. If +path+ is # absolute, or if there is no module whose name is the first component # of +path+, return +nil+ def self.find(modname, environment = nil) Puppet::Node::Environment.new(environment).module(modname) end # Instance methods # Find the concrete file denoted by +file+. If +file+ is absolute, # return it directly. Otherwise try to find it as a template in a # module. If that fails, return it relative to the +templatedir+ config # param. # In all cases, an absolute path is returned, which does not # necessarily refer to an existing file def self.find_template(template, environment = nil) if template =~ /^#{File::SEPARATOR}/ return template end if template_paths = templatepath(environment) # If we can find the template in :templatedir, we return that. td_file = template_paths.collect { |path| File::join(path, template) }.each do |f| return f if FileTest.exist?(f) end end # check in the default template dir, if there is one unless td_file = find_template_for_module(template, environment) raise Puppet::Error, "No valid template directory found, please check templatedir settings" if template_paths.nil? td_file = File::join(template_paths.first, template) end td_file end def self.find_template_for_module(template, environment = nil) path, file = split_path(template) # Because templates don't have an assumed template name, like manifests do, # we treat templates with no name as being templates in the main template # directory. return nil unless file if mod = find(path, environment) and t = mod.template(file) return t end nil end private_class_method :find_template_for_module # Return a list of manifests (as absolute filenames) that match +pat+ # with the current directory set to +cwd+. If the first component of # +pat+ does not contain any wildcards and is an existing module, return # a list of manifests in that module matching the rest of +pat+ # Otherwise, try to find manifests matching +pat+ relative to +cwd+ def self.find_manifests(start, options = {}) cwd = options[:cwd] || Dir.getwd module_name, pattern = split_path(start) if module_name and mod = find(module_name, options[:environment]) return mod.match_manifests(pattern) else abspat = File::expand_path(start, cwd) files = Dir.glob(abspat).reject { |f| FileTest.directory?(f) } if files.size == 0 files = Dir.glob(abspat + ".pp").reject { |f| FileTest.directory?(f) } end return files end end # Split the path into the module and the rest of the path. # This method can and often does return nil, so anyone calling # it needs to handle that. def self.split_path(path) if path =~ %r/^#{File::SEPARATOR}/ return nil end modname, rest = path.split(File::SEPARATOR, 2) return nil if modname.nil? || modname.empty? return modname, rest end attr_reader :name, :path def initialize(name, path) @name = name @path = path end FILETYPES.each do |type| # Create a method for returning the full path to a given # file type's directory. define_method(type.to_s) do File.join(path, type.to_s) end # Create a boolean method for testing whether our module has # files of a given type. define_method(type.to_s + "?") do FileTest.exist?(send(type)) end # Finally, a method for returning an individual file define_method(type.to_s.sub(/s$/, '')) do |file| - path = File.join(send(type), file) + if file + path = File.join(send(type), file) + else + path = send(type) + end return nil unless FileTest.exist?(path) return path end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.pp' for empty modules. def match_manifests(rest) rest ||= "init.pp" p = File::join(path, MANIFESTS, rest) files = Dir.glob(p).reject { |f| FileTest.directory?(f) } if files.size == 0 files = Dir.glob(p + ".pp") end return files end end diff --git a/spec/integration/file_serving/configuration.rb b/spec/integration/file_serving/configuration.rb deleted file mode 100755 index 06cf3abd9..000000000 --- a/spec/integration/file_serving/configuration.rb +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-10-18. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../spec_helper' - -require 'puppet/file_serving/configuration' - -describe Puppet::FileServing::Configuration, " when finding files with Puppet::FileServing::Mount" do - before do - # Just in case it already exists. - Puppet::Util::Cacher.expire - - @mount = Puppet::FileServing::Mount.new("mymount") - FileTest.stubs(:exists?).with("/my/path").returns(true) - FileTest.stubs(:readable?).with("/my/path").returns(true) - FileTest.stubs(:directory?).with("/my/path").returns(true) - @mount.path = "/my/path" - - FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true) - @parser = mock 'parser' - @parser.stubs(:parse).returns("mymount" => @mount) - @parser.stubs(:changed?).returns(true) - Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) - - @config = Puppet::FileServing::Configuration.create - end - - it "should return nil if the file does not exist" do - FileTest.expects(:exists?).with("/my/path/my/file").returns(false) - @config.file_path("mymount/my/file").should be_nil - end - - it "should return the full file path if the file exists" do - FileTest.expects(:exists?).with("/my/path/my/file").returns(true) - @config.file_path("mymount/my/file").should == "/my/path/my/file" - end - - after do - Puppet::Util::Cacher.expire - end -end diff --git a/spec/integration/indirector/direct_file_server.rb b/spec/integration/indirector/direct_file_server.rb index 1f1ea0dcc..7280cda98 100755 --- a/spec/integration/indirector/direct_file_server.rb +++ b/spec/integration/indirector/direct_file_server.rb @@ -1,74 +1,73 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-19. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/file_content/file' -require 'puppet/indirector/module_files' describe Puppet::Indirector::DirectFileServer, " when interacting with the filesystem and the model" do before do # We just test a subclass, since it's close enough. @terminus = Puppet::Indirector::FileContent::File.new @filepath = "/path/to/my/file" end it "should return an instance of the model" do FileTest.expects(:exists?).with(@filepath).returns(true) @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}")).should be_instance_of(Puppet::FileServing::Content) end it "should return an instance capable of returning its content" do FileTest.expects(:exists?).with(@filepath).returns(true) File.stubs(:lstat).with(@filepath).returns(stub("stat", :ftype => "file")) File.expects(:read).with(@filepath).returns("my content") instance = @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}")) instance.content.should == "my content" end end describe Puppet::Indirector::DirectFileServer, " when interacting with FileServing::Fileset and the model" do before do @terminus = Puppet::Indirector::FileContent::File.new @path = Tempfile.new("direct_file_server_testing") path = @path.path @path.close! @path = path Dir.mkdir(@path) File.open(File.join(@path, "one"), "w") { |f| f.print "one content" } File.open(File.join(@path, "two"), "w") { |f| f.print "two content" } @request = @terminus.indirection.request(:search, "file:///%s" % @path, :recurse => true) end after do system("rm -rf %s" % @path) end it "should return an instance for every file in the fileset" do result = @terminus.search(@request) result.should be_instance_of(Array) result.length.should == 3 result.each { |r| r.should be_instance_of(Puppet::FileServing::Content) } end it "should return instances capable of returning their content" do @terminus.search(@request).each do |instance| case instance.full_path when /one/: instance.content.should == "one content" when /two/: instance.content.should == "two content" when @path: else raise "No valid key for %s" % instance.path.inspect end end end end diff --git a/spec/integration/indirector/module_files.rb b/spec/integration/indirector/module_files.rb deleted file mode 100755 index 0eb1c9964..000000000 --- a/spec/integration/indirector/module_files.rb +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-10-19. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../spec_helper' - -require 'puppet/indirector/file_content/modules' -require 'puppet/indirector/module_files' - -describe Puppet::Indirector::ModuleFiles, " when interacting with Puppet::Module and FileServing::Content" do - it "should look for files in the module's 'files' directory" do - @environment = stub('env', :name => "myenv") - Puppet::Node::Environment.stubs(:new).returns(@environment) - # We just test a subclass, since it's close enough. - @terminus = Puppet::Indirector::FileContent::Modules.new - @module = Puppet::Module.new("mymod", "/some/path/mymod") - - @environment.expects(:module).with("mymod").returns @module - - filepath = "/some/path/mymod/files/myfile" - - FileTest.expects(:exists?).with(filepath).returns(true) - - @request = Puppet::Indirector::Request.new(:content, :find, "puppet://host/modules/mymod/myfile") - - @terminus.find(@request).should be_instance_of(Puppet::FileServing::Content) - end -end - -describe Puppet::Indirector::ModuleFiles, " when interacting with FileServing::Fileset and FileServing::Content" do - it "should return an instance for every file in the fileset" do - @environment = stub('env', :name => "myenv") - Puppet::Node::Environment.stubs(:new).returns @environment - @terminus = Puppet::Indirector::FileContent::Modules.new - - @path = Tempfile.new("module_file_testing") - path = @path.path - @path.close! - @path = path - - Dir.mkdir(@path) - Dir.mkdir(File.join(@path, "files")) - - basedir = File.join(@path, "files", "myfile") - Dir.mkdir(basedir) - - File.open(File.join(basedir, "one"), "w") { |f| f.print "one content" } - File.open(File.join(basedir, "two"), "w") { |f| f.print "two content" } - - @module = Puppet::Module.new("mymod", @path) - @environment.expects(:module).with("mymod").returns @module - - @request = Puppet::Indirector::Request.new(:content, :search, "puppet://host/modules/mymod/myfile", :recurse => true) - - result = @terminus.search(@request) - result.should be_instance_of(Array) - result.length.should == 3 - result.each { |r| r.should be_instance_of(Puppet::FileServing::Content) } - end -end diff --git a/spec/shared_behaviours/file_server_terminus.rb b/spec/shared_behaviours/file_server_terminus.rb index 1db6cfa0e..674651cbd 100644 --- a/spec/shared_behaviours/file_server_terminus.rb +++ b/spec/shared_behaviours/file_server_terminus.rb @@ -1,45 +1,45 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. describe "Puppet::Indirector::FileServerTerminus", :shared => true do # This only works if the shared behaviour is included before # the 'before' block in the including context. before do Puppet::Util::Cacher.expire FileTest.stubs(:exists?).returns true FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true) @path = Tempfile.new("file_server_testing") path = @path.path @path.close! @path = path Dir.mkdir(@path) File.open(File.join(@path, "myfile"), "w") { |f| f.print "my content" } # Use a real mount, so the integration is a bit deeper. - @mount1 = Puppet::FileServing::Configuration::Mount.new("one") + @mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") @mount1.path = @path @parser = stub 'parser', :changed? => false @parser.stubs(:parse).returns("one" => @mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) # Stub out the modules terminus @modules = mock 'modules terminus' @request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile") end it "should use the file server configuration to find files" do @modules.stubs(:find).returns(nil) @terminus.indirection.stubs(:terminus).with(:modules).returns(@modules) path = File.join(@path, "myfile") @terminus.find(@request).should be_instance_of(@test_class) end end diff --git a/spec/shared_behaviours/file_serving.rb b/spec/shared_behaviours/file_serving.rb index 99994b99a..8aad8885a 100644 --- a/spec/shared_behaviours/file_serving.rb +++ b/spec/shared_behaviours/file_serving.rb @@ -1,59 +1,60 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. describe "Puppet::FileServing::Files", :shared => true do it "should use the rest terminus when the 'puppet' URI scheme is used and a host name is present" do uri = "puppet://myhost/fakemod/my/file" # It appears that the mocking somehow interferes with the caching subsystem. # This mock somehow causes another terminus to get generated. term = @indirection.terminus(:rest) @indirection.stubs(:terminus).with(:rest).returns term term.expects(:find) @test_class.find(uri) end it "should use the rest terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is not 'puppet'" do uri = "puppet:///fakemod/my/file" Puppet.settings.stubs(:value).returns "foo" Puppet.settings.stubs(:value).with(:name).returns("puppetd") Puppet.settings.stubs(:value).with(:modulepath).returns("") @indirection.terminus(:rest).expects(:find) @test_class.find(uri) end it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'puppet'" do uri = "puppet:///fakemod/my/file" - Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing")) + Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil)) Puppet.settings.stubs(:value).returns "" Puppet.settings.stubs(:value).with(:name).returns("puppet") Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever") @indirection.terminus(:file_server).expects(:find) @indirection.terminus(:file_server).stubs(:authorized?).returns(true) @test_class.find(uri) end it "should use the file terminus when the 'file' URI scheme is used" do uri = "file:///fakemod/my/file" @indirection.terminus(:file).expects(:find) @test_class.find(uri) end it "should use the file terminus when a fully qualified path is provided" do uri = "/fakemod/my/file" @indirection.terminus(:file).expects(:find) @test_class.find(uri) end it "should use the configuration to test whether the request is allowed" do uri = "fakemod/my/file" - config = mock 'configuration' + mount = mock 'mount' + config = stub 'configuration', :split_path => [mount, "eh"] @indirection.terminus(:file_server).stubs(:configuration).returns config @indirection.terminus(:file_server).expects(:find) - config.expects(:authorized?).returns(true) + mount.expects(:allowed?).returns(true) @test_class.find(uri, :node => "foo", :ip => "bar") end end diff --git a/spec/unit/file_serving/configuration.rb b/spec/unit/file_serving/configuration.rb index 21fdfe3b4..c2d2e5014 100755 --- a/spec/unit/file_serving/configuration.rb +++ b/spec/unit/file_serving/configuration.rb @@ -1,229 +1,208 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/configuration' describe Puppet::FileServing::Configuration do it "should make :new a private method" do proc { Puppet::FileServing::Configuration.new }.should raise_error end it "should return the same configuration each time :create is called" do Puppet::FileServing::Configuration.create.should equal(Puppet::FileServing::Configuration.create) end it "should have a method for removing the current configuration instance" do old = Puppet::FileServing::Configuration.create Puppet::Util::Cacher.expire Puppet::FileServing::Configuration.create.should_not equal(old) end after do Puppet::Util::Cacher.expire end end describe Puppet::FileServing::Configuration do before :each do @path = "/path/to/configuration/file.conf" + Puppet.settings.stubs(:value).with(:trace).returns(false) Puppet.settings.stubs(:value).with(:fileserverconfig).returns(@path) end after :each do Puppet::Util::Cacher.expire end - describe Puppet::FileServing::Configuration, "when initializing" do + describe "when initializing" do it "should work without a configuration file" do FileTest.stubs(:exists?).with(@path).returns(false) proc { Puppet::FileServing::Configuration.create }.should_not raise_error end it "should parse the configuration file if present" do FileTest.stubs(:exists?).with(@path).returns(true) @parser = mock 'parser' @parser.expects(:parse).returns({}) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) Puppet::FileServing::Configuration.create end it "should determine the path to the configuration file from the Puppet settings" do Puppet::FileServing::Configuration.create end end - describe Puppet::FileServing::Configuration, "when parsing the configuration file" do + describe "when parsing the configuration file" do before do FileTest.stubs(:exists?).with(@path).returns(true) @parser = mock 'parser' Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) end it "should set the mount list to the results of parsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.create config.mounted?("one").should be_true end it "should not raise exceptions" do @parser.expects(:parse).raises(ArgumentError) proc { Puppet::FileServing::Configuration.create }.should_not raise_error end it "should replace the existing mount list with the results of reparsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.create config.mounted?("one").should be_true # Now parse again @parser.expects(:parse).returns("two" => mock('other')) config.send(:readconfig, false) config.mounted?("one").should be_false config.mounted?("two").should be_true end it "should not replace the mount list until the file is entirely parsed successfully" do @parser.expects(:parse).returns("one" => mock("mount")) @parser.expects(:parse).raises(ArgumentError) config = Puppet::FileServing::Configuration.create # Now parse again, so the exception gets thrown config.send(:readconfig, false) config.mounted?("one").should be_true end end - describe Puppet::FileServing::Configuration, "when finding files" do - - before do - @parser = mock 'parser' - @parser.stubs(:changed?).returns true - FileTest.stubs(:exists?).with(@path).returns(true) - Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) + describe "when finding the specified mount" do + it "should choose the named mount if one exists" do + config = Puppet::FileServing::Configuration.create + config.expects(:mounts).returns("one" => "foo") + config.find_mount("one", "mynode").should == "foo" + end - @mount1 = stub 'mount', :name => "one" - @mounts = {"one" => @mount1} + it "should modules mount's environment to find a matching module if the named module cannot be found" do + config = Puppet::FileServing::Configuration.create - Facter.stubs(:value).with("hostname").returns("whatever") + mod = mock 'module' + env = mock 'environment' + env.expects(:module).with("foo").returns mod + mount = mock 'mount' + mount.expects(:environment).with("mynode").returns env - @config = Puppet::FileServing::Configuration.create + config.stubs(:mounts).returns("modules" => mount) + config.find_mount("foo", "mynode").should equal(mount) end - it "should fail if the uri has a leading slash" do - @parser.expects(:parse).returns(@mounts) - proc { @config.file_path("/something") }.should raise_error(ArgumentError) - end + it "should return nil if there is no such named mount and no module with the same name exists" do + config = Puppet::FileServing::Configuration.create - it "should fail if the uri does not start with a valid mount name" do - @parser.expects(:parse).returns(@mounts) - proc { @config.file_path("some.thing") }.should raise_error(ArgumentError) - end + env = mock 'environment' + env.expects(:module).with("foo").returns nil + mount = mock 'mount' + mount.expects(:environment).with("mynode").returns env - it "should use the first term after the first slash for the mount name" do - @parser.expects(:parse).returns(@mounts) - FileTest.stubs(:exists?).returns(true) - @mount1.expects(:file) - @config.file_path("one") + config.stubs(:mounts).returns("modules" => mount) + config.find_mount("foo", "mynode").should be_nil end + end - it "should use the remainder of the URI after the mount name as the file name" do - @parser.expects(:parse).returns(@mounts) - @mount1.expects(:file).with("something/else", {}) - FileTest.stubs(:exists?).returns(true) - @config.file_path("one/something/else") - end + describe "when finding the mount name and relative path in a request key" do + before do + @config = Puppet::FileServing::Configuration.create + @config.stubs(:find_mount) - it "should treat a bare name as a mount and no relative file" do - @parser.expects(:parse).returns(@mounts) - @mount1.expects(:file).with(nil, {}) - FileTest.stubs(:exists?).returns(true) - @config.file_path("one") + @request = stub 'request', :key => "puppet:///foo/bar/baz", :options => {} end - it "should treat a name with a trailing slash equivalently to a name with no trailing slash" do - @parser.expects(:parse).returns(@mounts) - @mount1.expects(:file).with(nil, {}) - FileTest.stubs(:exists?).returns(true) - @config.file_path("one/") - end + it "should reread the configuration" do + @config.expects(:readconfig) - it "should return nil if the mount cannot be found" do - @parser.expects(:changed?).returns(true) - @parser.expects(:parse).returns({}) - @config.file_path("one/something").should be_nil + @config.split_path(@request) end - it "should return nil if the mount does not contain the file" do - @parser.expects(:parse).returns(@mounts) - @mount1.expects(:file).with("something/else", {}).returns(nil) - @config.file_path("one/something/else").should be_nil - end + it "should treat the first field of the URI path as the mount name" do + @config.expects(:find_mount).with { |name, node| name == "foo" } - it "should return the fully qualified path if the mount exists" do - @parser.expects(:parse).returns(@mounts) - @mount1.expects(:file).with("something/else", {}).returns("/full/path") - @config.file_path("one/something/else").should == "/full/path" + @config.split_path(@request) end - it "should reparse the configuration file when it has changed" do - @mount1.stubs(:file).returns("whatever") - @parser.expects(:changed?).returns(true) - @parser.expects(:parse).returns(@mounts) - FileTest.stubs(:exists?).returns(true) - @config.file_path("one/something") + it "should fail if the mount name is not alpha-numeric" do + @request.expects(:key).returns "puppet:///foo&bar/asdf" - @parser.expects(:changed?).returns(true) - @parser.expects(:parse).returns({}) - @config.file_path("one/something").should be_nil + lambda { @config.split_path(@request) }.should raise_error(ArgumentError) end - end - - describe Puppet::FileServing::Configuration, "when checking authorization" do - before do - @parser = mock 'parser' - @parser.stubs(:changed?).returns true - FileTest.stubs(:exists?).with(@path).returns(true) - Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) + it "should support dashes in the mount name" do + @request.expects(:key).returns "puppet:///foo-bar/asdf" - @mount1 = stub 'mount', :name => "one" - @mounts = {"one" => @mount1} - @parser.stubs(:parse).returns(@mounts) + lambda { @config.split_path(@request) }.should_not raise_error(ArgumentError) + end - Facter.stubs(:value).with("hostname").returns("whatever") + it "should use the mount name and node to find the mount" do + @config.expects(:find_mount).with { |name, node| name == "foo" and node == "mynode" } + @request.options[:node] = "mynode" - @config = Puppet::FileServing::Configuration.create + @config.split_path(@request) end - it "should return false if the mount cannot be found" do - @config.authorized?("nope/my/file").should be_false - end + it "should return nil if the mount cannot be found" do + @config.expects(:find_mount).returns nil - it "should use the mount to determine authorization" do - @mount1.expects(:allowed?) - @config.authorized?("one/my/file") + @config.split_path(@request).should be_nil end - it "should pass the client's name to the mount if provided" do - @mount1.expects(:allowed?).with("myhost", nil) - @config.authorized?("one/my/file", :node => "myhost") + it "should return the mount and the relative path if the mount is found" do + mount = stub 'mount', :name => "foo" + @config.expects(:find_mount).returns mount + + @config.split_path(@request).should == [mount, "bar/baz"] end - it "should pass the client's IP to the mount if provided" do - @mount1.expects(:allowed?).with("myhost", "myip") - @config.authorized?("one/my/file", :node => "myhost", :ipaddress => "myip") + it "should remove any double slashes" do + @request.stubs(:key).returns "puppet:///foo/bar//baz" + mount = stub 'mount', :name => "foo" + @config.expects(:find_mount).returns mount + + @config.split_path(@request).should == [mount, "bar/baz"] end - it "should return true if the mount allows the client" do - @mount1.expects(:allowed?).returns(true) - @config.authorized?("one/my/file").should be_true + it "should return the relative path as nil if it is an empty string" do + @request.expects(:key).returns "puppet:///foo" + mount = stub 'mount', :name => "foo" + @config.expects(:find_mount).returns mount + + @config.split_path(@request).should == [mount, nil] end - it "should return false if the mount denies the client" do - @mount1.expects(:allowed?).returns(false) - @config.authorized?("one/my/file").should be_false + it "should add 'modules/' to the relative path if the modules mount is used but not specified, for backward compatibility" do + @request.expects(:key).returns "puppet:///foo/bar" + mount = stub 'mount', :name => "modules" + @config.expects(:find_mount).returns mount + + @config.split_path(@request).should == [mount, "foo/bar"] end end end diff --git a/spec/unit/file_serving/configuration/parser.rb b/spec/unit/file_serving/configuration/parser.rb index 93d30ca1c..389e58389 100755 --- a/spec/unit/file_serving/configuration/parser.rb +++ b/spec/unit/file_serving/configuration/parser.rb @@ -1,135 +1,193 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/file_serving/configuration/parser' describe Puppet::FileServing::Configuration::Parser do it "should subclass the LoadedFile class" do Puppet::FileServing::Configuration::Parser.superclass.should equal(Puppet::Util::LoadedFile) end end module FSConfigurationParserTesting def mock_file_content(content) # We want an array, but we actually want our carriage returns on all of it. lines = content.split("\n").collect { |l| l + "\n" } @filehandle.stubs(:each).multiple_yields(*lines) end end describe Puppet::FileServing::Configuration::Parser do before :each do @path = "/my/config.conf" FileTest.stubs(:exists?).with(@path).returns(true) FileTest.stubs(:readable?).with(@path).returns(true) @filehandle = mock 'filehandle' File.expects(:open).with(@path).yields(@filehandle) @parser = Puppet::FileServing::Configuration::Parser.new(@path) end describe Puppet::FileServing::Configuration::Parser, " when parsing" do include FSConfigurationParserTesting before do @parser.stubs(:add_modules_mount) end it "should allow comments" do @filehandle.expects(:each).yields("# this is a comment\n") proc { @parser.parse }.should_not raise_error end it "should allow blank lines" do @filehandle.expects(:each).yields("\n") proc { @parser.parse }.should_not raise_error end it "should create a new mount for each section in the configuration" do - mount1 = mock 'one' - mount2 = mock 'two' - Puppet::FileServing::Mount.expects(:new).with("one").returns(mount1) - Puppet::FileServing::Mount.expects(:new).with("two").returns(mount2) + mount1 = mock 'one', :validate => true + mount2 = mock 'two', :validate => true + Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) + Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" @parser.parse end # This test is almost the exact same as the previous one. it "should return a hash of the created mounts" do - mount1 = mock 'one' - mount2 = mock 'two' - Puppet::FileServing::Mount.expects(:new).with("one").returns(mount1) - Puppet::FileServing::Mount.expects(:new).with("two").returns(mount2) + mount1 = mock 'one', :validate => true + mount2 = mock 'two', :validate => true + Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) + Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" - @parser.parse.should == {"one" => mount1, "two" => mount2} + result = @parser.parse + result["one"].should equal(mount1) + result["two"].should equal(mount2) + end + + it "should add plugins and modules mounts if they do not exist" do + mock_file_content "[one]\npath /foo" + + result = @parser.parse + result["plugins"].should_not be_nil + result["modules"].should_not be_nil end it "should only allow mount names that are alphanumeric plus dashes" do mock_file_content "[a*b]\n" proc { @parser.parse }.should raise_error(ArgumentError) end it "should fail if the value for path/allow/deny starts with an equals sign" do mock_file_content "[one]\npath = /testing" proc { @parser.parse }.should raise_error(ArgumentError) end + + it "should validate each created mount" do + mount1 = mock 'one' + Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) + mock_file_content "[one]\n" + + mount1.expects(:validate) + + @parser.parse + end + + it "should fail if any mount does not pass validation" do + mount1 = mock 'one' + Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) + mock_file_content "[one]\n" + + mount1.expects(:validate).raises RuntimeError + + lambda { @parser.parse }.should raise_error(RuntimeError) + end end describe Puppet::FileServing::Configuration::Parser, " when parsing mount attributes" do include FSConfigurationParserTesting before do - @mount = stub 'mount', :name => "one" - Puppet::FileServing::Mount.expects(:new).with("one").returns(@mount) + @mount = stub 'testmount', :name => "one", :validate => true + Puppet::FileServing::Mount::File.expects(:new).with("one").returns(@mount) @parser.stubs(:add_modules_mount) end it "should set the mount path to the path attribute from that section" do mock_file_content "[one]\npath /some/path\n" @mount.expects(:path=).with("/some/path") @parser.parse end it "should tell the mount to allow any allow values from the section" do mock_file_content "[one]\nallow something\n" @mount.expects(:info) @mount.expects(:allow).with("something") @parser.parse end it "should tell the mount to deny any deny values from the section" do mock_file_content "[one]\ndeny something\n" @mount.expects(:info) @mount.expects(:deny).with("something") @parser.parse end it "should fail on any attributes other than path, allow, and deny" do mock_file_content "[one]\ndo something\n" proc { @parser.parse }.should raise_error(ArgumentError) end end describe Puppet::FileServing::Configuration::Parser, " when parsing the modules mount" do include FSConfigurationParserTesting before do - @mount = stub 'mount', :name => "modules" - Puppet::FileServing::Mount.expects(:new).with("modules").returns(@mount) + @mount = stub 'modulesmount', :name => "modules", :validate => true + end + + it "should create an instance of the Modules Mount class" do + mock_file_content "[modules]\n" + + Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns @mount + @parser.parse end it "should warn if a path is set" do mock_file_content "[modules]\npath /some/path\n" + Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns(@mount) + + Puppet.expects(:warning) + @parser.parse + end + end + + describe Puppet::FileServing::Configuration::Parser, " when parsing the plugins mount" do + include FSConfigurationParserTesting + + before do + @mount = stub 'pluginsmount', :name => "plugins", :validate => true + end + + it "should create an instance of the Plugins Mount class" do + mock_file_content "[plugins]\n" + + Puppet::FileServing::Mount::Plugins.expects(:new).with("plugins").returns @mount + @parser.parse + end + + it "should warn if a path is set" do + mock_file_content "[plugins]\npath /some/path\n" - @modules.expects(:path=).never Puppet.expects(:warning) @parser.parse end end end diff --git a/spec/unit/file_serving/indirection_hooks.rb b/spec/unit/file_serving/indirection_hooks.rb index 83ff5848f..fee504bb6 100755 --- a/spec/unit/file_serving/indirection_hooks.rb +++ b/spec/unit/file_serving/indirection_hooks.rb @@ -1,124 +1,63 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/indirection_hooks' describe Puppet::FileServing::IndirectionHooks do before do @object = Object.new @object.extend(Puppet::FileServing::IndirectionHooks) @request = stub 'request', :key => "mymod/myfile", :options => {:node => "whatever"}, :server => nil, :protocol => nil end describe "when being used to select termini" do it "should return :file if the request key is fully qualified" do @request.expects(:key).returns "#{File::SEPARATOR}foo" @object.select_terminus(@request).should == :file end it "should return :file if the URI protocol is set to 'file'" do @request.expects(:protocol).returns "file" @object.select_terminus(@request).should == :file end it "should fail when a protocol other than :puppet or :file is used" do @request.stubs(:protocol).returns "http" proc { @object.select_terminus(@request) }.should raise_error(ArgumentError) end describe "and the protocol is 'puppet'" do before do @request.stubs(:protocol).returns "puppet" end it "should choose :rest when a server is specified" do @request.stubs(:protocol).returns "puppet" @request.expects(:server).returns "foo" @object.select_terminus(@request).should == :rest end # This is so a given file location works when bootstrapping with no server. it "should choose :rest when the Settings name isn't 'puppet'" do @request.stubs(:protocol).returns "puppet" @request.stubs(:server).returns "foo" Puppet.settings.stubs(:value).with(:name).returns "foo" @object.select_terminus(@request).should == :rest end - it "should not choose :rest when the settings name is 'puppet' and no server is specified" do + it "should choose :file_server when the settings name is 'puppet' and no server is specified" do modules = mock 'modules' - @object.stubs(:terminus).with(:modules).returns(modules) - modules.stubs(:find_module).with("mymod", @request.options[:node]).returns nil - @request.expects(:protocol).returns "puppet" @request.expects(:server).returns nil Puppet.settings.expects(:value).with(:name).returns "puppet" - @object.select_terminus(@request).should_not == :rest - end - end - - describe "and the terminus is not :rest or :file" do - before do - @request.stubs(:protocol).returns nil - end - - it "should choose :modules if the mount name is 'modules'" do - @request.stubs(:key).returns "modules/mymod/file" - @object.select_terminus(@request).should == :modules - end - - it "should choose :modules and provide a deprecation notice if a module exists with the mount name" do - modules = mock 'modules' - - @object.expects(:terminus).with(:modules).returns(modules) - modules.expects(:find_module).with("mymod", @request.options[:node]).returns(:thing) - - Puppet.expects(:warning) - - @request.stubs(:key).returns "mymod/file" - @object.select_terminus(@request).should == :modules - end - - it "should choose :file_server if the mount name is not 'modules' nor matches a module name" do - modules = mock 'modules' - @object.stubs(:terminus).with(:modules).returns(modules) - modules.stubs(:find_module).returns(nil) - - @request.stubs(:key).returns "notmodules/file" - @object.select_terminus(@request).should == :file_server end end - - describe "when looking for a module whose name matches the mount name" do - before do - @modules = mock 'modules' - @object.stubs(:terminus).with(:modules).returns(@modules) - - @request.stubs(:key).returns "mymod/file" - end - - it "should use the modules terminus to look up the module" do - @modules.expects(:find_module).with("mymod", @request.options[:node]) - @object.select_terminus @request - end - - it "should pass the node name to the modules terminus" do - @modules.expects(:find_module).with("mymod", @request.options[:node]) - @object.select_terminus @request - end - - it "should log a deprecation warning if a module is found" do - @modules.expects(:find_module).with("mymod", @request.options[:node]).returns(:something) - Puppet.expects(:warning) - @object.select_terminus @request - end - end end end diff --git a/spec/unit/file_serving/mount.rb b/spec/unit/file_serving/mount.rb index 42f5accb9..e9e1f0860 100755 --- a/spec/unit/file_serving/mount.rb +++ b/spec/unit/file_serving/mount.rb @@ -1,143 +1,50 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/mount' -module FileServingMountTesting - def stub_facter(hostname) - Facter.stubs(:value).with("hostname").returns(hostname.sub(/\..+/, '')) - Facter.stubs(:value).with("domain").returns(hostname.sub(/^[^.]+\./, '')) - end -end - describe Puppet::FileServing::Mount do - it "should provide a method for clearing its cached host information" do - old = Puppet::FileServing::Mount.localmap - Puppet::Util::Cacher.expire - Puppet::FileServing::Mount.localmap.should_not equal(old) - end -end - -describe Puppet::FileServing::Mount, " when initializing" do - it "should fail on non-alphanumeric name" do - proc { Puppet::FileServing::Mount.new("non alpha") }.should raise_error(ArgumentError) - end - - it "should allow dashes in its name" do - Puppet::FileServing::Mount.new("non-alpha").name.should == "non-alpha" - end - - it "should allow an optional path" do - Puppet::FileServing::Mount.new("name", "/").path.should == "/" - end -end - -describe Puppet::FileServing::Mount, " when setting the path" do - before do - @mount = Puppet::FileServing::Mount.new("test") - @dir = "/this/path/does/not/exist" - end - - it "should fail if the path is not a directory" do - FileTest.expects(:directory?).returns(false) - proc { @mount.path = @dir }.should raise_error(ArgumentError) - end - - it "should fail if the path is not readable" do - FileTest.expects(:directory?).returns(true) - FileTest.expects(:readable?).returns(false) - proc { @mount.path = @dir }.should raise_error(ArgumentError) - end -end - -describe Puppet::FileServing::Mount, " when finding files" do - include FileServingMountTesting - before do - FileTest.stubs(:directory?).returns(true) - FileTest.stubs(:readable?).returns(true) - @mount = Puppet::FileServing::Mount.new("test") - @host = "host.domain.com" - end - - it "should fail if the mount path has not been set" do - proc { @mount.file_path("/blah") }.should raise_error(ArgumentError) + @mount = Puppet::FileServing::Mount.new("foo") end - it "should replace incidences of %h in the path with the client's short name" do - @mount.path = "/dir/%h/yay" - @mount.path(@host).should == "/dir/host/yay" - end - - it "should replace incidences of %H in the path with the client's fully qualified name" do - @mount.path = "/dir/%H/yay" - @mount.path(@host).should == "/dir/host.domain.com/yay" - end - - it "should replace incidences of %d in the path with the client's domain name" do - @mount.path = "/dir/%d/yay" - @mount.path(@host).should == "/dir/domain.com/yay" - end - - it "should perform all necessary replacements" do - @mount.path = "/%h/%d/%H" - @mount.path(@host).should == "/host/domain.com/host.domain.com" - end - - it "should perform replacements on the base path" do - @mount.path = "/blah/%h" - @mount.file_path("/my/stuff", @host).should == "/blah/host/my/stuff" - end + it "should be able to look up a node's environment" do + Puppet::Node.expects(:find).with("mynode").returns mock('node', :environment => "myenv") + Puppet::Node::Environment.expects(:new).with("myenv").returns "eh" - it "should not perform replacements on the per-file path" do - @mount.path = "/blah" - @mount.file_path("/%h/stuff", @host).should == "/blah/%h/stuff" + @mount.environment("mynode").should == "eh" end - it "should look for files relative to its base directory" do - @mount.path = "/blah" - @mount.file_path("/my/stuff", @host).should == "/blah/my/stuff" - end + it "should use the default environment if no node information is provided" do + Puppet::Node.expects(:find).with("mynode").returns nil + Puppet::Node::Environment.expects(:new).with(nil).returns "eh" - it "should use local host information if no client data is provided" do - stub_facter("myhost.mydomain.com") - @mount.path = "/%h/%d/%H" - @mount.path().should == "/myhost/mydomain.com/myhost.mydomain.com" + @mount.environment("mynode").should == "eh" end - after do - Puppet::Util::Cacher.expire + it "should use 'mount[$name]' as its string form" do + @mount.to_s.should == "mount[foo]" end end -describe Puppet::FileServing::Mount, " when providing file paths" do - include FileServingMountTesting - - before do - FileTest.stubs(:exists?).returns(true) - FileTest.stubs(:directory?).returns(true) - FileTest.stubs(:readable?).returns(true) - @mount = Puppet::FileServing::Mount.new("test", "/mount/%h") - stub_facter("myhost.mydomain.com") - @host = "host.domain.com" - end - - it "should return nil if the file is absent" do - FileTest.stubs(:exists?).returns(false) - @mount.file("/my/path").should be_nil +describe Puppet::FileServing::Mount, " when initializing" do + it "should fail on non-alphanumeric name" do + proc { Puppet::FileServing::Mount.new("non alpha") }.should raise_error(ArgumentError) end - it "should return the file path if the file is absent" do - FileTest.stubs(:exists?).with("/my/path").returns(true) - @mount.file("/my/path").should == "/mount/myhost/my/path" + it "should allow dashes in its name" do + Puppet::FileServing::Mount.new("non-alpha").name.should == "non-alpha" end +end - it "should treat a nil file name as the path to the mount itself" do - FileTest.stubs(:exists?).returns(true) - @mount.file(nil).should == "/mount/myhost" +describe Puppet::FileServing::Mount, " when finding files" do + it "should fail" do + lambda { Puppet::FileServing::Mount.new("test").find("foo", :one => "two") }.should raise_error(NotImplementedError) end +end - it "should use the client host name if provided in the options" do - @mount.file("/my/path", :node => @host).should == "/mount/host/my/path" +describe Puppet::FileServing::Mount, " when searching for files" do + it "should fail" do + lambda { Puppet::FileServing::Mount.new("test").search("foo", :one => "two") }.should raise_error(NotImplementedError) end end diff --git a/spec/unit/file_serving/mount/file.rb b/spec/unit/file_serving/mount/file.rb new file mode 100755 index 000000000..499a0357d --- /dev/null +++ b/spec/unit/file_serving/mount/file.rb @@ -0,0 +1,184 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/file_serving/mount/file' + +module FileServingMountTesting + def stub_facter(hostname) + Facter.stubs(:value).with("hostname").returns(hostname.sub(/\..+/, '')) + Facter.stubs(:value).with("domain").returns(hostname.sub(/^[^.]+\./, '')) + end +end + +describe Puppet::FileServing::Mount::File do + it "should provide a method for clearing its cached host information" do + old = Puppet::FileServing::Mount::File.localmap + Puppet::Util::Cacher.expire + Puppet::FileServing::Mount::File.localmap.should_not equal(old) + end + + it "should be invalid if it does not have a path" do + lambda { Puppet::FileServing::Mount::File.new("foo").validate }.should raise_error(ArgumentError) + end + + it "should be valid if it has a path" do + FileTest.stubs(:directory?).returns true + FileTest.stubs(:readable?).returns true + mount = Puppet::FileServing::Mount::File.new("foo") + mount.path = "/foo" + lambda { mount.validate }.should_not raise_error(ArgumentError) + end +end + +describe Puppet::FileServing::Mount::File, " when setting the path" do + before do + @mount = Puppet::FileServing::Mount::File.new("test") + @dir = "/this/path/does/not/exist" + end + + it "should fail if the path is not a directory" do + FileTest.expects(:directory?).returns(false) + proc { @mount.path = @dir }.should raise_error(ArgumentError) + end + + it "should fail if the path is not readable" do + FileTest.expects(:directory?).returns(true) + FileTest.expects(:readable?).returns(false) + proc { @mount.path = @dir }.should raise_error(ArgumentError) + end +end + +describe Puppet::FileServing::Mount::File, " when substituting hostnames and ip addresses into file paths" do + include FileServingMountTesting + + before do + FileTest.stubs(:directory?).returns(true) + FileTest.stubs(:readable?).returns(true) + @mount = Puppet::FileServing::Mount::File.new("test") + @host = "host.domain.com" + end + + it "should replace incidences of %h in the path with the client's short name" do + @mount.path = "/dir/%h/yay" + @mount.path(@host).should == "/dir/host/yay" + end + + it "should replace incidences of %H in the path with the client's fully qualified name" do + @mount.path = "/dir/%H/yay" + @mount.path(@host).should == "/dir/host.domain.com/yay" + end + + it "should replace incidences of %d in the path with the client's domain name" do + @mount.path = "/dir/%d/yay" + @mount.path(@host).should == "/dir/domain.com/yay" + end + + it "should perform all necessary replacements" do + @mount.path = "/%h/%d/%H" + @mount.path(@host).should == "/host/domain.com/host.domain.com" + end + + it "should use local host information if no client data is provided" do + stub_facter("myhost.mydomain.com") + @mount.path = "/%h/%d/%H" + @mount.path().should == "/myhost/mydomain.com/myhost.mydomain.com" + end + + after do + Puppet::Util::Cacher.expire + end +end + +describe Puppet::FileServing::Mount::File, "when determining the complete file path" do + include FileServingMountTesting + + before do + FileTest.stubs(:exist?).returns(true) + FileTest.stubs(:directory?).returns(true) + FileTest.stubs(:readable?).returns(true) + @mount = Puppet::FileServing::Mount::File.new("test") + @mount.path = "/mount" + stub_facter("myhost.mydomain.com") + @host = "host.domain.com" + end + + it "should return nil if the file is absent" do + FileTest.stubs(:exist?).returns(false) + @mount.complete_path("/my/path").should be_nil + end + + it "should return the file path if the file is present" do + FileTest.stubs(:exist?).with("/my/path").returns(true) + @mount.complete_path("/my/path").should == "/mount/my/path" + end + + it "should treat a nil file name as the path to the mount itself" do + FileTest.stubs(:exist?).returns(true) + @mount.complete_path(nil).should == "/mount" + end + + it "should use the client host name if provided in the options" do + @mount.path = "/mount/%h" + @mount.complete_path("/my/path", @host).should == "/mount/host/my/path" + end + + it "should perform replacements on the base path" do + @mount.path = "/blah/%h" + @mount.complete_path("/my/stuff", @host).should == "/blah/host/my/stuff" + end + + it "should not perform replacements on the per-file path" do + @mount.path = "/blah" + @mount.complete_path("/%h/stuff", @host).should == "/blah/%h/stuff" + end + + it "should look for files relative to its base directory" do + @mount.complete_path("/my/stuff", @host).should == "/mount/my/stuff" + end +end + +describe Puppet::FileServing::Mount::File, "when finding files" do + include FileServingMountTesting + + before do + FileTest.stubs(:exist?).returns(true) + FileTest.stubs(:directory?).returns(true) + FileTest.stubs(:readable?).returns(true) + @mount = Puppet::FileServing::Mount::File.new("test") + @mount.path = "/mount" + stub_facter("myhost.mydomain.com") + @host = "host.domain.com" + end + + it "should return the results of the complete file path" do + FileTest.stubs(:exist?).returns(false) + @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" + @mount.find("/my/path", :node => "foo").should == "eh" + end +end + +describe Puppet::FileServing::Mount::File, "when searching for files" do + include FileServingMountTesting + + before do + FileTest.stubs(:exist?).returns(true) + FileTest.stubs(:directory?).returns(true) + FileTest.stubs(:readable?).returns(true) + @mount = Puppet::FileServing::Mount::File.new("test") + @mount.path = "/mount" + stub_facter("myhost.mydomain.com") + @host = "host.domain.com" + end + + it "should return the results of the complete file path as an array" do + FileTest.stubs(:exist?).returns(false) + @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" + @mount.search("/my/path", :node => "foo").should == ["eh"] + end + + it "should return nil if the complete path is nil" do + FileTest.stubs(:exist?).returns(false) + @mount.expects(:complete_path).with("/my/path", "foo").returns nil + @mount.search("/my/path", :node => "foo").should be_nil + end +end diff --git a/spec/unit/file_serving/mount/modules.rb b/spec/unit/file_serving/mount/modules.rb new file mode 100755 index 000000000..6861e94a5 --- /dev/null +++ b/spec/unit/file_serving/mount/modules.rb @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/file_serving/mount/modules' + +describe Puppet::FileServing::Mount::Modules, "when finding files" do + before do + @mount = Puppet::FileServing::Mount::Modules.new("modules") + + @environment = stub 'environment', :module => nil + @mount.stubs(:environment).returns @environment + end + + it "should use the node's environment to find the module" do + env = mock 'env' + @mount.expects(:environment).with("mynode").returns env + env.expects(:module) + + @mount.find("foo", :node => "mynode") + end + + it "should treat the first field of the relative path as the module name" do + @environment.expects(:module).with("foo") + @mount.find("foo/bar/baz") + end + + it "should return nil if the specified module does not exist" do + @environment.expects(:module).with("foo").returns nil + @mount.find("foo/bar/baz") + end + + it "should return the file path from the module" do + mod = mock 'module' + mod.expects(:file).with("bar/baz").returns "eh" + @environment.expects(:module).with("foo").returns mod + @mount.find("foo/bar/baz").should == "eh" + end +end + +describe Puppet::FileServing::Mount::Modules, "when searching for files" do + before do + @mount = Puppet::FileServing::Mount::Modules.new("modules") + + @environment = stub 'environment', :module => nil + @mount.stubs(:environment).returns @environment + end + + it "should use the node's environment to search the module" do + env = mock 'env' + @mount.expects(:environment).with("mynode").returns env + env.expects(:module) + + @mount.search("foo", :node => "mynode") + end + + it "should treat the first field of the relative path as the module name" do + @environment.expects(:module).with("foo") + @mount.search("foo/bar/baz") + end + + it "should return nil if the specified module does not exist" do + @environment.expects(:module).with("foo").returns nil + @mount.search("foo/bar/baz") + end + + it "should return the file path as an array from the module" do + mod = mock 'module' + mod.expects(:file).with("bar/baz").returns "eh" + @environment.expects(:module).with("foo").returns mod + @mount.search("foo/bar/baz").should == ["eh"] + end +end diff --git a/spec/unit/file_serving/mount/plugins.rb b/spec/unit/file_serving/mount/plugins.rb new file mode 100755 index 000000000..c658b8cd6 --- /dev/null +++ b/spec/unit/file_serving/mount/plugins.rb @@ -0,0 +1,70 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/file_serving/mount/plugins' + +describe Puppet::FileServing::Mount::Plugins, "when finding files" do + before do + @mount = Puppet::FileServing::Mount::Plugins.new("modules") + + @environment = stub 'environment', :module => nil + @mount.stubs(:environment).returns @environment + end + + it "should use the node's environment to find the modules" do + env = mock 'env' + @mount.expects(:environment).with("mynode").returns env + env.expects(:modules).returns [] + + @mount.find("foo", :node => "mynode") + end + + it "should return nil if no module can be found with a matching plugin" do + mod = mock 'module' + mod.stubs(:plugin).with("foo/bar").returns nil + + @environment.expects(:modules).returns [mod] + @mount.find("foo/bar").should be_nil + end + + it "should return the file path from the module" do + mod = mock 'module' + mod.stubs(:plugin).with("foo/bar").returns "eh" + + @environment.expects(:modules).returns [mod] + @mount.find("foo/bar").should == "eh" + end +end + +describe Puppet::FileServing::Mount::Plugins, "when searching for files" do + before do + @mount = Puppet::FileServing::Mount::Plugins.new("modules") + + @environment = stub 'environment', :module => nil + @mount.stubs(:environment).returns @environment + end + + it "should use the node's environment to find the modules" do + env = mock 'env' + @mount.expects(:environment).with("mynode").returns env + env.expects(:modules).returns [] + + @mount.search("foo", :node => "mynode") + end + + it "should return nil if no modules can be found that have plugins" do + mod = mock 'module' + mod.stubs(:plugins?).returns false + + @environment.expects(:modules).returns [mod] + @mount.search("foo/bar").should be_nil + end + + it "should return the plugin paths for each module that has plugins" do + one = stub 'module', :plugins? => true, :plugins => "/one" + two = stub 'module', :plugins? => true, :plugins => "/two" + + @environment.expects(:modules).returns [one, two] + @mount.search("foo/bar").should == %w{/one /two} + end +end diff --git a/spec/unit/indirector/file_content/modules.rb b/spec/unit/indirector/file_content/modules.rb deleted file mode 100755 index 00f02bb0b..000000000 --- a/spec/unit/indirector/file_content/modules.rb +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-10-18. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'puppet/indirector/file_content/modules' - -describe Puppet::Indirector::FileContent::Modules do - it "should be registered with the file_content indirection" do - Puppet::Indirector::Terminus.terminus_class(:file_content, :modules).should equal(Puppet::Indirector::FileContent::Modules) - end - - it "should be a subclass of the ModuleFiles terminus" do - Puppet::Indirector::FileContent::Modules.superclass.should equal(Puppet::Indirector::ModuleFiles) - end -end diff --git a/spec/unit/indirector/file_metadata/modules.rb b/spec/unit/indirector/file_metadata/modules.rb deleted file mode 100755 index 7e5113df5..000000000 --- a/spec/unit/indirector/file_metadata/modules.rb +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-10-18. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'puppet/indirector/file_metadata/modules' - -describe Puppet::Indirector::FileMetadata::Modules do - it "should be registered with the file_metadata indirection" do - Puppet::Indirector::Terminus.terminus_class(:file_metadata, :modules).should equal(Puppet::Indirector::FileMetadata::Modules) - end - - it "should be a subclass of the ModuleFiles terminus" do - Puppet::Indirector::FileMetadata::Modules.superclass.should equal(Puppet::Indirector::ModuleFiles) - end -end - -describe Puppet::Indirector::FileMetadata::Modules, " when finding metadata" do - before do - @finder = Puppet::Indirector::FileMetadata::Modules.new - @finder.stubs(:environment).returns(nil) - @module = Puppet::Module.new("mymod", "/path/to") - @finder.stubs(:find_module).returns(@module) - - @request = Puppet::Indirector::Request.new(:metadata, :find, "puppet://hostname/modules/mymod/my/file") - end - - it "should return nil if the file is not found" do - FileTest.expects(:exists?).with("/path/to/files/my/file").returns false - @finder.find(@request).should be_nil - end - - it "should retrieve the instance's attributes if the file is found" do - FileTest.expects(:exists?).with("/path/to/files/my/file").returns true - instance = mock 'metadta' - Puppet::FileServing::Metadata.expects(:new).returns instance - instance.expects :collect - @finder.find(@request) - end -end diff --git a/spec/unit/indirector/file_server.rb b/spec/unit/indirector/file_server.rb index e17deecf9..a80f5aed3 100755 --- a/spec/unit/indirector/file_server.rb +++ b/spec/unit/indirector/file_server.rb @@ -1,191 +1,251 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-19. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/file_server' require 'puppet/file_serving/configuration' describe Puppet::Indirector::FileServer do before :each do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) @file_server_class = Class.new(Puppet::Indirector::FileServer) do def self.to_s "Testing::Mytype" end end @file_server = @file_server_class.new @uri = "puppet://host/my/local/file" @configuration = mock 'configuration' Puppet::FileServing::Configuration.stubs(:create).returns(@configuration) @request = Puppet::Indirector::Request.new(:myind, :mymethod, @uri) end - describe Puppet::Indirector::FileServer, " when finding files" do + describe "when finding files" do + before do + @mount = stub 'mount', :find => nil + @instance = stub('instance', :links= => nil, :collect => nil) + end + + it "should use the configuration to find the mount and relative path" do + @configuration.expects(:split_path).with(@request) - it "should use the path portion of the URI as the file name" do - @configuration.expects(:file_path).with("my/local/file", :node => nil) @file_server.find(@request) end - it "should use the FileServing configuration to convert the file name to a fully qualified path" do - @configuration.expects(:file_path).with("my/local/file", :node => nil) - @file_server.find(@request) + it "should return nil if it cannot find the mount" do + @configuration.expects(:split_path).with(@request).returns(nil, nil) + + @file_server.find(@request).should be_nil end - it "should pass the node name to the FileServing configuration if one is provided" do - @configuration.expects(:file_path).with("my/local/file", :node => "testing") - @request.node = "testing" + it "should use the mount to find the full path" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:find).with { |key, options| key == "rel/path" } + @file_server.find(@request) end - it "should return nil if no fully qualified path is found" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns(nil) + it "should return nil if it cannot find a full path" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:find).with { |key, options| key == "rel/path" }.returns nil + @file_server.find(@request).should be_nil end - it "should return an instance of the model created with the full path if a file is found" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns("/some/file") - instance = stub("instance", :collect => nil) - @model.expects(:new).returns instance - @file_server.find(@request).should equal(instance) + it "should create an instance with the found path" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:find).with { |key, options| key == "rel/path" }.returns "/my/file" + + @model.expects(:new).with("/my/file").returns @instance + + @file_server.find(@request).should equal(@instance) end - end - describe Puppet::Indirector::FileServer, " when returning instances" do - before :each do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns("/some/file") - @instance = stub 'instance', :collect => nil + it "should set 'links' on the instance if it is set in the request options" do + @request.options[:links] = true + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:find).with { |key, options| key == "rel/path" }.returns "/my/file" + + @model.expects(:new).with("/my/file").returns @instance + + @instance.expects(:links=).with(true) + + @file_server.find(@request).should equal(@instance) end - it "should create the instance with the path at which the instance was found" do - @model.expects(:new).with { |key, options| key == "/some/file" }.returns @instance - @file_server.find(@request) + it "should collect the instance" do + @request.options[:links] = true + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:find).with { |key, options| key == "rel/path" }.returns "/my/file" + + @model.expects(:new).with("/my/file").returns @instance + + @instance.expects(:collect) + + @file_server.find(@request).should equal(@instance) end + end - it "should set the provided :links setting on to the instance if one is provided" do - @model.expects(:new).returns(@instance) - @instance.expects(:links=).with(:mytest) - @request.options[:links] = :mytest - @file_server.find(@request) + describe "when searching for instances" do + before do + @mount = stub 'mount', :search => nil + @instance = stub('instance', :links= => nil, :collect => nil) end - it "should not set a :links value if no :links parameter is provided" do - @model.expects(:new).returns(@instance) - @file_server.find(@request) + it "should use the configuration to search the mount and relative path" do + @configuration.expects(:split_path).with(@request) + + @file_server.search(@request) end - it "should collect each instance's attributes before returning" do - @instance.expects(:collect) - @model.expects(:new).returns @instance - @file_server.find(@request) + it "should return nil if it cannot search the mount" do + @configuration.expects(:split_path).with(@request).returns(nil, nil) + + @file_server.search(@request).should be_nil end - end - describe Puppet::Indirector::FileServer, " when checking authorization" do + it "should use the mount to search for the full paths" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) - it "should have an authorization hook" do - @file_server.should respond_to(:authorized?) + @mount.expects(:search).with { |key, options| key == "rel/path" } + + @file_server.search(@request) end - it "should deny the :destroy method" do - @request.method = :destroy - @file_server.authorized?(@request).should be_false + it "should return nil if searching does not find any full paths" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:search).with { |key, options| key == "rel/path" }.returns nil + + @file_server.search(@request).should be_nil end - it "should deny the :save method" do - @request.method = :save - @file_server.authorized?(@request).should be_false + it "should create a fileset with each returned path and merge them" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:search).with { |key, options| key == "rel/path" }.returns %w{/one /two} + + FileTest.stubs(:exist?).returns true + + one = mock 'fileset_one' + Puppet::FileServing::Fileset.expects(:new).with("/one", @request).returns(one) + two = mock 'fileset_two' + Puppet::FileServing::Fileset.expects(:new).with("/two", @request).returns(two) + + Puppet::FileServing::Fileset.expects(:merge).with(one, two).returns [] + + @file_server.search(@request) end - - describe "and finding file information" do - before do - @request.method = :find - end - it "should use the file server configuration to determine authorization" do - @configuration.expects(:authorized?) - @file_server.authorized?(@request) - end + it "should create an instance with each path resulting from the merger of the filesets" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) - it "should pass the file path from the URI to the file server configuration" do - @configuration.expects(:authorized?).with { |uri, *args| uri == "my/local/file" } - @file_server.authorized?(@request) - end + @mount.expects(:search).with { |key, options| key == "rel/path" }.returns [] - it "should pass the node name to the file server configuration" do - @configuration.expects(:authorized?).with { |key, options| options[:node] == "mynode" } - @request.node = "mynode" - @file_server.authorized?(@request) - end + FileTest.stubs(:exist?).returns true - it "should pass the IP address to the file server configuration" do - @configuration.expects(:authorized?).with { |key, options| options[:ipaddress] == "myip" } - @request.ip = "myip" - @file_server.authorized?(@request) - end + Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one", "two" => "/two") - it "should return false if the file server configuration denies authorization" do - @configuration.expects(:authorized?).returns(false) - @file_server.authorized?(@request) - end + one = stub 'one', :collect => nil + @model.expects(:new).with("/one", :relative_path => "one").returns one - it "should return true if the file server configuration approves authorization" do - @configuration.expects(:authorized?).returns(true) - @file_server.authorized?(@request) - end + two = stub 'two', :collect => nil + @model.expects(:new).with("/two", :relative_path => "two").returns two + + # order can't be guaranteed + result = @file_server.search(@request) + result.should be_include(one) + result.should be_include(two) + result.length.should == 2 end - end - describe Puppet::Indirector::FileServer, " when searching for files" do + it "should set 'links' on the instances if it is set in the request options" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:search).with { |key, options| key == "rel/path" }.returns [] + + FileTest.stubs(:exist?).returns true + + Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") + + one = stub 'one', :collect => nil + @model.expects(:new).with("/one", :relative_path => "one").returns one + one.expects(:links=).with true + + @request.options[:links] = true - it "should use the path portion of the URI as the file name" do - @configuration.expects(:file_path).with("my/local/file", :node => nil) @file_server.search(@request) end - it "should use the FileServing configuration to convert the file name to a fully qualified path" do - @configuration.expects(:file_path).with("my/local/file", :node => nil) + it "should collect the instances" do + @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) + + @mount.expects(:search).with { |key, options| key == "rel/path" }.returns [] + + FileTest.stubs(:exist?).returns true + + Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") + + one = mock 'one' + @model.expects(:new).with("/one", :relative_path => "one").returns one + one.expects(:collect) + @file_server.search(@request) end + end - it "should pass the node name to the FileServing configuration if one is provided" do - @configuration.expects(:file_path).with("my/local/file", :node => "testing") - @request.node = "testing" - @file_server.search(@request) + describe "when checking authorization" do + before do + @request.method = :find end - it "should return nil if no fully qualified path is found" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns(nil) - @file_server.search(@request).should be_nil + it "should return false when destroying" do + @request.method = :destroy + @file_server.should_not be_authorized(@request) end - it "should use :path2instances from the terminus_helper to return instances if a module is found and the file exists" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns("my/file") - @file_server.expects(:path2instances) - @file_server.search(@request) + it "should return false when saving" do + @request.method = :save + @file_server.should_not be_authorized(@request) end - it "should pass the request on to :path2instances" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns("my/file") - @file_server.expects(:path2instances).with(@request, "my/file").returns [] - @file_server.search(@request) + it "should use the configuration to find the mount and relative path" do + @configuration.expects(:split_path).with(@request) + + @file_server.authorized?(@request) end - it "should return the result of :path2instances" do - @configuration.expects(:file_path).with("my/local/file", :node => nil).returns("my/file") - @file_server.expects(:path2instances).with(@request, "my/file").returns :footest - @file_server.search(@request).should == :footest + it "should return false if it cannot find the mount" do + @configuration.expects(:split_path).with(@request).returns(nil, nil) + + @file_server.should_not be_authorized(@request) + 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.options[:node] = "mynode" + @request.options[:ipaddress] = "myip" + @mount.expects(:allowed?).with("mynode", "myip").returns "something" + + @file_server.authorized?(@request).should == "something" end end end diff --git a/spec/unit/indirector/module_files.rb b/spec/unit/indirector/module_files.rb deleted file mode 100755 index 14ca8913c..000000000 --- a/spec/unit/indirector/module_files.rb +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-10-19. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../spec_helper' - -require 'puppet/indirector/module_files' - - -describe Puppet::Indirector::ModuleFiles do - - before :each do - @environment = stub('env', :name => 'myenv') - Puppet::Node::Environment.stubs(:new).returns(@environment) - - Puppet::Indirector::Terminus.stubs(:register_terminus_class) - @model = mock 'model' - @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model - Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) - - @module_files_class = Class.new(Puppet::Indirector::ModuleFiles) do - def self.to_s - "Testing::Mytype" - end - end - - @module_files = @module_files_class.new - - @module = Puppet::Module.new("mymod", "/module/path") - - @request = Puppet::Indirector::Request.new(:mytype, :find, "puppet://host/modules/mymod/local/file") - end - - describe Puppet::Indirector::ModuleFiles, " when finding files" do - before do - @environment.stubs(:module).returns @module - end - - it "should strip off the leading 'modules/' mount name" do - @environment.expects(:module).with("mymod").returns @module - @module_files.find(@request) - end - - it "should not strip off leading terms that start with 'modules' but are longer words" do - @request.stubs(:key).returns "modulestart/mymod/local/file" - @environment.expects(:module).with("modulestart").returns @module - @module_files.find(@request) - end - - it "should search for a module whose name is the first term in the remaining file path" do - @module_files.find(@request) - end - - it "should search for a file relative to the module's files directory" do - FileTest.expects(:exists?).with("/module/path/files/local/file") - @module_files.find(@request) - end - - it "should return nil if the module does not exist" do - @environment.expects(:module).returns nil - @module_files.find(@request).should be_nil - end - - it "should return nil if the module exists but the file does not" do - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(false) - @module_files.find(@request).should be_nil - end - - it "should return an instance of the model if a module is found and the file exists" do - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(true) - @model.expects(:new).returns(:myinstance) - @module_files.find(@request).should == :myinstance - end - - it "should use the node's environment to look up the module if the node name is provided" do - node = stub "node", :environment => "testing" - Puppet::Node.expects(:find).with("mynode").returns(node) - - newenv = stub 'newenv', :name => "newenv" - - Puppet::Node::Environment.expects(:new).with("testing").returns newenv - newenv.expects(:module).returns nil - - @request.stubs(:node).returns "mynode" - @module_files.find(@request) - end - - it "should use the default environment setting to look up the module if the node name is not provided" do - newenv = stub 'newenv', :name => "newenv" - - Puppet::Node::Environment.expects(:new).with(nil).returns newenv - newenv.expects(:module) - - @request.stubs(:node).returns nil - @module_files.find(@request) - end - end - - describe Puppet::Indirector::ModuleFiles, " when returning instances" do - - before do - @environment.expects(:module).with("mymod").returns @module - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(true) - @instance = mock 'instance' - end - - it "should create the instance with the path at which the instance was found" do - @model.expects(:new).with { |key, options| key == "/module/path/files/local/file" } - @module_files.find(@request) - end - - it "should set the provided :links setting on to the instance if one is provided" do - @model.expects(:new).returns(@instance) - @instance.expects(:links=).with(:mytest) - - @request.options[:links] = :mytest - @module_files.find(@request) - end - - it "should not set a :links value if no :links parameter is provided" do - @model.expects(:new).returns(@instance) - @module_files.find(@request) - end - end - - describe Puppet::Indirector::ModuleFiles, " when authorizing" do - - before do - @configuration = mock 'configuration' - Puppet::FileServing::Configuration.stubs(:create).returns(@configuration) - end - - it "should have an authorization hook" do - @module_files.should respond_to(:authorized?) - end - - it "should deny the :destroy method" do - @request.expects(:method).returns :destroy - @module_files.authorized?(@request).should be_false - end - - it "should deny the :save method" do - @request.expects(:method).returns :save - @module_files.authorized?(@request).should be_false - end - - it "should use the file server configuration to determine authorization" do - @configuration.expects(:authorized?) - @module_files.authorized?(@request) - end - - it "should use the path directly from the URI if it already includes /modules" do - @request.expects(:key).returns "modules/my/file" - @configuration.expects(:authorized?).with { |uri, *args| uri == "modules/my/file" } - @module_files.authorized?(@request) - end - - it "should add modules/ to the file path if it's not included in the URI" do - @request.expects(:key).returns "my/file" - @configuration.expects(:authorized?).with { |uri, *args| uri == "modules/my/file" } - @module_files.authorized?(@request) - end - - it "should pass the node name to the file server configuration" do - @request.expects(:key).returns "my/file" - @configuration.expects(:authorized?).with { |key, options| options[:node] == "mynode" } - @request.stubs(:node).returns "mynode" - @module_files.authorized?(@request) - end - - it "should pass the IP address to the file server configuration" do - @request.expects(:ip).returns "myip" - @configuration.expects(:authorized?).with { |key, options| options[:ipaddress] == "myip" } - @module_files.authorized?(@request) - end - - it "should return false if the file server configuration denies authorization" do - @configuration.expects(:authorized?).returns(false) - @module_files.authorized?(@request).should be_false - end - - it "should return true if the file server configuration approves authorization" do - @configuration.expects(:authorized?).returns(true) - @module_files.authorized?(@request).should be_true - end - end - - describe Puppet::Indirector::ModuleFiles, " when searching for files" do - - it "should strip off the leading 'modules/' mount name" do - @environment.expects(:module).with("mymod").returns @module - @module_files.search(@request) - end - - it "should not strip off leading terms that start with '/modules' but are longer words" do - @environment.expects(:module).with("modulestart").returns @module - @request.stubs(:key).returns "modulestart/my/local/file" - @module_files.search @request - end - - it "should search for a module whose name is the first term in the remaining file path" do - @environment.expects(:module).with("mymod").returns @module - @module_files.search(@request) - end - - it "should search for a file relative to the module's files directory" do - @environment.expects(:module).with("mymod").returns @module - FileTest.expects(:exists?).with("/module/path/files/local/file") - @module_files.search(@request) - end - - it "should return nil if the module does not exist" do - @environment.expects(:module).with("mymod").returns @module - @module_files.search(@request).should be_nil - end - - it "should return nil if the module exists but the file does not" do - @environment.expects(:module).with("mymod").returns @module - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(false) - @module_files.search(@request).should be_nil - end - - it "should use the node's environment to look up the module if the node name is provided" do - node = stub "node", :environment => "testing" - Puppet::Node.expects(:find).with("mynode").returns(node) - - newenv = stub 'newenv', :name => "newenv" - - Puppet::Node::Environment.expects(:new).with("testing").returns newenv - newenv.expects(:module).returns nil - - @request.stubs(:node).returns "mynode" - @module_files.search(@request) - end - - it "should use the default environment setting to look up the module if the node name is not provided and the environment is not set to ''" do - newenv = stub 'newenv', :name => "newenv" - - Puppet::Node::Environment.expects(:new).with(nil).returns newenv - newenv.expects(:module) - - @request.stubs(:node).returns nil - @module_files.search(@request) - end - - it "should use :path2instances from the terminus_helper to return instances if a module is found and the file exists" do - @environment.expects(:module).with("mymod").returns @module - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(true) - @module_files.expects(:path2instances).with(@request, "/module/path/files/local/file") - @module_files.search(@request) - end - - it "should pass the request directly to :path2instances" do - @environment.expects(:module).with("mymod").returns @module - FileTest.expects(:exists?).with("/module/path/files/local/file").returns(true) - @module_files.expects(:path2instances).with(@request, "/module/path/files/local/file") - @module_files.search(@request) - end - end -end diff --git a/spec/unit/module.rb b/spec/unit/module.rb index d7fb14c66..313de6761 100755 --- a/spec/unit/module.rb +++ b/spec/unit/module.rb @@ -1,345 +1,351 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' describe Puppet::Module do [:plugins, :templates, :files, :manifests].each do |filetype| it "should be able to indicate whether it has #{filetype}" do Puppet::Module.new("foo", "/foo/bar").should respond_to(filetype.to_s + "?") end it "should correctly detect when it has #{filetype}" do FileTest.expects(:exist?).with("/foo/bar/#{filetype}").returns true Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s + "?").should be_true end it "should correctly detect when it does not have #{filetype}" do FileTest.expects(:exist?).with("/foo/bar/#{filetype}").returns false Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s + "?").should be_false end it "should have a method for returning the full path to the #{filetype}" do Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s).should == File.join("/foo/bar", filetype.to_s) end it "should be able to return individual #{filetype}" do path = File.join("/foo/bar", filetype.to_s, "my/file") FileTest.expects(:exist?).with(path).returns true Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s.sub(/s$/, ''), "my/file").should == path end it "should return nil if asked to return individual #{filetype} that don't exist" do FileTest.expects(:exist?).with(File.join("/foo/bar", filetype.to_s, "my/file")).returns false Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end + + it "should return the base directory if asked for a nil path" do + path = File.join("/foo/bar", filetype.to_s) + FileTest.expects(:exist?).with(path).returns true + Puppet::Module.new("foo", "/foo/bar").send(filetype.to_s.sub(/s$/, ''), nil).should == path + end end end describe Puppet::Module, "when yielding each module in a list of directories" do before do FileTest.stubs(:directory?).returns true end it "should search for modules in each directory in the list" do Dir.expects(:entries).with("/one").returns [] Dir.expects(:entries).with("/two").returns [] Puppet::Module.each_module("/one", "/two") end it "should accept the list of directories as an array" do Dir.expects(:entries).with("/one").returns [] Dir.expects(:entries).with("/two").returns [] Puppet::Module.each_module(%w{/one /two}) end it "should accept the list of directories joined by #{File::PATH_SEPARATOR}" do Dir.expects(:entries).with("/one").returns [] Dir.expects(:entries).with("/two").returns [] list = %w{/one /two}.join(File::PATH_SEPARATOR) Puppet::Module.each_module(list) end it "should not create modules for '.' or '..' in the provided directory list" do Dir.expects(:entries).with("/one").returns(%w{. ..}) result = [] Puppet::Module.each_module("/one") do |mod| result << mod end result.should be_empty end it "should not create modules for non-directories in the provided directory list" do Dir.expects(:entries).with("/one").returns(%w{notdir}) FileTest.expects(:directory?).with("/one/notdir").returns false result = [] Puppet::Module.each_module("/one") do |mod| result << mod end result.should be_empty end it "should yield each found module" do Dir.expects(:entries).with("/one").returns(%w{f1 f2}) one = mock 'one' two = mock 'two' Puppet::Module.expects(:new).with("f1", "/one/f1").returns one Puppet::Module.expects(:new).with("f2", "/one/f2").returns two result = [] Puppet::Module.each_module("/one") do |mod| result << mod end result.should == [one, two] end it "should not yield a module with the same name as a previously yielded module" do Dir.expects(:entries).with("/one").returns(%w{f1}) Dir.expects(:entries).with("/two").returns(%w{f1}) one = mock 'one' Puppet::Module.expects(:new).with("f1", "/one/f1").returns one Puppet::Module.expects(:new).with("f1", "/two/f1").never result = [] Puppet::Module.each_module("/one", "/two") do |mod| result << mod end result.should == [one] end end describe Puppet::Module, " when building its search path" do it "should use the current environment's search path if no environment is specified" do env = mock 'env' env.expects(:modulepath).returns "eh" Puppet::Node::Environment.expects(:new).with(nil).returns env Puppet::Module.modulepath.should == "eh" end it "should use the specified environment's search path if an environment is specified" do env = mock 'env' env.expects(:modulepath).returns "eh" Puppet::Node::Environment.expects(:new).with("foo").returns env Puppet::Module.modulepath("foo").should == "eh" end end describe Puppet::Module, " when searching for modules" do it "should use the current environment to find the specified module if no environment is provided" do env = mock 'env' env.expects(:module).with("foo").returns "yay" Puppet::Node::Environment.expects(:new).with(nil).returns env Puppet::Module.find("foo").should == "yay" end it "should use the specified environment to find the specified module if an environment is provided" do env = mock 'env' env.expects(:module).with("foo").returns "yay" Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find("foo", "myenv").should == "yay" end end describe Puppet::Module, " when searching for templates" do it "should return fully-qualified templates directly" do Puppet::Module.expects(:modulepath).never Puppet::Module.find_template("/my/template").should == "/my/template" end it "should return the template from the first found module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Module.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should return the file in the templatedir if it exists" do Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet[:modulepath] = "/one:/two" File.stubs(:directory?).returns(true) FileTest.stubs(:exist?).returns(true) Puppet::Module.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should raise an error if no valid templatedir exists" do Puppet::Module.stubs(:templatepath).with(nil).returns(nil) lambda { Puppet::Module.find_template("mytemplate") }.should raise_error end it "should not raise an error if no valid templatedir exists and the template exists in a module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Module.stubs(:templatepath).with(nil).returns(nil) Puppet::Module.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should use the main templatedir if no module is found" do Puppet::Module.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Module.expects(:find).with("mymod", nil).returns(nil) Puppet::Module.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should return unqualified templates directly in the template dir" do Puppet::Module.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should accept relative templatedirs" do Puppet[:templatedir] = "my/templates" File.expects(:directory?).with(File.join(Dir.getwd,"my/templates")).returns(true) Puppet::Module.find_template("mytemplate").should == File.join(Dir.getwd,"my/templates/mytemplate") end it "should use the environment templatedir if no module is found and an environment is specified" do Puppet::Module.stubs(:templatepath).with("myenv").returns(["/myenv/templates"]) Puppet::Module.expects(:find).with("mymod", "myenv").returns(nil) Puppet::Module.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use first dir from environment templatedir if no module is found and an environment is specified" do Puppet::Module.stubs(:templatepath).with("myenv").returns(["/myenv/templates", "/two/templates"]) Puppet::Module.expects(:find).with("mymod", "myenv").returns(nil) Puppet::Module.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and the first dir contains template" do Puppet::Module.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(true) Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/one/templates/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and only second dir contains template" do Puppet::Module.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(false) FileTest.expects(:exist?).with("/two/templates/mytemplate").returns(true) Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/two/templates/mytemplate" end it "should use the node environment if specified" do mod = mock 'module' Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:template).returns("/my/modules/mymod/templates/envtemplate") Puppet::Module.find_template("mymod/envtemplate", "myenv").should == "/my/modules/mymod/templates/envtemplate" end after { Puppet.settings.clear } end describe Puppet::Module, " when searching for manifests when no module is found" do before do File.stubs(:find).returns(nil) end it "should not look for modules when paths are fully qualified" do Puppet.expects(:value).with(:modulepath).never file = "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Module.find_manifests(file) end it "should directly return fully qualified files" do file = "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Module.find_manifests(file).should == [file] end it "should match against provided fully qualified patterns" do pattern = "/fully/qualified/pattern/*" Dir.expects(:glob).with(pattern).returns(%w{my file list}) Puppet::Module.find_manifests(pattern).should == %w{my file list} end it "should look for files relative to the current directory" do cwd = Dir.getwd Dir.expects(:glob).with("#{cwd}/foobar/init.pp").returns(["#{cwd}/foobar/init.pp"]) Puppet::Module.find_manifests("foobar/init.pp").should == ["#{cwd}/foobar/init.pp"] end it "should only return files, not directories" do pattern = "/fully/qualified/pattern/*" file = "/my/file" dir = "/my/directory" Dir.expects(:glob).with(pattern).returns([file, dir]) FileTest.expects(:directory?).with(file).returns(false) FileTest.expects(:directory?).with(dir).returns(true) Puppet::Module.find_manifests(pattern).should == [file] end end describe Puppet::Module, " when searching for manifests in a found module" do before do @module = Puppet::Module.new("mymod", "/one") end it "should return the manifests from the first found module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Module.find_manifests("mymod/init.pp").should == ["/one/mymod/manifests/init.pp"] end it "should use the node environment if specified" do mod = mock 'module' Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Module.find_manifests("mymod/init.pp", :environment => "myenv").should == ["/one/mymod/manifests/init.pp"] end it "should return all manifests matching the glob pattern" do File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/one/manifests/yay/*.pp").returns(%w{/one /two}) @module.match_manifests("yay/*.pp").should == %w{/one /two} end it "should not return directories" do Dir.expects(:glob).with("/one/manifests/yay/*.pp").returns(%w{/one /two}) FileTest.expects(:directory?).with("/one").returns false FileTest.expects(:directory?).with("/two").returns true @module.match_manifests("yay/*.pp").should == %w{/one} end it "should default to the 'init.pp' file in the manifests directory" do Dir.expects(:glob).with("/one/manifests/init.pp").returns(%w{/init.pp}) @module.match_manifests(nil).should == %w{/init.pp} end after { Puppet.settings.clear } end describe Puppet::Module, " when returning files" do it "should return the path to the module's 'files' directory" do mod = Puppet::Module.send(:new, "mymod", "/my/mod") mod.files.should == "/my/mod/files" end end