Class: Puppet::Resource::Type

Inherits:
Object
  • Object
show all
Extended by:
Indirector
Includes:
Util::Errors, 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, :capability_mapping, :application, :site]
RESOURCE_KINDS_TO_EXTERNAL_NAMES =

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

{
    :hostclass => "class",
    :node => "node",
    :definition => "defined_type",
    :application => "application",
    :site => 'site'
}
RESOURCE_EXTERNAL_NAMES_TO_KINDS =
RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert
NAME =
'name'.freeze
TITLE =
'title'.freeze
MODULE_NAME =
'module_name'.freeze
CALLER_MODULE_NAME =
'caller_module_name'.freeze
PARAMETERS =
'parameters'.freeze
KIND =
'kind'.freeze
NODES =
'nodes'.freeze
DOUBLE_COLON =
'::'.freeze
EMPTY_ARRAY =
[].freeze

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

Constructor Details

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

Returns a new instance of Type.

Raises:

  • (ArgumentError)


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/puppet/resource/type.rb', line 187

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:



56
57
58
# File 'lib/puppet/resource/type.rb', line 56

def argument_types
  @argument_types
end

#argumentsObject (readonly)



42
43
44
# File 'lib/puppet/resource/type.rb', line 42

def arguments
  @arguments
end

#behaves_likeObject (readonly)



42
43
44
# File 'lib/puppet/resource/type.rb', line 42

def behaves_like
  @behaves_like
end

#codeObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def code
  @code
end

#docObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def doc
  @doc
end

#fileObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def file
  @file
end

#lineObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def line
  @line
end

#module_nameObject (readonly)



42
43
44
# File 'lib/puppet/resource/type.rb', line 42

def module_name
  @module_name
end

#namespaceObject (readonly)



42
43
44
# File 'lib/puppet/resource/type.rb', line 42

def namespace
  @namespace
end

#parentObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def parent
  @parent
end

#resource_type_collectionObject



41
42
43
# File 'lib/puppet/resource/type.rb', line 41

def resource_type_collection
  @resource_type_collection
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


61
62
63
# File 'lib/puppet/resource/type.rb', line 61

def type
  @type
end

Class Method Details

.from_data_hash(data) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/puppet/resource/type.rb', line 71

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

Instance Method Details

#add_consumes(blueprint) ⇒ Object



221
222
223
224
# File 'lib/puppet/resource/type.rb', line 221

def add_consumes(blueprint)
  @consumes ||= []
  @consumes << blueprint
end

#add_produces(blueprint) ⇒ Object



216
217
218
219
# File 'lib/puppet/resource/type.rb', line 216

def add_produces(blueprint)
  @produces ||= []
  @produces << blueprint
end

#assign_parameter_values(parameters, resource) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deprecated.

Not used by Puppet



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

def assign_parameter_values(parameters, resource)
  Puppet.deprecation_warning('The method Puppet::Resource::Type.assign_parameter_values is deprecated and will be removed in the next major release of Puppet.')

  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)


112
113
114
115
116
# File 'lib/puppet/resource/type.rb', line 112

def child_of?(klass)
  return false unless parent

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

#consumesObject



212
213
214
# File 'lib/puppet/resource/type.rb', line 212

def consumes
  @consumes || EMPTY_ARRAY
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.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/puppet/resource/type.rb', line 268

def ensure_in_catalog(scope, parameters=nil)
  resource_type =
  case type
  when :definition
    raise ArgumentError, 'Cannot create resources for defined resource types'
  when :hostclass
    :class
  when :node
    :node
  when :site
    :site
  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.
  # 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 parameters.nil?
    resource = scope.catalog.resource(resource_type, name)
    return resource unless resource.nil?
  elsif parameters.is_a?(Hash)
    parameters = parameters.map {|k, v| Puppet::Parser::Resource::Param.new(:name => k, :value => v, :source => self)}
  end
  resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self, :parameters => parameters)
  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.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/puppet/resource/type.rb', line 161

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

  evaluate_produces(resource, scope)

  if code
    if @match # Only bother setting up the ephemeral scope if there are match variables to add into it
      scope.with_guarded_scope do
        scope.ephemeral_from(@match, file, line)
        code.safeevaluate(scope)
      end
    else
      code.safeevaluate(scope)
    end
  end
end

#evaluate_produces(resource, scope) ⇒ Object

Evaluate the resources produced by the given resource. These resources are evaluated in a separate but identical scope from the rest of the resource.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/puppet/resource/type.rb', line 120

