diff options
| author | Luke Kanies <luke@reductivelabs.com> | 2010-01-07 17:23:31 -0800 |
|---|---|---|
| committer | test branch <puppet-dev@googlegroups.com> | 2010-02-17 06:50:53 -0800 |
| commit | d0389f4d16efbeccf47d6cd2f1b0854ccb1c88d5 (patch) | |
| tree | 3a3060ac94be20b25742f5ec956e95c8ff3633d8 /lib/puppet/resource | |
| parent | 67ef78d9f231661d0fdd6260d470cf0d06f1bac2 (diff) | |
| download | puppet-d0389f4d16efbeccf47d6cd2f1b0854ccb1c88d5.tar.gz puppet-d0389f4d16efbeccf47d6cd2f1b0854ccb1c88d5.tar.xz puppet-d0389f4d16efbeccf47d6cd2f1b0854ccb1c88d5.zip | |
Renaming Parser::ResourceType to Resource::Type
Basically, these classes (ResourceType and ResourceTypeCollection)
don't really belong in Parser, so I'm moving them to the
Resource namespace. This will be where anything RAL-related goes
from now on, and as we migrate functionality out of Puppet::Type,
it should go here.
Signed-off-by: Luke Kanies <luke@reductivelabs.com>
Diffstat (limited to 'lib/puppet/resource')
| -rw-r--r-- | lib/puppet/resource/type.rb | 237 | ||||
| -rw-r--r-- | lib/puppet/resource/type_collection.rb | 182 | ||||
| -rw-r--r-- | lib/puppet/resource/type_collection_helper.rb | 7 |
3 files changed, 426 insertions, 0 deletions
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb new file mode 100644 index 000000000..1a18b4e97 --- /dev/null +++ b/lib/puppet/resource/type.rb @@ -0,0 +1,237 @@ +require 'puppet/parser/parser' +require 'puppet/util/warnings' +require 'puppet/util/errors' +require 'puppet/util/inline_docs' +require 'puppet/parser/ast/leaf' + +class Puppet::Resource::Type + include Puppet::Util::InlineDocs + include Puppet::Util::Warnings + include Puppet::Util::Errors + + RESOURCE_SUPERTYPES = [:hostclass, :node, :definition] + + attr_accessor :file, :line, :doc, :code, :parent, :code_collection + attr_reader :type, :namespace, :arguments, :behaves_like + + # Are we a child of the passed class? Do a recursive search up our + # parentage tree to figure it out. + def child_of?(klass) + return false unless parent + + return true if klass == parent_type + return parent_type.child_of?(klass) + end + + # Now evaluate the code associated with this class or definition. + def evaluate_code(resource) + # Create a new scope. + scope = subscope(resource.scope, resource) + scope.compiler.class_set(name, scope) + + set_resource_parameters(resource, scope) + + return nil unless c = self.code + return c.safeevaluate(scope) + end + + def initialize(type, name, options = {}) + @type = type.to_s.downcase.to_sym + raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_SUPERTYPES.include?(@type) + + name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName) + + set_name_and_namespace(name) + + [:code, :doc, :line, :file, :parent].each do |param| + next unless value = options[param] + send(param.to_s + "=", value) + end + + set_arguments(options[:arguments]) + end + + # This is only used for node names, and really only when the node name + # is a regexp. + def match(string) + return string.to_s.downcase == name unless name_is_regex? + + return @name =~ string + end + + # Add code from a new instance to our code. + def merge(other) + fail ArgumentError, "#{name} is not a class; cannot add code to it" unless type == :hostclass + fail ArgumentError, "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass + + if parent and other.parent and parent != other.parent + fail ArgumentError, "Cannot merge classes with different parent classes" + end + + # We know they're either equal or only one is set, so keep whichever parent is specified. + self.parent ||= other.parent + + if other.doc + self.doc ||= "" + self.doc += other.doc + end + + # This might just be an empty, stub class. + return unless other.code + + unless self.code + self.code = other.code + return + end + + array_class = Puppet::Parser::AST::ASTArray + unless self.code.is_a?(array_class) + self.code = array_class.new(:children => [self.code]) + end + + if other.code.is_a?(array_class) + code.children += other.code.children + else + code.children << other.code + end + end + + # Make an instance of our resource type. This is only possible + # for those classes and nodes that don't have any arguments, and is + # only useful for things like the 'include' function. + def mk_plain_resource(scope) + type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" + resource_type = type == :hostclass ? :class : :node + + # Make sure our parent class has been evaluated, if we have one. + if parent and ! scope.catalog.resource(resource_type, parent) + parent_type.mk_plain_resource(scope) + end + + # Do nothing if the resource already exists; this makes sure we don't + # get multiple copies of the class resource, which helps provide the + # singleton nature of classes. + if resource = scope.catalog.resource(resource_type, name) + return resource + end + + resource = Puppet::Parser::Resource.new(:type => resource_type, :title => name, :scope => scope, :source => self) + scope.compiler.add_resource(scope, resource) + scope.catalog.tag(*resource.tags) + resource + end + + def name + return @name unless @name.is_a?(Regexp) + return @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') + end + + def name_is_regex? + @name.is_a?(Regexp) + end + + def parent_type + return nil unless parent + + unless @parent_type ||= code_collection.send(type, parent) + fail Puppet::ParseError, "Could not find parent resource type '#{parent}'" + end + + @parent_type + end + + # Set any arguments passed by the resource as variables in the scope. + def set_resource_parameters(resource, scope) + set = {} + resource.to_hash.each do |param, value| + param = param.to_sym + fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless validattr?(param) + + exceptwrap { scope.setvar(param.to_s, value) } + + set[param] = true + end + + # Verify that all required arguments are either present or + # have been provided with defaults. + arguments.each do |param, default| + param = param.to_sym + next if set.include?(param) + + # Even if 'default' is a false value, it's an AST value, so this works fine + fail Puppet::ParseError, "Must pass #{param} to #{resource.ref}" unless default + + scope.setvar(param.to_s, default.safeevaluate(scope)) + end + + scope.setvar("title", resource.title) unless set.include? :title + scope.setvar("name", resource.name) unless set.include? :name + scope.class_set(self.name,scope) + end + + # Create a new subscope in which to evaluate our code. + def subscope(scope, resource) + scope.newscope :resource => resource, :namespace => self.namespace, :source => self + end + + # Check whether a given argument is valid. + def validattr?(param) + param = param.to_s + + return true if param == "name" + return true if Puppet::Type.metaparam?(param) + return false unless defined?(@arguments) + return true if arguments.include?(param) + return false + end + + def set_arguments(arguments) + @arguments = {} + return if arguments.nil? + + arguments.each do |arg, default| + arg = arg.to_s + warn_if_metaparam(arg, default) + @arguments[arg] = default + end + end + + private + + def convert_from_ast(name) + value = name.value + if value.is_a?(Puppet::Parser::AST::Regex) + name = value.value + else + name = value + end + end + + # Split an fq name into a namespace and name + def namesplit(fullname) + ary = fullname.split("::") + n = ary.pop || "" + ns = ary.join("::") + return ns, n + end + + def set_name_and_namespace(name) + if name.is_a?(Regexp) + @name = name + @namespace = "" + else + @name = name.to_s.downcase + @namespace, ignored_shortname = namesplit(@name) + end + end + + def warn_if_metaparam(param, default) + return unless Puppet::Type.metaparamclass(param) + + if default + warnonce "#{param} is a metaparam; this value will inherit to all contained resources" + else + raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition" + end + end +end diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb new file mode 100644 index 000000000..7ca95b1b8 --- /dev/null +++ b/lib/puppet/resource/type_collection.rb @@ -0,0 +1,182 @@ +class Puppet::Resource::TypeCollection + attr_reader :environment + + def initialize(env) + @environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env + @hostclasses = {} + @definitions = {} + @nodes = {} + + # So we can keep a list and match the first-defined regex + @node_list = [] + + @watched_files = {} + end + + def <<(thing) + add(thing) + self + end + + def add(instance) + method = "add_#{instance.type}" + send(method, instance) + instance.code_collection = self + instance + end + + def add_hostclass(instance) + dupe_check(instance, @hostclasses) { |dupe| "Class #{instance.name} is already defined#{dupe.error_context}; cannot redefine" } + dupe_check(instance, @definitions) { |dupe| "Definition #{instance.name} is already defined#{dupe.error_context}; cannot be redefined as a class" } + + @hostclasses[instance.name] = instance + instance + end + + def hostclass(name) + @hostclasses[munge_name(name)] + end + + def add_node(instance) + dupe_check(instance, @nodes) { |dupe| "Node #{instance.name} is already defined#{dupe.error_context}; cannot redefine" } + + @node_list << instance + @nodes[instance.name] = instance + instance + end + + def node(name) + name = munge_name(name) + + if node = @nodes[name] + return node + end + + @node_list.each do |node| + next unless node.name_is_regex? + return node if node.match(name) + end + nil + end + + def node_exists?(name) + @nodes[munge_name(name)] + end + + def nodes? + @nodes.length > 0 + end + + def add_definition(code) + @definitions[code.name] = code + end + + def definition(name) + @definitions[munge_name(name)] + end + + def find(namespace, name, type) + if r = find_fully_qualified(name, type) + return r + end + + ary = namespace.split("::") + + while ary.length > 0 + tmp_namespace = ary.join("::") + if r = find_partially_qualified(tmp_namespace, name, type) + return r + end + + # Delete the second to last object, which reduces our namespace by one. + ary.pop + end + + send(type, name) + end + + def find_node(name) + find("", name, :node) + end + + def find_hostclass(namespace, name) + find(namespace, name, :hostclass) + end + + def find_definition(namespace, name) + find(namespace, name, :definition) + end + + [:hostclasses, :nodes, :definitions].each do |m| + define_method(m) do + instance_variable_get("@#{m}").dup + end + end + + def perform_initial_import + parser = Puppet::Parser::Parser.new(environment) + if code = Puppet.settings.uninterpolated_value(:code, environment.to_s) and code != "" + parser.string = code + else + file = Puppet.settings.value(:manifest, environment.to_s) + return unless File.exist?(file) + parser.file = file + end + parser.parse + rescue => detail + msg = "Could not parse for environment #{environment}: #{detail}" + error = Puppet::Error.new(msg) + error.set_backtrace(detail.backtrace) + raise error + end + + def stale? + @watched_files.values.detect { |file| file.changed? } + end + + def version + return @version if defined?(@version) + + if environment[:config_version] == "" + @version = Time.now.to_i + return @version + end + + @version = Puppet::Util.execute([environment[:config_version]]).strip + + rescue Puppet::ExecutionFailure => e + raise Puppet::ParseError, "Unable to set config_version: #{e.message}" + end + + def watch_file(file) + @watched_files[file] = Puppet::Util::LoadedFile.new(file) + end + + def watching_file?(file) + @watched_files.include?(file) + end + + private + + def find_fully_qualified(name, type) + return nil unless name =~ /^::/ + + name = name.sub(/^::/, '') + + send(type, name) + end + + def find_partially_qualified(namespace, name, type) + send(type, [namespace, name].join("::")) + end + + def munge_name(name) + name.to_s.downcase + end + + def dupe_check(instance, hash) + return unless dupe = hash[instance.name] + message = yield dupe + instance.fail Puppet::ParseError, message + end +end diff --git a/lib/puppet/resource/type_collection_helper.rb b/lib/puppet/resource/type_collection_helper.rb new file mode 100644 index 000000000..819cfba0b --- /dev/null +++ b/lib/puppet/resource/type_collection_helper.rb @@ -0,0 +1,7 @@ +require 'puppet/resource/type_collection' + +module Puppet::Resource::TypeCollectionHelper + def known_resource_types + environment.known_resource_types + end +end |
