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