def evaluate_produces(resource, scope)
  # Only defined types and classes can produce capabilities
  return unless definition? || hostclass?

  resource.export.map do |ex|
    # Assert that the ref really is a resource reference
    raise Puppet::Error, "Invalid export in #{resource.ref}: #{ex} is not a resource" unless ex.is_a?(Puppet::Resource)
    raise Puppet::Error, "Invalid export in #{resource.ref}: #{ex} is not a capability resource" if ex.resource_type.nil? || !ex.resource_type.is_capability?

    blueprint = produces.find { |pr| pr[:capability] == ex.type }
    if blueprint.nil?
      raise Puppet::ParseError, "Resource type #{resource.type} does not produce #{ex.type}"
    end
    produced_resource = Puppet::Parser::Resource.new(ex.type, ex.title, :scope => scope, :source => self)

    produced_resource.resource_type.parameters.each do |name|
      next if name == :name

      if expr = blueprint[:mappings][name.to_s]
        produced_resource[name] = expr.safeevaluate(scope)
      else
        produced_resource[name] = scope[name.to_s]
      end
    end
    # Tag the produced resource so we can later distinguish it from
    # copies of the resource that wind up in the catalogs of nodes that
    # use this resource. We tag the resource with producer:<environment>,
    # meaning produced resources need to be unique within their
    # environment
    # @todo lutter 2014-11-13: we would really like to use a dedicated
    # metadata field to indicate the producer of a resource, but that
    # requires changes to PuppetDB and its API; so for now, we just use
    # tagging
    produced_resource.tag("producer:#{scope.catalog.environment}")
    scope.catalog.add_resource(produced_resource)
    produced_resource[:require] = resource.ref
    produced_resource
  end
end

#instantiate_resource(scope, resource) ⇒ Object



300
301
302
303
304
305
306
307
308
309
# File 'lib/puppet/resource/type.rb', line 300

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

#is_capability?Boolean

Returns boolean true if an instance of this type is a capability. This implementation always returns false. This “duck-typing” interface is shared among other classes and makes it easier to detect capabilities when they are intermixed with non capability instances.

Returns:

  • (Boolean)


474
475
476
# File 'lib/puppet/resource/type.rb', line 474

def is_capability?
  false
end

#match(string) ⇒ Object

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



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

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.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/puppet/resource/type.rb', line 235

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])
end

#nameObject



311
312
313
314
315
316
317
# File 'lib/puppet/resource/type.rb', line 311

def name
  if type == :node && name_is_regex?
    "__node_regexp__#{@name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')}"
  else
    @name
  end
end

#name_is_regex?Boolean

Returns:

  • (Boolean)


319
320
321
# File 'lib/puppet/resource/type.rb', line 319

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

#parent_type(scope = nil) ⇒ Object



338
339
340
341
342
343
# File 'lib/puppet/resource/type.rb', line 338

def parent_type(scope = nil)
  return nil unless parent

  @parent_type ||= scope.environment.known_resource_types.send("find_#{type}", parent) ||
    fail(Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}")
end

#producesObject



208
209
210
# File 'lib/puppet/resource/type.rb', line 208

def produces
  @produces || EMPTY_ARRAY
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.



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/puppet/resource/type.rb', line 454

def set_argument_types(name_to_type_hash)
  @argument_types = {}
  @parameter_struct = nil
  return unless 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



438
439
440
441
442
443
444
445
446
447
448
# File 'lib/puppet/resource/type.rb', line 438

def set_arguments(arguments)
  @arguments = {}
  @parameter_struct = nil
  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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validate and set any arguments passed by the resource as variables in the scope.

This method is known to only be used on the server/compile side.

Parameters:



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/puppet/resource/type.rb', line 353

def set_resource_parameters(resource, scope)
  # Inject parameters from using external lookup
  modname = resource[:module_name] || module_name
  scope[MODULE_NAME] = modname unless modname.nil?
  caller_name = resource[:caller_module_name] || scope.parent_module_name
  scope[CALLER_MODULE_NAME] = caller_name unless caller_name.nil?

  resource.add_parameters_from_consume
  inject_external_parameters(resource, scope)

  if @type == :hostclass
    scope[TITLE] = resource.title.to_s.downcase
    scope[NAME] =  resource.name.to_s.downcase
  else
    scope[TITLE] = resource.title
    scope[NAME] =  resource.name
  end
  scope.class_set(self.name,scope) if hostclass? || node?

  param_hash = scope.with_parameter_scope(arguments.keys) do |param_scope|
    # Assign directly to the parameter scope to avoid scope parameter validation at this point. It
    # will happen anyway when the values are assigned to the scope after the parameter scoped has
    # been popped.
    resource.each { |k, v| param_scope[k.to_s] = v.value unless k == :name || k == :title }
    assign_defaults(resource, param_scope, scope)
    param_scope.to_hash
  end

  validate_resource_hash(resource, param_hash)

  # Assign parameter values to current scope
  param_hash.each { |param, value| exceptwrap { scope[param] = value }}
end

#to_data_hashObject



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/puppet/resource/type.rb', line 88

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"
  # Dump any arguments as source
  data[PARAMETERS] = Hash[arguments.map do |k,v|
                              [k, v.respond_to?(:source_text) ? v.source_text : v]
                            end]
  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)


434
435
436
# File 'lib/puppet/resource/type.rb', line 434

def valid_parameter?(param)
  parameter_struct.hashed_elements.include?(param.to_s)
end

#validate_resource(resource) ⇒ Object

Validate that all parameters given to the resource are correct

Parameters:



429
430
431
# File 'lib/puppet/resource/type.rb', line 429

def validate_resource(resource)
  validate_resource_hash(resource, Hash[resource.parameters.map { |name, value| [name.to_s, value.value] }])
end