diff --git a/lib/puppet/indirector/resource_type/parser.rb b/lib/puppet/indirector/resource_type/parser.rb index 4af03d0f8..60e72250e 100644 --- a/lib/puppet/indirector/resource_type/parser.rb +++ b/lib/puppet/indirector/resource_type/parser.rb @@ -1,91 +1,106 @@ require 'puppet/resource/type' require 'puppet/indirector/code' require 'puppet/indirector/resource_type' # The main terminus for Puppet::Resource::Type # # This exposes the known resource types from within Puppet. Only find # and search are supported. When a request is received, Puppet will # attempt to load all resource types (by parsing manifests and modules) and # returns a description of the resource types found. The format of these # objects is documented at {Puppet::Resource::Type}. # # @api public class Puppet::Indirector::ResourceType::Parser < Puppet::Indirector::Code desc "Return the data-form of a resource type." # Find will return the first resource_type with the given name. It is # not possible to specify the kind of the resource type. # # @param request [Puppet::Indirector::Request] The request object. # The only parameters used from the request are `environment` and # `key`, which corresponds to the resource type's `name` field. # @return [Puppet::Resource::Type, nil] # @api public def find(request) - krt = request.environment.known_resource_types + begin + # This is a fix in 3.x that will be replaced with the use of a context + # (That is not available until 3.5). + $squelsh_parse_errors = true + krt = request.environment.known_resource_types - # This is a bit ugly. - [:hostclass, :definition, :node].each do |type| - # We have to us 'find_' here because it will - # load any missing types from disk, whereas the plain - # '' method only returns from memory. - if r = krt.send("find_#{type}", [""], request.key) - return r + # This is a bit ugly. + [:hostclass, :definition, :node].each do |type| + # We have to us 'find_' here because it will + # load any missing types from disk, whereas the plain + # '' method only returns from memory. + if r = krt.send("find_#{type}", [""], request.key) + return r + end end + nil + ensure + $squelsh_parse_errors = false end - nil end # Search for resource types using a regular expression. Unlike `find`, this # allows you to filter the results by the "kind" of the resource type # ("class", "defined_type", or "node"). All three are searched if no # `kind` filter is given. This also accepts the special string "`*`" # to return all resource type objects. # # @param request [Puppet::Indirector::Request] The request object. The # `key` field holds the regular expression used to search, and # `options[:kind]` holds the kind query parameter to filter the # result as described above. The `environment` field specifies the # environment used to load resources. # # @return [Array, nil] # # @api public def search(request) - krt = request.environment.known_resource_types - # Make sure we've got all of the types loaded. - krt.loader.import_all + begin + # This is a fix in 3.x that will be replaced with the use of a context + # (That is not available until 3.5). + $squelsh_parse_errors = true - result_candidates = case request.options[:kind] - when "class" - krt.hostclasses.values - when "defined_type" - krt.definitions.values - when "node" - krt.nodes.values - when nil - result_candidates = [krt.hostclasses.values, krt.definitions.values, krt.nodes.values] - else - raise ArgumentError, "Unrecognized kind filter: " + - "'#{request.options[:kind]}', expected one " + - " of 'class', 'defined_type', or 'node'." - end + krt = request.environment.known_resource_types + # Make sure we've got all of the types loaded. + krt.loader.import_all - result = result_candidates.flatten.reject { |t| t.name == "" } - return nil if result.empty? - return result if request.key == "*" + result_candidates = case request.options[:kind] + when "class" + krt.hostclasses.values + when "defined_type" + krt.definitions.values + when "node" + krt.nodes.values + when nil + result_candidates = [krt.hostclasses.values, krt.definitions.values, krt.nodes.values] + else + raise ArgumentError, "Unrecognized kind filter: " + + "'#{request.options[:kind]}', expected one " + + " of 'class', 'defined_type', or 'node'." + end - # Strip the regex of any wrapping slashes that might exist - key = request.key.sub(/^\//, '').sub(/\/$/, '') - begin - regex = Regexp.new(key) - rescue => detail - raise ArgumentError, "Invalid regex '#{request.key}': #{detail}" - end + result = result_candidates.flatten.reject { |t| t.name == "" } + return nil if result.empty? + return result if request.key == "*" + + # Strip the regex of any wrapping slashes that might exist + key = request.key.sub(/^\//, '').sub(/\/$/, '') + begin + regex = Regexp.new(key) + rescue => detail + raise ArgumentError, "Invalid regex '#{request.key}': #{detail}" + end - result.reject! { |t| t.name.to_s !~ regex } - return nil if result.empty? - result + result.reject! { |t| t.name.to_s !~ regex } + return nil if result.empty? + result + end + ensure + $squelsh_parse_errors = false end end diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb index 9e03fd8af..4ce9dc24f 100644 --- a/lib/puppet/parser/type_loader.rb +++ b/lib/puppet/parser/type_loader.rb @@ -1,131 +1,141 @@ require 'find' require 'forwardable' require 'puppet/node/environment' require 'puppet/parser/parser_factory' class Puppet::Parser::TypeLoader extend Forwardable include Puppet::Node::Environment::Helper # Import manifest files that match a given file glob pattern. # # @param pattern [String] the file glob to apply when determining which files # to load # @param dir [String] base directory to use when the file is not # found in a module # @api private def import(pattern, dir) return if Puppet[:ignoreimport] modname, files = Puppet::Parser::Files.find_manifests_in_modules(pattern, environment) if files.empty? abspat = File.expand_path(pattern, dir) file_pattern = abspat + (File.extname(abspat).empty? ? '{.pp,.rb}' : '' ) files = Dir.glob(file_pattern).uniq.reject { |f| FileTest.directory?(f) } modname = nil if files.empty? raise_no_files_found(pattern) end end load_files(modname, files) end # Load all of the manifest files in all known modules. # @api private def import_all # And then load all files from each module, but (relying on system # behavior) only load files from the first module of a given name. E.g., # given first/foo and second/foo, only files from first/foo will be loaded. environment.modules.each do |mod| load_files(mod.name, mod.all_manifests) end end def_delegator :environment, :known_resource_types def initialize(env) self.environment = env end # Try to load the object with the given fully qualified name. def try_load_fqname(type, fqname) return nil if fqname == "" # special-case main. files_to_try_for(fqname).each do |filename| begin imported_types = import_from_modules(filename) if result = imported_types.find { |t| t.type == type and t.name == fqname } Puppet.debug "Automatically imported #{fqname} from #{filename} into #{environment}" return result end rescue Puppet::ImportError => detail # I'm not convienced we should just drop these errors, but this # preserves existing behaviours. end end # Nothing found. return nil end def parse_file(file) Puppet.debug("importing '#{file}' in environment #{environment}") parser = Puppet::Parser::ParserFactory.parser(environment) parser.file = file return parser.parse end private def import_from_modules(pattern) modname, files = Puppet::Parser::Files.find_manifests_in_modules(pattern, environment) if files.empty? raise_no_files_found(pattern) end load_files(modname, files) end def raise_no_files_found(pattern) raise Puppet::ImportError, "No file(s) found for import of '#{pattern}'" end def load_files(modname, files) @loaded ||= {} loaded_asts = [] files.reject { |file| @loaded[file] }.each do |file| - begin + # NOTE: This ugly implementation will be replaced in Puppet 3.5. + # The implementation now makes use of a global variable because the context support is + # not available until Puppet 3.5. + # The use case is that parsing for the purpose of searching for information + # should not abort. There is currently one such use case in indirector/resourcetype/parser + # + if $squelsh_parse_errors + begin + loaded_asts << parse_file(file) + rescue => e + # Resume from errors so that all parseable files would + # still be parsed. Mark this file as loaded so that + # it would not be parsed next time (handle it as if + # it was successfully parsed). + Puppet.debug("Unable to parse '#{file}': #{e.message}") + end + else loaded_asts << parse_file(file) - rescue => e - # Resume from errors so that all parseable files would - # still be parsed. Mark this file as loaded so that - # it would not be parsed next time (handle it as if - # it was successfully parsed). - Puppet.debug("Unable to parse '#{file}': #{e.message}") end @loaded[file] = true end loaded_asts.collect do |ast| known_resource_types.import_ast(ast, modname) end.flatten end # Return a list of all file basenames that should be tried in order # to load the object with the given fully qualified name. def files_to_try_for(qualified_name) qualified_name.split('::').inject([]) do |paths, name| add_path_for_name(paths, name) end end def add_path_for_name(paths, name) if paths.empty? [name] else paths.unshift(File.join(paths.first, name)) end end end