class Puppet::Provider::AixObject

Common code for AIX user/group providers.

Public Class Methods

instances() click to toggle source
    # File lib/puppet/provider/aix_object.rb
291 def instances
292   list_all.to_a.map! do |object|
293     new({ :name => object[:name] })
294   end
295 end
list_all(ia_module_args = []) click to toggle source

Lists all instances of the given object, taking in an optional set of ia_module arguments. Returns an array of hashes, each hash having the schema

{
  :name => <object_name>
  :id   => <object_id>
}
    # File lib/puppet/provider/aix_object.rb
277 def list_all(ia_module_args = [])
278   cmd = [command(:list), '-c', *ia_module_args, '-a', 'id', 'ALL']
279   parse_aix_objects(execute(cmd)).to_a.map do |object|
280     name = object[:name]
281     id = object[:attributes].delete(:id)
282 
283     { name: name, id: id }
284   end
285 end
mapping(info = {}) click to toggle source

Add a mapping from a Puppet property to an AIX attribute. The info must include:

* :puppet_property       -- The puppet property corresponding to this attribute
* :aix_attribute         -- The AIX attribute corresponding to this attribute. Defaults
                         to puppet_property if this is not provided.
* :property_to_attribute -- A lambda that converts a Puppet Property to an AIX attribute
                         value. Defaults to the identity function if not provided.
* :attribute_to_property -- A lambda that converts an AIX attribute to a Puppet property.
                         Defaults to the identity function if not provided.

NOTE: The lambdas for :property_to_attribute or :attribute_to_property can be 'pure' or 'impure'. A 'pure' lambda is one that needs only the value to do the conversion, while an 'impure' lambda is one that requires the provider instance along with the value. 'Pure' lambdas have the interface 'do |value| …' while 'impure' lambdas have the interface 'do |provider, value| …'.

NOTE: 'Impure' lambdas are useful in case we need to generate more specific error messages or pass-in instance-specific command-line arguments.

    # File lib/puppet/provider/aix_object.rb
 96 def mapping(info = {})
 97   identity_fn = lambda { |x| x }
 98   info[:aix_attribute] ||= info[:puppet_property]
 99   info[:property_to_attribute] ||= identity_fn
100   info[:attribute_to_property] ||= identity_fn
101 
102   mappings[:aix_attribute][info[:puppet_property]] = MappedObject.new(
103     info[:aix_attribute],
104     :convert_property_value,
105     info[:property_to_attribute]
106   )
107   mappings[:puppet_property][info[:aix_attribute]] = MappedObject.new(
108     info[:puppet_property],
109     :convert_attribute_value,
110     info[:attribute_to_property]
111   )
112 end
mappings() click to toggle source
   # File lib/puppet/provider/aix_object.rb
68 def mappings
69   return @mappings if @mappings
70 
71   @mappings = {}
72   @mappings[:aix_attribute] = {}
73   @mappings[:puppet_property] = {}
74 
75   @mappings
76 end
mk_resource_methods() click to toggle source

Defines the getter and setter methods for each Puppet property that's mapped to an AIX attribute. We define only a getter for the :attributes property.

Provider subclasses should call this method after they've defined all of their <puppet_property> => <aix_attribute> mappings.

    # File lib/puppet/provider/aix_object.rb
149 def mk_resource_methods
150   # Define the Getter methods for each of our properties + the attributes
151   # property
152   properties = [:attributes]
153   properties += mappings[:aix_attribute].keys
154   properties.each do |property|
155     # Define the getter
156     define_method(property) do
157       get(property)
158     end
159 
160     # We have a custom setter for the :attributes property,
161     # so no need to define it.
162     next if property == :attributes
163 
164     # Define the setter
165     define_method("#{property}=".to_sym) do |value|
166       set(property, value)
167     end
168   end
169 end
numeric_mapping(info = {}) click to toggle source

Creates a mapping from a purely numeric Puppet property to an attribute

    # File lib/puppet/provider/aix_object.rb
