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
290 def instances
291   list_all.to_a.map! do |object|
292     new({ :name => object[:name] })
293   end
294 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
276 def list_all(ia_module_args = [])
277   cmd = [command(:list), '-c', *ia_module_args, '-a', 'id', 'ALL']
278   parse_aix_objects(execute(cmd)).to_a.map do |object|
279     name = object[:name]
280     id = object[:attributes].delete(:id)
281 
282     Hash[[[:name, name,],[:id, id]]]
283   end
284 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
 95 def mapping(info = {})
 96   identity_fn = lambda { |x| x }
 97   info[:aix_attribute] ||= info[:puppet_property]
 98   info[:property_to_attribute] ||= identity_fn
 99   info[:attribute_to_property] ||= identity_fn
100 
101   mappings[:aix_attribute][info[:puppet_property]] = MappedObject.new(
102     info[:aix_attribute],
103     :convert_property_value,
104     info[:property_to_attribute]
105   )
106   mappings[:puppet_property][info[:aix_attribute]] = MappedObject.new(
107     info[:puppet_property],
108     :convert_attribute_value,
109     info[:attribute_to_property]
110   )
111 end
mappings() click to toggle source
   # File lib/puppet/provider/aix_object.rb
67 def mappings
68   return @mappings if @mappings
69 
70   @mappings = {}
71   @mappings[:aix_attribute] = {}
72   @mappings[:puppet_property] = {}
73 
74   @mappings
75 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
148 def mk_resource_methods
149   # Define the Getter methods for each of our properties + the attributes
150   # property
151   properties = [:attributes]
152   properties += mappings[:aix_attribute].keys
153   properties.each do |property|
154     # Define the getter
155     define_method(property) do
156       get(property)
157     end
158 
159     # We have a custom setter for the :attributes property,
160     # so no need to define it.
161     next if property == :attributes
162 
163     # Define the setter
164     define_method("#{property}=".to_sym) do |value|
165       set(property, value)
166     end
167   end
168 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
115 def numeric_mapping(info = {})
116   property = info[:puppet_property]
117 
118   # We have this validation here b/c not all numeric properties
119   # handle this at the property level (e.g. like the UID). Given
120   # that, we might as well go ahead and do this validation for all
121   # of our numeric properties. Doesn't hurt.
122   info[:property_to_attribute] = lambda do |value|
123     unless value.is_a?(Integer)
124       raise ArgumentError, _("Invalid value %{value}: %{property} must be an Integer!") % { value: value, property: property }
125     end
126 
127     value.to_s
128   end
129 
130   # AIX will do the right validation to ensure numeric attributes
131   # can't be set to non-numeric values, so no need for the extra clutter.
132   info[:attribute_to_property] = lambda do |value|
133     value.to_i
134   end
135 
136   mapping(info)
137 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
243 def parse_aix_objects(output)
244   # Object names cannot begin with '#', so we are safe to
245   # split individual users this way. We do not have to worry
246   # about an empty list either since there is guaranteed to be
247   # at least one instance of an AIX object (e.g. at least one
248   # user or one group on the system).
249   _, *objects = output.chomp.split(/^#/)
250 
251   objects.map! do |object|
252     attributes_line, values_line = object.chomp.split("\n")
253 
254     attributes = parse_colon_separated_list(attributes_line.chomp)
255     attributes.map!(&:to_sym)
256 
257     values = parse_colon_separated_list(values_line.chomp)
258 
259     attributes_hash = Hash[attributes.zip(values)]
260 
261     object_name = attributes_hash.delete(:name)
262 
263     Hash[[[:name, object_name.to_s], [:attributes, attributes_hash]]]
264   end
265 
266   objects
267 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
198 def parse_colon_separated_list(colon_list)
199   # ALGORITHM:
200   # Treat the colon_list as a list separated by '#!:' We will get
201   # something like:
202   #     [ <chunk1>, <chunk2>, ... <chunkn> ]
203   #
204   # Each chunk is now a list separated by ':' and none of the items
205   # in each chunk contains an escaped ':'. Now, split each chunk on
206   # ':' to get:
207   #     [ [<piece11>, ..., <piece1n>], [<piece21>, ..., <piece2n], ... ]
208   #
209   # Now note that <item1> = <piece11>, <item2> = <piece12> in our original
210   # list, and that <itemn> = <piece1n>#!:<piece21>. This is the main idea
211   # behind what our inject method is trying to do at the end, except that
212   # we replace '#!:' with ':' since the colons are no longer escaped.
213   chunks = split_list(colon_list, '#!:')
214   chunks.map! { |chunk| split_list(chunk, ':') }
215 
216   chunks.inject do |accum, chunk|
217     left = accum.pop
218     right = chunk.shift
219 
220     accum.push("#{left}:#{right}")
221     accum += chunk
222 
223     accum
224   end
225 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
185 def split_list(list, sep)
186   return [""] if list.empty?
187 
188   list.split(sep, -1)
189 end

Public Instance Methods

addcmd(attributes) click to toggle source
    # File lib/puppet/provider/aix_object.rb
343 def addcmd(attributes)
344   attribute_args = attributes_to_args(attributes)
345   [self.class.command(:add)] + ia_module_args + attribute_args + [@resource[:name]]
346 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
409 def attributes=(new_attributes)
410   validate_new_attributes(new_attributes)
411   modify_object(new_attributes)
412 rescue Puppet::ExecutionFailure => detail
413   raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
414 end
attributes_to_args(attributes) click to toggle source

Converts the given attributes hash to CLI args.

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

Creates a new instance of the resource

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

Deletes this instance resource

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

Check that the AIX object exists

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

Gets a Puppet property's value from object_info

    # File lib/puppet/provider/aix_object.rb
364 def get(property)
365   return :absent unless exists?
366   object_info[property] || :absent
367 end
ia_module_args() click to toggle source
    # File lib/puppet/provider/aix_object.rb
332 def ia_module_args
333   raise ArgumentError, _("Cannot have both 'forcelocal' and 'ia_load_module' at the same time!") if @resource[:ia_load_module] && @resource[:forcelocal]
334   return ["-R", @resource[:ia_load_module].to_s] if @resource[:ia_load_module]
335   return ["-R", "files"] if @resource[:forcelocal]
336   []
337 end
lscmd() click to toggle source
    # File lib/puppet/provider/aix_object.rb
339 def lscmd
340   [self.class.command(:list), '-c'] + ia_module_args + [@resource[:name]]
341 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
300 def mappings
301   return @mappings if @mappings
302   
303   @mappings = {}
304   self.class.mappings.each do |type, mapped_objects|
305     @mappings[type] = {}
306     mapped_objects.each do |input, mapped_object|
307       if mapped_object.pure_conversion_fn?
308         # Our mapped_object has a pure conversion function so we
309         # can go ahead and use it as-is.
310         @mappings[type][input] = mapped_object
311         next
312       end
313 
314       # Otherwise, we need to dup it and set its provider to our
315       # provider instance. The dup is necessary so that we do not
316       # touch the class-level mapped object.
317       @mappings[type][input] = mapped_object.dup
318       @mappings[type][input].set_provider(self)
319     end
320   end
321 
322   @mappings
323 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
358 def modify_object(new_attributes)
359   execute(modifycmd(new_attributes))
360   object_info(true) 
361 end
modifycmd(new_attributes) click to toggle source
    # File lib/puppet/provider/aix_object.rb
352 def modifycmd(new_attributes)
353   attribute_args = attributes_to_args(new_attributes)
354   [self.class.command(:modify)] + ia_module_args + attribute_args + [@resource[:name]]
355 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
418 def object_info(refresh = false)
419   return @object_info if @object_info && ! refresh
420   @object_info = nil
421 
422   begin
423     output = execute(lscmd)
424   rescue Puppet::ExecutionFailure
425     Puppet.debug(_("aix.object_info(): Could not find %{resource}[%{name}]") % { resource: @resource.class.name, name: @resource.name })
426 
427     return @object_info
428   end
429 
430   # If lscmd succeeds, then output will contain our object's information.
431   # Thus, .parse_aix_objects will always return a single element array.
432   aix_attributes = self.class.parse_aix_objects(output).first[:attributes]
433   aix_attributes.each do |attribute, value|
434     @object_info ||= {}
435 
436     # If our attribute has a Puppet property, then we store that. Else, we store it as part
437     # of our :attributes property hash
438     if (property = mappings[:puppet_property][attribute])
439       @object_info[property.name] = property.convert_attribute_value(value)
440     else
441       @object_info[:attributes] ||= {}
442       @object_info[:attributes][attribute] = value
443     end
444   end
445 
446   @object_info
447 end
set(property, value) click to toggle source

Sets a mapped Puppet property's value.

    # File lib/puppet/provider/aix_object.rb
370 def set(property, value)
371   aix_attribute = mappings[:aix_attribute][property]
372   modify_object(
373     { aix_attribute.name => aix_attribute.convert_property_value(value) }
374   )
375 rescue Puppet::ExecutionFailure => detail
376   raise Puppet::Error, _("Could not set %{property} on %{resource}[%{name}]: %{detail}") % { property: property, resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
377 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
381 def validate_new_attributes(new_attributes)
382   # Gather all of the <puppet property>, <aix attribute> conflicts to print
383   # them all out when we create our error message. This makes it easy for the
384   # user to update their manifest based on our error message.
385   conflicts = {}
386   mappings[:aix_attribute].each do |property, aix_attribute|
387     next unless new_attributes.key?(aix_attribute.name)
388 
389     conflicts[:properties] ||= []
390     conflicts[:properties].push(property)
391 
392     conflicts[:attributes] ||= []
393     conflicts[:attributes].push(aix_attribute.name)
394   end
395 
396   return if conflicts.empty?
397 
398   properties, attributes = conflicts.keys.map do |key|
399     conflicts[key].map! { |name| "'#{name}'" }.join(', ')
400   end
401     
402   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 }
403 
404   raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }
405 end