class Puppet::Provider::AixObject
Common code for AIX user/group providers.
Public Class Methods
# 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
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
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
# 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
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
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
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
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
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
# 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
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
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
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
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
# File lib/puppet/provider/aix_object.rb 348 def deletecmd 349 [self.class.command(:delete)] + ia_module_args + [@resource[:name]] 350 end
Check that the AIX object exists
# File lib/puppet/provider/aix_object.rb 454 def exists? 455 ! object_info.nil? 456 end
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
# 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
# File lib/puppet/provider/aix_object.rb 339 def lscmd 340 [self.class.command(:list), '-c'] + ia_module_args + [@resource[:name]] 341 end
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
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
# 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
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
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
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