116 def numeric_mapping(info = {})
117   property = info[:puppet_property]
118 
119   # We have this validation here b/c not all numeric properties
120   # handle this at the property level (e.g. like the UID). Given
121   # that, we might as well go ahead and do this validation for all
122   # of our numeric properties. Doesn't hurt.
123   info[:property_to_attribute] = lambda do |value|
124     unless value.is_a?(Integer)
125       raise ArgumentError, _("Invalid value %{value}: %{property} must be an Integer!") % { value: value, property: property }
126     end
127 
128     value.to_s
129   end
130 
131   # AIX will do the right validation to ensure numeric attributes
132   # can't be set to non-numeric values, so no need for the extra clutter.
133   info[:attribute_to_property] = lambda do |value|
134     value.to_i
135   end
136 
137   mapping(info)
138 end
parse_aix_objects(output) click to toggle source

Parses the AIX objects from the command output, returning an array of hashes with each hash having the following schema:

{
  :name       => <object_name>
  :attributes => <object_attributes>
}

Output should be of the form

#name:<attr1>:<attr2> ...
<name>:<value1>:<value2> ...
#name:<attr1>:<attr2> ...
<name>:<value1>:<value2> ...

NOTE: We need to parse the colon-formatted output in case we have space-separated attributes (e.g. 'gecos'). “:” characters are escaped with a “#!”.

    # File lib/puppet/provider/aix_object.rb
