Class: Puppet::Resource::Type

Inherits:
Object
  • Object
show all
Extended by:
Indirector
Includes:
Util::Errors, Util::InlineDocs, Util::Warnings
Defined in:
lib/puppet/resource/type.rb

Overview

Puppet::Resource::Type represents nodes, classes and defined types.

It has a standard format for external consumption, usable from the resource_type indirection via rest and the resource_type face. See the resource type schema description.

Constant Summary collapse

RESOURCE_KINDS =
[:hostclass, :node, :definition]
RESOURCE_KINDS_TO_EXTERNAL_NAMES =

Map the names used in our documentation to the names used internally

{
    :hostclass => "class",
    :node => "node",
    :definition => "defined_type",
}
RESOURCE_EXTERNAL_NAMES_TO_KINDS =
RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert

Constants included from Indirector

Indirector::BadNameRegexp

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Indirector

configure_routes, indirects

Methods included from Util::Errors

#adderrorcontext, #devfail, #error_context, #exceptwrap, #fail

Methods included from Util::Warnings

clear_warnings, debug_once, notice_once, warnonce

Methods included from Util::InlineDocs

included

Constructor Details

#initialize(type, name, options = {}) ⇒ Type

Returns a new instance of Type.

Raises:

  • (ArgumentError)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/puppet/resource/type.rb', line 134

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])
  set_argument_types(options[:argument_types])

  @match = nil

  @module_name = options[:module_name]
end

Instance Attribute Details

#argument_typesHash<Symbol, Puppet::Pops::Types::PAnyType] map from name to type (readonly)

Map from argument (aka parameter) names to Puppet Type

Returns:



39
40
41
# File 'lib/puppet/resource/type.rb', line 39

def argument_types
  @argument_types
end

#argumentsObject (readonly)



34
35
36
# File 'lib/puppet/resource/type.rb', line 34

def arguments
  @arguments
end

#behaves_likeObject (readonly)



34
35
36
# File 'lib/puppet/resource/type.rb', line 34

def behaves_like
  @behaves_like
end

#codeObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def code
  @code
end

#docObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def doc
  @doc
end

#fileObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def file
  @file
end

#lineObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def line
  @line
end

#module_nameObject (readonly)



34
35
36
# File 'lib/puppet/resource/type.rb', line 34

def module_name
  @module_name
end

#namespaceObject (readonly)



34
35
36
# File 'lib/puppet/resource/type.rb', line 34

def namespace
  @namespace
end

#parentObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def parent
  @parent
end

#resource_type_collectionObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def resource_type_collection
  @resource_type_collection
end

#ruby_codeObject



33
34
35
# File 'lib/puppet/resource/type.rb', line 33

def ruby_code
  @ruby_code
end

#typeObject (readonly)

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


44
45
46
# File 'lib/puppet/resource/type.rb', line 44

def type
  @type
end

Class Method Details

.from_data_hash(data) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/puppet/resource/type.rb', line 54

def self.from_data_hash(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 }

  # External documentation uses "parameters" but the internal name
  # is "arguments"
  data[:arguments] = data.delete(:parameters)

  new(type, name, data)
end

.from_pson(data) ⇒ Object



71
72
73
74
# File 'lib/puppet/resource/type.rb', line 71

def self.from_pson(data)
  Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.")
  self.from_data_hash(data)
end

Instance Method Details

#assign_parameter_values(parameters, resource) ⇒ Object



239
240
241
242
243
244
245
246
247
248
# File 'lib/puppet/resource/type.rb', line 239

def assign_parameter_values(parameters, resource)
  return unless parameters

  # 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

#child_of?(klass) ⇒ Boolean

Are we a child of the passed class? Do a recursive search up our parentage tree to figure it out.

Returns:

  • (Boolean)


98
99
100
101
102
# File 'lib/puppet/resource/type.rb', line 98

def child_of?(klass)
  return false unless parent

  return(klass == parent_type ? true : parent_type.child_of?(klass))
end

#ensure_in_catalog(scope, parameters = nil) ⇒ Object

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.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/puppet/resource/type.rb', line 198

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

#evaluate_code(resource) ⇒ Object

Now evaluate the code associated with this class or definition.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/puppet/resource/type.rb', line 105

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

  if code
    if @match # Only bother setting up the ephemeral scope if there are match variables to add into it
      begin
        elevel = scope.ephemeral_level
        scope.ephemeral_from(@match, file, line)
        code.safeevaluate(scope)
      ensure
        scope.unset_ephemeral_var(elevel)
      end
    else
      code.safeevaluate(scope)
    end
  end

  evaluate_ruby_code(resource, scope) if ruby_code
end

#instantiate_resource(scope, resource) ⇒ Object



219
220
221
222
223
224
225
226
227
228
# File 'lib/puppet/resource/type.rb', line 219

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

#match(string) ⇒ Object

This is only used for node names, and really only when the node name is a regexp.



157
158
159
160
161
# File 'lib/puppet/resource/type.rb', line 157

def match(string)
  return string.to_s.downcase == name unless name_is_regex?

  @match = @name.match(string)
end

#merge(other) ⇒ Object

Add code from a new instance to our code.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/puppet/resource/type.rb', line 164

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

  self.code = Puppet::Parser::ParserFactory.code_merger.concatenate([self, other])
#    self.code = self.code.sequence_with(other.code)
end

#nameObject



230
231
232
233
# File 'lib/puppet/resource/type.rb', line 230

def name
  return @name unless @name.is_a?(Regexp)
  @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')
end

#name_is_regex?Boolean

Returns:

  • (Boolean)


235
236
237
# File 'lib/puppet/resource/type.rb', line 235

def name_is_regex?
  @name.is_a?(Regexp)
end

#parent_type(scope = nil) ⇒ Object

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.



267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/puppet/resource/type.rb', line 267

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

#set_argument_types(name_to_type_hash) ⇒ Object

Sets the argument name to Puppet Type hash used for type checking. Names must correspond to available arguments (they must be defined first). Arguments not mentioned will not be type-checked. Only supported when parser == “future”



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/puppet/resource/type.rb', line 340

def set_argument_types(name_to_type_hash)
  @argument_types = {}
  # Stop here if not running under future parser, the rest requires pops to be initialized
  # and that the type system is available
  return unless Puppet[:parser] == 'future' && name_to_type_hash
  name_to_type_hash.each do |name, t|
    # catch internal errors
    unless @arguments.include?(name)
      raise Puppet::DevError, "Parameter '#{name}' is given a type, but is not a valid parameter."
    end
    unless t.is_a? Puppet::Pops::Types::PAnyType
      raise Puppet::DevError, "Parameter '#{name}' is given a type that is not a Puppet Type, got #{t.class}"
    end
    @argument_types[name] = t
  end
end

#set_arguments(arguments) ⇒ Object



325
326
327
328
329
330
331
332
333
334
# File 'lib/puppet/resource/type.rb', line 325

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_resource_parameters(resource, scope) ⇒ Object

Set any arguments passed by the resource as variables in the scope.



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/puppet/resource/type.rb', line 281

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

#to_data_hashObject



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/puppet/resource/type.rb', line 76

def to_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

  # External documentation uses "parameters" but the internal name
  # is "arguments"
  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

#valid_parameter?(param) ⇒ Boolean

Check whether a given argument is valid.

Returns:

  • (Boolean)


316
317
318
319
320
321
322
323
# File 'lib/puppet/resource/type.rb', line 316

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