We have reached a point where we’ve established some naming conventions
in our documentation that don't entirely match up with our internal names for things. Ideally we'd change the internal representation to match the conventions expressed in our docs, but that would be a fairly far-reaching and risky change. For the time being, we're settling for mapping the internal names to the external ones (and vice-versa) during serialization and deserialization. These two hashes is here to help with that mapping.
This should probably be renamed to ‘kind’ eventually, in accordance with the changes
made for serialization and API usability (#14137). At the moment that seems like it would touch a whole lot of places in the code, though. --cprice 2012-04-23
# File lib/puppet/resource/type.rb, line 46 def self.from_pson(data) name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified" kind = data.delete('kind') || "definition" unless type = RESOURCE_EXTERNAL_NAMES_TO_KINDS[kind] raise ArgumentError, "Unsupported resource kind '#{kind}'" end data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result } # This is a bit of a hack; when we serialize, we use the term "parameters" because that # is the terminology that we use in our documentation. However, internally to this # class we use the term "arguments". Ideally we'd change the implementation to be consistent # with the documentation, but that would be challenging right now because it could potentially # touch a lot of places in the code, not to mention that we already have another meaning for # "parameters" internally. So, for now, we will simply transform the internal "arguments" # value to "parameters" when serializing, and the opposite when deserializing. # --cprice 2012-04-23 data[:arguments] = data.delete(:parameters) new(type, name, data) end
# File lib/puppet/resource/type.rb, line 141 def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_KINDS.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]) @module_name = options[:module_name] end
# File lib/puppet/resource/type.rb, line 249 def assign_parameter_values(parameters, resource) return unless parameters scope = resource.scope || {} # It'd be nice to assign default parameter values here, # but we can't because they often rely on local variables # created during set_resource_parameters. parameters.each do |name, value| resource.set_parameter name, value end end
Are we a child of the passed class? Do a recursive search up our parentage tree to figure it out.
# File lib/puppet/resource/type.rb, line 117 def child_of?(klass) return false unless parent return(klass == parent_type ? true : parent_type.child_of?(klass)) end
Make an instance of the resource type, and place it in the catalog if it isn’t in the catalog already. This is only possible for classes and nodes. No parameters are be supplied–if this is a parameterized class, then all parameters take on their default values.
# File lib/puppet/resource/type.rb, line 208 def ensure_in_catalog(scope, parameters=nil) type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" resource_type = type == :hostclass ? :class : :node # 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. # we should not do this for classes with parameters # if parameters are passed, we should still try to create the resource # even if it exists so that we can fail # this prevents us from being able to combine param classes with include if resource = scope.catalog.resource(resource_type, name) and !parameters return resource end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self) assign_parameter_values(parameters, resource) instantiate_resource(scope, resource) scope.compiler.add_resource(scope, resource) resource end
Now evaluate the code associated with this class or definition.
# File lib/puppet/resource/type.rb, line 124 def evaluate_code(resource) static_parent = evaluate_parent_type(resource) scope = static_parent || resource.scope scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) resource.add_edge_to_stage code.safeevaluate(scope) if code evaluate_ruby_code(resource, scope) if ruby_code end
# File lib/puppet/resource/type.rb, line 229 def instantiate_resource(scope, resource) # Make sure our parent class has been evaluated, if we have one. if parent && !scope.catalog.resource(resource.type, parent) parent_type(scope).ensure_in_catalog(scope) end if ['Class', 'Node'].include? resource.type scope.catalog.tag(*resource.tags) end end
This is only used for node names, and really only when the node name is a regexp.
# File lib/puppet/resource/type.rb, line 161 def match(string) return string.to_s.downcase == name unless name_is_regex? @name =~ string end
Add code from a new instance to our code.
# File lib/puppet/resource/type.rb, line 168 def merge(other) fail "#{name} is not a class; cannot add code to it" unless type == :hostclass fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main] if parent and other.parent and parent != other.parent fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})" 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 self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class) if other.code.is_a?(array_class) code.children += other.code.children else code.children << other.code end end
# File lib/puppet/resource/type.rb, line 240 def name return @name unless @name.is_a?(Regexp) @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end
# File lib/puppet/resource/type.rb, line 245 def name_is_regex? @name.is_a?(Regexp) end
MQR TODO:
The change(s) introduced by the fix for #4270 are mostly silly & should be removed, though we didn’t realize it at the time. If it can be established/ ensured that nodes never call #parent_type and that resource_types are always (as they should be) members of exactly one #resource_type_collection the following method could / should be replaced with:
def #parent_type
@parent_type ||= parent && (
resource_type_collection.find_or_load([name],parent,type.to_sym) ||
fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}"
)
end
…and then the rest of the changes around passing in scope reverted.
# File lib/puppet/resource/type.rb, line 278 def parent_type(scope = nil) return nil unless parent unless @parent_type raise "Must pass scope to parent_type when called first time" unless scope unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent) fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}" end end @parent_type end
# File lib/puppet/resource/type.rb, line 336 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
Set any arguments passed by the resource as variables in the scope.
# File lib/puppet/resource/type.rb, line 292 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 valid_parameter?(param) exceptwrap { scope[param.to_s] = value } set[param] = true end if @type == :hostclass scope["title"] = resource.title.to_s.downcase unless set.include? :title scope["name"] = resource.name.to_s.downcase unless set.include? :name else scope["title"] = resource.title unless set.include? :title scope["name"] = resource.name unless set.include? :name end scope["module_name"] = module_name if module_name and ! set.include? :module_name if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name) scope["caller_module_name"] = caller_name end scope.class_set(self.name,scope) if hostclass? or node? # Evaluate the default parameters, now that all other variables are set default_params = resource.set_default_parameters(scope) default_params.each { |param| scope[param] = resource[param] } # This has to come after the above parameters so that default values # can use their values resource.validate_complete end
It seems wrong that we have a ‘to_pson’ method on this class, but not a ‘to_yaml’.
As a result, if you use the REST API to retrieve one or more objects of this type, you will receive different data if you use 'Accept: yaml' vs 'Accept: pson'. That seems really, really wrong. The "Accept" header should never affect what data is being returned--only the format of the data. If the data itself is going to differ, then there should be a different request URL. Documenting the REST API becomes a much more complex problem when the "Accept" header can change the semantics of the response. --cprice 2012-04-23
# File lib/puppet/resource/type.rb, line 111 def to_pson(*args) to_pson_data_hash.to_pson(*args) end
This method doesn’t seem like it has anything to do with PSON in particular, and it shouldn’t.
It's just transforming to a simple object that can be serialized and de-serialized via any transport format. Should probably be renamed if we get a chance to clean up our serialization / deserialization, and there are probably many other similar methods in other classes. --cprice 2012-04-23
# File lib/puppet/resource/type.rb, line 76 def to_pson_data_hash data = [:doc, :line, :file, :parent].inject({}) do |hash, param| next hash unless (value = self.send(param)) and (value != "") hash[param.to_s] = value hash end # This is a bit of a hack; when we serialize, we use the term "parameters" because that # is the terminology that we use in our documentation. However, internally to this # class we use the term "arguments". Ideally we'd change the implementation to be consistent # with the documentation, but that would be challenging right now because it could potentially # touch a lot of places in the code, not to mention that we already have another meaning for # "parameters" internally. So, for now, we will simply transform the internal "arguments" # value to "parameters" when serializing, and the opposite when deserializing. # --cprice 2012-04-23 data['parameters'] = arguments.dup unless arguments.empty? data['name'] = name unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type) raise ArgumentError, "Unsupported resource kind '#{type}'" end data['kind'] = RESOURCE_KINDS_TO_EXTERNAL_NAMES[type] data end
Check whether a given argument is valid.
# File lib/puppet/resource/type.rb, line 327 def valid_parameter?(param) param = param.to_s return true if param == "name" return true if Puppet::Type.metaparam?(param) return false unless defined?(@arguments) return(arguments.include?(param) ? true : false) end