244 def parse_aix_objects(output)
245   # Object names cannot begin with '#', so we are safe to
246   # split individual users this way. We do not have to worry
247   # about an empty list either since there is guaranteed to be
248   # at least one instance of an AIX object (e.g. at least one
249   # user or one group on the system).
250   _, *objects = output.chomp.split(/^#/)
251 
252   objects.map! do |object|
253     attributes_line, values_line = object.chomp.split("\n")
254 
255     attributes = parse_colon_separated_list(attributes_line.chomp)
256     attributes.map!(&:to_sym)
257 
258     values = parse_colon_separated_list(values_line.chomp)
259 
260     attributes_hash = Hash[attributes.zip(values)]
261 
262     object_name = attributes_hash.delete(:name)
263 
264     Hash[[[:name, object_name.to_s], [:attributes, attributes_hash]]]
265   end
266 
267   objects
268 end
parse_colon_separated_list(colon_list) click to toggle source

Parses a colon-separated list. Example includes something like:

<item1>:<item2>:<item3>:<item4>

Returns an array of the parsed items, e.g.

[ <item1>, <item2>, <item3>, <item4> ]

Note that colons inside items are escaped by #!

    # File lib/puppet/provider/aix_object.rb
199 def parse_colon_separated_list(colon_list)
200   # ALGORITHM:
201   # Treat the colon_list as a list separated by '#!:' We will get
202   # something like:
203   #     [ <chunk1>, <chunk2>, ... <chunkn> ]
204   #
205   # Each chunk is now a list separated by ':' and none of the items
206   # in each chunk contains an escaped ':'. Now, split each chunk on
207   # ':' to get:
208   #     [ [<piece11>, ..., <piece1n>], [<piece21>, ..., <piece2n], ... ]
209   #
210   # Now note that <item1> = <piece11>, <item2> = <piece12> in our original
211   # list, and that <itemn> = <piece1n>#!:<piece21>. This is the main idea
212   # behind what our inject method is trying to do at the end, except that
213   # we replace '#!:' with ':' since the colons are no longer escaped.
214   chunks = split_list(colon_list, '#!:')
215   chunks.map! { |chunk| split_list(chunk, ':') }
216 
217   chunks.inject do |accum, chunk|
218     left = accum.pop
219     right = chunk.shift
220 
221     accum.push("#{left}:#{right}")
222     accum += chunk
223 
224     accum
225   end
226 end
split_list(list, sep) click to toggle source

This helper splits a list separated by sep into its corresponding items. Note that a key precondition here is that none of the items in the list contain sep.

Let A be the return value. Then one of our postconditions is:

A.join(sep) == list

NOTE: This function is only used by the parse_colon_separated_list function below. It is meant to be an inner lambda. The reason it isn't here is so we avoid having to create a proc. object for the split_list lambda each time parse_colon_separated_list is invoked. This will happen quite often since it is used at the class level and at the instance level. Since this function is meant to be an inner lambda and thus not exposed anywhere else, we do not have any unit tests for it. These test cases are instead covered by the unit tests for parse_colon_separated_list

    # File lib/puppet/provider/aix_object.rb
186 def split_list(list, sep)
187   return [""] if list.empty?
188 
189   list.split(sep, -1)
190 end

Public Instance Methods

addcmd(attributes) click to toggle source
    # File lib/puppet/provider/aix_object.rb
344 def addcmd(attributes)
345   attribute_args = attributes_to_args(attributes)
346   [self.class.command(:add)] + ia_module_args + attribute_args + [@resource[:name]]
347 end
attributes=(new_attributes) click to toggle source

Modifies the attribute property. Note we raise an error if the user specified an AIX attribute corresponding to a Puppet property.

    # File lib/puppet/provider/aix_object.rb
410 def attributes=(new_attributes)
411   validate_new_attributes(new_attributes)
412   modify_object(new_attributes)
413 rescue Puppet::ExecutionFailure => detail
414   raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
415 end
attributes_to_args(attributes) click to toggle source

Converts the given attributes hash to CLI args.

    # File lib/puppet/provider/aix_object.rb
327 def attributes_to_args(attributes)
328   attributes.map do |attribute, value|
329     "#{attribute}=#{value}"
330   end
331 end
create() click to toggle source

Creates a new instance of the resource

    # File lib/puppet/provider/aix_object.rb
460 def create
461   attributes = @resource.should(:attributes) || {}
462   validate_new_attributes(attributes)
463 
464   mappings[:aix_attribute].each do |property, aix_attribute|
465     property_should = @resource.should(property)
466     next if property_should.nil?
467     attributes[aix_attribute.name] = aix_attribute.convert_property_value(property_should)
468   end
469 
470   execute(addcmd(attributes))
471 rescue Puppet::ExecutionFailure => detail
472   raise Puppet::Error, _("Could not create %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
473 end
delete() click to toggle source

Deletes this instance resource

    # File lib/puppet/provider/aix_object.rb
476 def delete
477   execute(deletecmd)
478 
479   # Recollect the object info so that our current properties reflect
480   # the actual state of the system. Otherwise, puppet resource reports
481   # the wrong info. at the end. Note that this should return nil.
482   object_info(true)
483 rescue Puppet::ExecutionFailure => detail
484   raise Puppet::Error, _("Could not delete %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
485 end
deletecmd() click to toggle source
    # File lib/puppet/provider/aix_object.rb
349 def deletecmd
350   [self.class.command(:delete)] + ia_module_args + [@resource[:name]]
351 end
exists?() click to toggle source

Check that the AIX object exists

    # File lib/puppet/provider/aix_object.rb
455 def exists?
456   ! object_info.nil?
457 end
get(property) click to toggle source

Gets a Puppet property's value from object_info

    # File lib/puppet/provider/aix_object.rb
365 def get(property)
366   return :absent unless exists?
367   object_info[property] || :absent
368 end
ia_module_args() click to toggle source
    # File lib/puppet/provider/aix_object.rb
333 def ia_module_args
334   raise ArgumentError, _("Cannot have both 'forcelocal' and 'ia_load_module' at the same time!") if @resource[:ia_load_module] && @resource[:forcelocal]
335   return ["-R", @resource[:ia_load_module].to_s] if @resource[:ia_load_module]
336   return ["-R", "files"] if @resource[:forcelocal]
337   []
338 end
lscmd() click to toggle source
    # File lib/puppet/provider/aix_object.rb
340 def lscmd
341   [self.class.command(:list), '-c'] + ia_module_args + [@resource[:name]]
342 end
mappings() click to toggle source

Instantiate our mappings. These need to be at the instance-level since some of our mapped objects may have impure conversion functions that need our provider instance.

    # File lib/puppet/provider/aix_object.rb
301 def mappings
302   return @mappings if @mappings
303   
304   @mappings = {}
305   self.class.mappings.each do |type, mapped_objects|
306     @mappings[type] = {}
307     mapped_objects.each do |input, mapped_object|
308       if mapped_object.pure_conversion_fn?
309         # Our mapped_object has a pure conversion function so we
310         # can go ahead and use it as-is.
311         @mappings[type][input] = mapped_object
312         next
313       end
314 
315       # Otherwise, we need to dup it and set its provider to our
316       # provider instance. The dup is necessary so that we do not
317       # touch the class-level mapped object.
318       @mappings[type][input] = mapped_object.dup
319       @mappings[type][input].set_provider(self)
320     end
321   end
322 
323   @mappings
324 end
modify_object(new_attributes) click to toggle source

Modifies the AIX object by setting its new attributes.

    # File lib/puppet/provider/aix_object.rb
359 def modify_object(new_attributes)
360   execute(modifycmd(new_attributes))
361   object_info(true) 
362 end
modifycmd(new_attributes) click to toggle source
    # File lib/puppet/provider/aix_object.rb
353 def modifycmd(new_attributes)
354   attribute_args = attributes_to_args(new_attributes)
355   [self.class.command(:modify)] + ia_module_args + attribute_args + [@resource[:name]]
356 end
object_info(refresh = false) click to toggle source

Collects the current property values of all mapped properties + the attributes property.

    # File lib/puppet/provider/aix_object.rb
419 def object_info(refresh = false)
420   return @object_info if @object_info && ! refresh
421   @object_info = nil
422 
423   begin
424     output = execute(lscmd)
425   rescue Puppet::ExecutionFailure
426     Puppet.debug(_("aix.object_info(): Could not find %{resource}[%{name}]") % { resource: @resource.class.name, name: @resource.name })
427 
428     return @object_info
429   end
430 
431   # If lscmd succeeds, then output will contain our object's information.
432   # Thus, .parse_aix_objects will always return a single element array.
433   aix_attributes = self.class.parse_aix_objects(output).first[:attributes]
434   aix_attributes.each do |attribute, value|
435     @object_info ||= {}
436 
437     # If our attribute has a Puppet property, then we store that. Else, we store it as part
438     # of our :attributes property hash
439     if (property = mappings[:puppet_property][attribute])
440       @object_info[property.name] = property.convert_attribute_value(value)
441     else
442       @object_info[:attributes] ||= {}
443       @object_info[:attributes][attribute] = value
444     end
445   end
446 
447   @object_info
448 end
set(property, value) click to toggle source

Sets a mapped Puppet property's value.

    # File lib/puppet/provider/aix_object.rb
371 def set(property, value)
372   aix_attribute = mappings[:aix_attribute][property]
373   modify_object(
374     { aix_attribute.name => aix_attribute.convert_property_value(value) }
375   )
376 rescue Puppet::ExecutionFailure => detail
377   raise Puppet::Error, _("Could not set %{property} on %{resource}[%{name}]: %{detail}") % { property: property, resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
378 end
validate_new_attributes(new_attributes) click to toggle source

This routine validates our new attributes property value to ensure that it does not contain any Puppet properties.

    # File lib/puppet/provider/aix_object.rb
382 def validate_new_attributes(new_attributes)
383   # Gather all of the <puppet property>, <aix attribute> conflicts to print
384   # them all out when we create our error message. This makes it easy for the
385   # user to update their manifest based on our error message.
386   conflicts = {}
387   mappings[:aix_attribute].each do |property, aix_attribute|
388     next unless new_attributes.key?(aix_attribute.name)
389 
390     conflicts[:properties] ||= []
391     conflicts[:properties].push(property)
392 
393     conflicts[:attributes] ||= []
394     conflicts[:attributes].push(aix_attribute.name)
395   end
396 
397   return if conflicts.empty?
398 
399   properties, attributes = conflicts.keys.map do |key|
400     conflicts[key].map! { |name| "'#{name}'" }.join(', ')
401   end
402     
403   detail = _("attributes is setting the %{properties} properties via. the %{attributes} attributes, respectively! Please specify these property values in the resource declaration instead.") % { properties: properties, attributes: attributes }
404 
405   raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }
406 end