/
type.rb
2670 lines (2306 loc) · 100 KB
/
type.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
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
159
160
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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
262
263
264
265
266
267
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/logging'
require 'puppet/util/tagging'
# see the bottom of the file for the rest of the inclusions
module Puppet
# The base class for all Puppet types.
#
# A type describes:
#--
# * **Attributes** - properties, parameters, and meta-parameters are different types of attributes of a type.
# * **Properties** - these are the properties of the managed resource (attributes of the entity being managed; like
# a file's owner, group and mode). A property describes two states; the 'is' (current state) and the 'should' (wanted
# state).
# * **Ensurable** - a set of traits that control the lifecycle (create, remove, etc.) of a managed entity.
# There is a default set of operations associated with being _ensurable_, but this can be changed.
# * **Name/Identity** - one property is the name/identity of a resource, the _namevar_ that uniquely identifies
# one instance of a type from all others.
# * **Parameters** - additional attributes of the type (that does not directly related to an instance of the managed
# resource; if an operation is recursive or not, where to look for things, etc.). A Parameter (in contrast to Property)
# has one current value where a Property has two (current-state and wanted-state).
# * **Meta-Parameters** - parameters that are available across all types. A meta-parameter typically has
# additional semantics; like the `require` meta-parameter. A new type typically does not add new meta-parameters,
# but you need to be aware of their existence so you do not inadvertently shadow an existing meta-parameters.
# * **Parent** - a type can have a super type (that it inherits from).
# * **Validation** - If not just a basic data type, or an enumeration of symbolic values, it is possible to provide
# validation logic for a type, properties and parameters.
# * **Munging** - munging/unmunging is the process of turning a value in external representation (as used
# by a provider) into an internal representation and vice versa. A Type supports adding custom logic for these.
# * **Auto Requirements** - a type can specify automatic relationships to resources to ensure that if they are being
# managed, they will be processed before this type.
# * **Providers** - a provider is an implementation of a type's behavior - the management of a resource in the
# system being managed. A provider is often platform specific and is selected at runtime based on
# criteria/predicates specified in the configured providers. See {Puppet::Provider} for details.
# * **Device Support** - A type has some support for being applied to a device; i.e. something that is managed
# by running logic external to the device itself. There are several methods that deals with type
# applicability for these special cases such as {apply_to_device}.
#
# Additional Concepts:
# --
# * **Resource-type** - A _resource type_ is a term used to denote the type of a resource; internally a resource
# is really an instance of a Ruby class i.e. {Puppet::Resource} which defines its behavior as "resource data".
# Conceptually however, a resource is an instance of a subclass of Type (e.g. File), where such a class describes
# its interface (what can be said/what is known about a resource of this type),
# * **Managed Entity** - This is not a term in general use, but is used here when there is a need to make
# a distinction between a resource (a description of what/how something should be managed), and what it is
# managing (a file in the file system). The term _managed entity_ is a reference to the "file in the file system"
# * **Isomorphism** - the quality of being _isomorphic_ means that two resource instances with the same name
# refers to the same managed entity. Or put differently; _an isomorphic name is the identity of a resource_.
# As an example, `exec` resources (that executes some command) have the command (i.e. the command line string) as
# their name, and these resources are said to be non-isomorphic.
#
# @note The Type class deals with multiple concerns; some methods provide an internal DSL for convenient definition
# of types, other methods deal with various aspects while running; wiring up a resource (expressed in Puppet DSL)
# with its _resource type_ (i.e. an instance of Type) to enable validation, transformation of values
# (munge/unmunge), etc. Lastly, Type is also responsible for dealing with Providers; the concrete implementations
# of the behavior that constitutes how a particular Type behaves on a particular type of system (e.g. how
# commands are executed on a flavor of Linux, on Windows, etc.). This means that as you are reading through the
# documentation of this class, you will be switching between these concepts, as well as switching between
# the conceptual level "a resource is an instance of a resource-type" and the actual implementation classes
# (Type, Resource, Provider, and various utility and helper classes).
#
# @api public
#
#
class Type
extend Puppet::CompilableResourceType
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Util::Tagging
# Comparing type instances.
include Comparable
# Compares this type against the given _other_ (type) and returns -1, 0, or +1 depending on the order.
# @param other [Object] the object to compare against (produces nil, if not kind of Type}
# @return [-1, 0, +1, nil] produces -1 if this type is before the given _other_ type, 0 if equals, and 1 if after.
# Returns nil, if the given _other_ is not a kind of Type.
# @see Comparable
#
def <=>(other)
# Order is only maintained against other types, not arbitrary objects.
# The natural order is based on the reference name used when comparing
return nil unless other.is_a?(Puppet::CompilableResourceType) || other.class.is_a?(Puppet::CompilableResourceType)
# against other type instances.
self.ref <=> other.ref
end
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
# @return [Array<Puppet::Property>] The list of declared properties for the resource type.
# The returned lists contains instances if Puppet::Property or its subclasses.
attr_reader :properties
end
# Allow declaring that a type is actually a capability
class << self
attr_accessor :is_capability
def is_capability?
c = is_capability
c.nil? ? false : c
end
end
# Returns whether this type represents an application instance; since
# only defined types, i.e., instances of Puppet::Resource::Type can
# represent application instances, this implementation always returns
# +false+. Having this method though makes code checking whether a
# resource is an application instance simpler
def self.application?
false
end
# Returns all the attribute names of the type in the appropriate order.
# The {key_attributes} come first, then the {provider}, then the {properties}, and finally
# the {parameters} and {metaparams},
# all in the order they were specified in the respective files.
# @return [Array<String>] all type attribute names in a defined order.
#
def self.allattrs
key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams
end
# Returns the class associated with the given attribute name.
# @param name [String] the name of the attribute to obtain the class for
# @return [Class, nil] the class for the given attribute, or nil if the name does not refer to an existing attribute
#
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property; @validproperties[name]
when :meta; @@metaparamhash[name]
when :param; @paramhash[name]
end
end
@attrclasses[name]
end
# Returns the attribute type (`:property`, `;param`, `:meta`).
# @comment What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
# @return [Symbol] a symbol describing the type of attribute (`:property`, `;param`, `:meta`)
#
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr); :property
when @paramhash.include?(attr); :param
when @@metaparamhash.include?(attr); :meta
end
end
@attrtypes[attr]
end
# Provides iteration over meta-parameters.
# @yieldparam p [Puppet::Parameter] each meta parameter
# @return [void]
#
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Creates a new `ensure` property with configured default values or with configuration by an optional block.
# This method is a convenience method for creating a property `ensure` with default accepted values.
# If no block is specified, the new `ensure` property will accept the default symbolic
# values `:present`, and `:absent` - see {Puppet::Property::Ensure}.
# If something else is wanted, pass a block and make calls to {Puppet::Property.newvalue} from this block
# to define each possible value. If a block is passed, the defaults are not automatically added to the set of
# valid values.
#
# @note This method will be automatically called without a block if the type implements the methods
# specified by {ensurable?}. It is recommended to always call this method and not rely on this automatic
# specification to clearly state that the type is ensurable.
#
# @overload ensurable()
# @overload ensurable({|| ... })
# @yield [ ] A block evaluated in scope of the new Parameter
# @yieldreturn [void]
# @return [void]
# @dsl type
# @api public
#
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Returns true if the type implements the default behavior expected by being _ensurable_ "by default".
# A type is _ensurable_ by default if it responds to `:exists`, `:create`, and `:destroy`.
# If a type implements these methods and have not already specified that it is _ensurable_, it will be
# made so with the defaults specified in {ensurable}.
# @return [Boolean] whether the type is _ensurable_ or not.
#
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
[:exists?, :create, :destroy].all? { |method|
self.public_method_defined?(method)
}
end
# @comment These `apply_to` methods are horrible. They should really be implemented
# as part of the usual system of constraints that apply to a type and
# provider pair, but were implemented as a separate shadow system.
#
# @comment We should rip them out in favour of a real constraint pattern around the
# target device - whatever that looks like - and not have this additional
# magic here. --daniel 2012-03-08
#
# Makes this type applicable to `:device`.
# @return [Symbol] Returns `:device`
# @api private
#
def self.apply_to_device
@apply_to = :device
end
# Makes this type applicable to `:host`.
# @return [Symbol] Returns `:host`
# @api private
#
def self.apply_to_host
@apply_to = :host
end
# Makes this type applicable to `:both` (i.e. `:host` and `:device`).
# @return [Symbol] Returns `:both`
# @api private
#
def self.apply_to_all
@apply_to = :both
end
# Makes this type apply to `:host` if not already applied to something else.
# @return [Symbol] a `:device`, `:host`, or `:both` enumeration
# @api private
def self.apply_to
@apply_to ||= :host
end
# Returns true if this type is applicable to the given target.
# @param target [Symbol] should be :device, :host or :target, if anything else, :host is enforced
# @return [Boolean] true
# @api private
#
def self.can_apply_to(target)
[ target == :device ? :device : :host, :both ].include?(apply_to)
end
# Processes the options for a named parameter.
# @param name [String] the name of a parameter
# @param options [Hash] a hash of options
# @option options [Boolean] :boolean if option set to true, an access method on the form _name_? is added for the param
# @return [void]
#
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the given parameter a meta-parameter?
# @return [Boolean] true if the given parameter is a meta-parameter.
#
def self.metaparam?(param)
@@metaparamhash.include?(param.intern)
end
# Returns the meta-parameter class associated with the given meta-parameter name.
# Accepts a `nil` name, and return nil.
# @param name [String, nil] the name of a meta-parameter
# @return [Class,nil] the class for the given meta-parameter, or `nil` if no such meta-parameter exists, (or if
# the given meta-parameter name is `nil`.
#
def self.metaparamclass(name)
return nil if name.nil?
@@metaparamhash[name.intern]
end
# Returns all meta-parameter names.
# @return [Array<String>] all meta-parameter names
#
def self.metaparams
@@metaparams.collect { |param| param.name }
end
# Returns the documentation for a given meta-parameter of this type.
# @param metaparam [Puppet::Parameter] the meta-parameter to get documentation for.
# @return [String] the documentation associated with the given meta-parameter, or nil of no such documentation
# exists.
# @raise if the given metaparam is not a meta-parameter in this type
#
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Creates a new meta-parameter.
# This creates a new meta-parameter that is added to this and all inheriting types.
# @param name [Symbol] the name of the parameter
# @param options [Hash] a hash with options.
# @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter
# @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class
# by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given
# block is evaluated.
# @option options [Boolean] :boolean (false) specifies if this is a boolean parameter
# @option options [Boolean] :namevar (false) specifies if this parameter is the namevar
# @option options [Symbol, Array<Symbol>] :required_features specifies required provider features by name
# @return [Class<inherits Puppet::Parameter>] the created parameter
# @yield [ ] a required block that is evaluated in the scope of the new meta-parameter
# @api public
# @dsl type
# @todo Verify that this description is ok
#
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = name.intern
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
handle_param_options(name, options)
param.metaparam = true
param
end
# Returns the list of parameters that comprise the composite key / "uniqueness key".
# All parameters that return true from #isnamevar? or is named `:name` are included in the returned result.
# @see uniqueness_key
# @return [Array<Puppet::Parameter>] WARNING: this return type is uncertain
def self.key_attribute_parameters
@key_attribute_parameters ||= (
@parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
)
end
# Returns cached {key_attribute_parameters} names.
# Key attributes are properties and parameters that comprise a composite key
# or "uniqueness key".
# @return [Array<String>] cached key_attribute names
#
def self.key_attributes
# This is a cache miss around 0.05 percent of the time. --daniel 2012-07-17
@key_attributes_cache ||= key_attribute_parameters.collect { |p| p.name }
end
# Returns a mapping from the title string to setting of attribute value(s).
# This default implementation provides a mapping of title to the one and only _namevar_ present
# in the type's definition.
# @note Advanced: some logic requires this mapping to be done differently, using a different
# validation/pattern, breaking up the title
# into several parts assigning each to an individual attribute, or even use a composite identity where
# all namevars are seen as part of the unique identity (such computation is done by the {#uniqueness} method.
# These advanced options are rarely used (only one of the built in puppet types use this, and then only
# a small part of the available functionality), and the support for these advanced mappings is not
# implemented in a straight forward way. For these reasons, this method has been marked as private).
#
# @raise [Puppet::DevError] if there is no title pattern and there are two or more key attributes
# @return [Array<Array<Regexp, Array<Array <Symbol, Proc>>>>, nil] a structure with a regexp and the first key_attribute ???
# @comment This wonderful piece of logic creates a structure used by Resource.parse_title which
# has the capability to assign parts of the title to one or more attributes; It looks like an implementation
# of a composite identity key (all parts of the key_attributes array are in the key). This can also
# be seen in the method uniqueness_key.
# The implementation in this method simply assigns the title to the one and only namevar (which is name
# or a variable marked as namevar).
# If there are multiple namevars (any in addition to :name?) then this method MUST be implemented
# as it raises an exception if there is more than 1. Note that in puppet, it is only File that uses this
# to create a different pattern for assigning to the :path attribute
# This requires further digging.
# The entire construct is somewhat strange, since resource checks if the method "title_patterns" is
# implemented (it seems it always is) - why take this more expensive regexp mathching route for all
# other types?
# @api private
#
def self.title_patterns
case key_attributes.length
when 0; []
when 1;
[ [ /(.*)/m, [ [key_attributes.first] ] ] ]
else
raise Puppet::DevError, _("you must specify title patterns when there are two or more key attributes")
end
end
# Produces a resource's _uniqueness_key_ (or composite key).
# This key is an array of all key attributes' values. Each distinct tuple must be unique for each resource type.
# @see key_attributes
# @return [Object] an object that is a _uniqueness_key_ for this object
#
def uniqueness_key
self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] }
end
# Creates a new parameter.
# @param name [Symbol] the name of the parameter
# @param options [Hash] a hash with options.
# @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter
# @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class
# by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given
# block is evaluated.
# @option options [Boolean] :boolean (false) specifies if this is a boolean parameter
# @option options [Boolean] :namevar (false) specifies if this parameter is the namevar
# @option options [Symbol, Array<Symbol>] :required_features specifies required provider features by name
# @return [Class<inherits Puppet::Parameter>] the created parameter
# @yield [ ] a required block that is evaluated in the scope of the new parameter
# @api public
# @dsl type
#
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
param.isnamevar if options[:namevar]
param
end
# Creates a new property.
# @param name [Symbol] the name of the property
# @param options [Hash] a hash with options.
# @option options [Symbol] :array_matching (:first) specifies how the current state is matched against
# the wanted state. Use `:first` if the property is single valued, and (`:all`) otherwise.
# @option options [Class<inherits Puppet::Property>] :parent (Puppet::Property) the super class of this property
# @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class
# by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given
# block is evaluated.
# @option options [Boolean] :boolean (false) specifies if this is a boolean parameter
# @option options [Symbol] :retrieve the method to call on the provider (or `parent` if `provider` is not set)
# to retrieve the current value of this property.
# @option options [Symbol, Array<Symbol>] :required_features specifies required provider features by name
# @return [Class<inherits Puppet::Property>] the created property
# @yield [ ] a required block that is evaluated in the scope of the new property
# @api public
# @dsl type
#
def self.newproperty(name, options = {}, &block)
name = name.intern
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError, _("Options must be a hash, not %{type}") % { type: options.inspect }
end
raise Puppet::DevError, _("Class %{class_name} already has a property named %{property}") % { class_name: self.name, property: name } if @validproperties.include?(name)
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
class_eval(&block) if block
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# @return [Array<String>] Returns the parameter names
def self.parameters
return [] unless defined?(@parameters)
@parameters.collect { |klass| klass.name }
end
# @return [Puppet::Parameter] Returns the parameter class associated with the given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# @return [Puppet::Property] Returns the property class ??? associated with the given property name
def self.propertybyname(name)
@validproperties[name]
end
# Returns whether or not the given name is the name of a property, parameter or meta-parameter
# @return [Boolean] true if the given attribute name is the name of an existing property, parameter or meta-parameter
#
def self.validattr?(name)
name = name.intern
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
@validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name))
end
@validattrs[name]
end
# @return [Boolean] Returns true if the given name is the name of an existing property
def self.validproperty?(name)
name = name.intern
@validproperties.include?(name) && @validproperties[name]
end
# @return [Array<Symbol>, {}] Returns a list of valid property names, or an empty hash if there are none.
# @todo An empty hash is returned if there are no defined parameters (not an empty array). This looks like
# a bug.
#
def self.validproperties
return {} unless defined?(@parameters)
@validproperties.keys
end
# @return [Boolean] Returns true if the given name is the name of an existing parameter
def self.validparameter?(name)
raise Puppet::DevError, _("Class %{class_name} has not defined parameters") % { class_name: self } unless defined?(@parameters)
!!(@paramhash.include?(name) or @@metaparamhash.include?(name))
end
# (see validattr?)
# @note see comment in code - how should this be documented? Are some of the other query methods deprecated?
# (or should be).
# @comment This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
end
# @return [Boolean] Returns true if the wanted state of the resource is that it should be absent (i.e. to be deleted).
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Creates a new property value holder for the resource if it is valid and does not already exist
# @return [Boolean] true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
false
end
# @return [Symbol, Boolean] Returns the name of the namevar if there is only one or false otherwise.
# @comment This is really convoluted and part of the support for multiple namevars (?).
# If there is only one namevar, the produced value is naturally this namevar, but if there are several?
# The logic caches the name of the namevar if it is a single name, but otherwise always
# calls key_attributes, and then caches the first if there was only one, otherwise it returns
# false and caches this (which is then subsequently returned as a cache hit).
#
def name_var
return @name_var_cache unless @name_var_cache.nil?
key_attributes = self.class.key_attributes
@name_var_cache = (key_attributes.length == 1) && key_attributes.first
end
# Gets the 'should' (wanted state) value of a parameter or property by name.
# To explicitly get the 'is' (current state) value use `o.is(:name)`, and to explicitly get the 'should' value
# use `o.should(:name)`
# @param name [String] the name of the attribute to obtain the 'should' value for.
# @return [Object] 'should'/wanted value of the given attribute
def [](name)
name = name.intern
fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name)
if name == :name && nv = name_var
name = nv
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Sets the 'should' (wanted state) value of a property, or the value of a parameter.
# @return
# @raise [Puppet::Error] if the setting of the value fails, or if the given name is nil.
# @raise [Puppet::ResourceError] when the parameter validation raises Puppet::Error or
# ArgumentError
def []=(name,value)
name = name.intern
fail("no parameter named '#{name}'") unless self.class.validattr?(name)
if name == :name && nv = name_var
name = nv
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
if property
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue Puppet::Error, ArgumentError => detail
error = Puppet::ResourceError.new(_("Parameter %{name} failed on %{ref}: %{detail}") %
{ name: name, ref: ref, detail: detail })
adderrorcontext(error, detail)
raise error
end
end
nil
end
# Removes an attribute from the object; useful in testing or in cleanup
# when an error has been encountered
# @todo Don't know what the attr is (name or Property/Parameter?). Guessing it is a String name...
# @todo Is it possible to delete a meta-parameter?
# @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted?
# @param attr [String] the attribute to delete from this object. WHAT IS THE TYPE?
# @raise [Puppet::DecError] when an attempt is made to delete an attribute that does not exists.
#
def delete(attr)
attr = attr.intern
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new(_("Undefined attribute '%{attribute}' in %{name}") % { attribute: attr, name: self})
end
end
# Iterates over the properties that were set on this resource.
# @yieldparam property [Puppet::Property] each property
# @return [void]
def eachproperty
# properties is a private method
properties.each { |property|
yield property
}
end
# Return the parameters, metaparams, and properties that have a value or were set by a default. Properties are
# included since they are a subclass of parameter.
# @return [Array<Puppet::Parameter>] Array of parameter objects ( or subclass thereof )
def parameters_with_value
self.class.allattrs.collect { |attr| parameter(attr) }.compact
end
# Iterates over all parameters with value currently set.
# @yieldparam parameter [Puppet::Parameter] or a subclass thereof
# @return [void]
def eachparameter
parameters_with_value.each { |parameter| yield parameter }
end
# Creates a transaction event.
# Called by Transaction or by a property.
# Merges the given options with the options `:resource`, `:file`, `:line`, and `:tags`, initialized from
# values in this object. For possible options to pass (if any ????) see {Puppet::Transaction::Event}.
# @todo Needs a better explanation "Why should I care who is calling this method?", What do I need to know
# about events and how they work? Where can I read about them?
# @param options [Hash] options merged with a fixed set of options defined by this method, passed on to {Puppet::Transaction::Event}.
# @return [Puppet::Transaction::Event] the created event
def event(options = {})
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options))
end
# @return [Object, nil] Returns the 'should' (wanted state) value for a specified property, or nil if the
# given attribute name is not a property (i.e. if it is a parameter, meta-parameter, or does not exist).
def should(name)
name = name.intern
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
end
# Registers an attribute to this resource type instance.
# Requires either the attribute name or class as its argument.
# This is a noop if the named property/parameter is not supported
# by this resource. Otherwise, an attribute instance is created
# and kept in this resource's parameters hash.
# @overload newattr(name)
# @param name [Symbol] symbolic name of the attribute
# @overload newattr(klass)
# @param klass [Class] a class supported as an attribute class, i.e. a subclass of
# Parameter or Property
# @return [Object] An instance of the named Parameter or Property class associated
# to this resource type instance, or nil if the attribute is not supported
#
def newattr(name)
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}"
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
debug "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
return @parameters[name] if @parameters.include?(name)
@parameters[name] = klass.new(:resource => self)
end
# Returns a string representation of the resource's containment path in
# the catalog.
# @return [String]
def path
@path ||= '/' + pathbuilder.join('/')
end
# Returns the value of this object's parameter given by name
# @param name [String] the name of the parameter
# @return [Object] the value
def parameter(name)
@parameters[name.to_sym]
end
# Returns a shallow copy of this object's hash of attributes by name.
# Note that his not only comprises parameters, but also properties and metaparameters.
# Changes to the contained parameters will have an effect on the parameters of this type, but changes to
# the returned hash does not.
# @return [Hash{String => Object}] a new hash being a shallow copy of the parameters map name to parameter
def parameters
@parameters.dup
end
# @return [Boolean] Returns whether the attribute given by name has been added
# to this resource or not.
def propertydefined?(name)
name = name.intern unless name.is_a? Symbol
@parameters.include?(name)
end
# Returns a {Puppet::Property} instance by name.
# To return the value, use 'resource[param]'
# @todo LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point. - Does this mean it should be deprecated ?
# @return [Puppet::Property] the property with the given name, or nil if not a property or does not exist.
def property(name)
(obj = @parameters[name.intern] and obj.is_a?(Puppet::Property)) ? obj : nil
end
# @todo comment says "For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes."
# @todo Needs a better explanation, and investigation about the claim an array can be passed (it is passed
# to self.class.attrclass to produce a class on which a check is made if it has a method class :default (does
# not seem to support an array...
# @return [void]
#
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
value = parameter.default
if value.nil?
@parameters.delete(parameter.name)
else
parameter.value = value
end
end
# @todo the comment says: "Convert our object to a hash. This just includes properties."
# @todo this is confused, again it is the @parameters instance variable that is consulted, and
# each value is copied - does it contain "properties" and "parameters" or both? Does it contain
# meta-parameters?
#
# @return [Hash{ ??? => ??? }] a hash of WHAT?. The hash is a shallow copy, any changes to the
# objects returned in this hash will be reflected in the original resource having these attributes.
#
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
# @return [String] the name of this object's class
# @todo Would that be "file" for the "File" resource type? of "File" or something else?
#
def type
self.class.name
end
# @todo Comment says "Return a specific value for an attribute.", as opposed to what "An unspecific value"???
# @todo is this the 'is' or the 'should' value?
# @todo why is the return restricted to things that respond to :value? (Only non structural basic data types
# supported?
#
# @return [Object, nil] the value of the attribute having the given name, or nil if the given name is not
# an attribute, or the referenced attribute does not respond to `:value`.
def value(name)
name = name.intern
(obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil
end
# @todo What is this used for? Needs a better explanation.
# @return [???] the version of the catalog or 0 if there is no catalog.
def version
return 0 unless catalog
catalog.version
end
# @return [Array<Puppet::Property>] Returns all of the property objects, in the order specified in the
# class.
# @todo "what does the 'order specified in the class' mean? The order the properties where added in the
# ruby file adding a new type with new properties?
#
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Returns true if the type's notion of name is the identity of a resource.
# See the overview of this class for a longer explanation of the concept _isomorphism_.
# Defaults to true.
#
# @return [Boolean] true, if this type's name is isomorphic with the object
def self.isomorphic?
if defined?(@isomorphic)
return @isomorphic
else
return true
end
end
# @todo check that this gets documentation (it is at the class level as well as instance).
# (see isomorphic?)
def isomorphic?
self.class.isomorphic?
end
# Returns true if the instance is a managed instance.
# A 'yes' here means that the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package in a list.
# @note An object that is managed always stays managed, but an object that is not managed
# may become managed later in its lifecycle.
# @return [Boolean] true if the object is managed
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# Returns true if the search should be done in depth-first order.
# This implementation always returns false.
# @todo What is this used for?
#
# @return [Boolean] true if the search should be done in depth first order.
#
def depthfirst?
false
end
# Removes this object (FROM WHERE?)
# @todo removes if from where?
# @return [void]
def remove()
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Returns the ancestors - WHAT?
# This implementation always returns an empty list.
# @todo WHAT IS THIS ?
# @return [Array<???>] returns a list of ancestors.
def ancestors
[]
end
# Lifecycle method for a resource. This is called during graph creation.
# It should perform any consistency checking of the catalog and raise a
# Puppet::Error if the transaction should be aborted.
#
# It differs from the validate method, since it is called later during
# initialization and can rely on self.catalog to have references to all
# resources that comprise the catalog.
#
# @see Puppet::Transaction#add_vertex
# @raise [Puppet::Error] If the pre-run check failed.
# @return [void]
# @abstract a resource type may implement this method to perform
# validation checks that can query the complete catalog
def pre_run_check
end
# Flushes the provider if supported by the provider, else no action.