summaryrefslogtreecommitdiffstats
path: root/lib/puppet/resource
diff options
context:
space:
mode:
authorLuke Kanies <luke@reductivelabs.com>2010-01-07 17:23:31 -0800
committertest branch <puppet-dev@googlegroups.com>2010-02-17 06:50:53 -0800
commitd0389f4d16efbeccf47d6cd2f1b0854ccb1c88d5 (patch)
tree3a3060ac94be20b25742f5ec956e95c8ff3633d8 /lib/puppet/resource
parent67ef78d9f231661d0fdd6260d470cf0d06f1bac2 (diff)
downloadpuppet-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.rb237
-rw-r--r--lib/puppet/resource/type_collection.rb182
-rw-r--r--lib/puppet/resource/type_collection_helper.rb7
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