From 9032898eb32080bfae66a707d55976e9f984a512 Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Wed, 1 Dec 2010 13:47:57 +0100 Subject: (#5432) Use AIX native commands to manage users and groups Specific providers should be created for AIX to manage users and groups. AIX bases the authentication management on a set of commands: mkuser, rmuser, chuser, lsuser, mkgroup, rmgroup, chgroup, lsgroup, etc. This commit implements such providers. Notes:: - AIX users can have expiry date defined with minute granularity, but puppet does not allow it. There is a ticket open for that (#5431) - AIX maximum password age is in WEEKs, not days. - I force the compat IA module. TODO:: - Add new AIX specific attributes, specilly registry and SYSTEM. --- lib/puppet/provider/aixobject.rb | 282 +++++++++++++++++++++++++++++++++++++ lib/puppet/provider/group/aix.rb | 76 ++++++++++ lib/puppet/provider/user/aix.rb | 294 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 652 insertions(+) create mode 100755 lib/puppet/provider/aixobject.rb create mode 100755 lib/puppet/provider/group/aix.rb create mode 100755 lib/puppet/provider/user/aix.rb (limited to 'lib') diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb new file mode 100755 index 000000000..98ac13bdf --- /dev/null +++ b/lib/puppet/provider/aixobject.rb @@ -0,0 +1,282 @@ +# +# Common code for AIX providers +# +# Author:: Hector Rivas Gandara +# +# +class Puppet::Provider::AixObject < Puppet::Provider + desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" + + # Constants + # Loadable AIX I/A module for users and groups. By default we manage compat. + # TODO:: add a type parameter to change this + class << self + attr_accessor :ia_module + end + + + # AIX attributes to properties mapping. Subclasses should rewrite them + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + class << self + attr_accessor :attribute_mapping + end + + # Provider must implement these functions. + def lscmd(value=@resource[:name]) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def addcmd(extra_attrs = []) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def modifycmd(attributes_hash) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + def deletecmd + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" + end + + # attribute_mapping class variable, + class << self + attr_accessor :attribute_mapping + end + def self.attribute_mapping_to + if ! @attribute_mapping_to + @attribute_mapping_to = {} + attribute_mapping.each { |elem| + attribute_mapping_to[elem[:puppet_prop]] = { + :key => elem[:aix_attr], + :method => elem[:to] + } + } + end + @attribute_mapping_to + end + def self.attribute_mapping_from + if ! @attribute_mapping_from + @attribute_mapping_from = {} + attribute_mapping.each { |elem| + attribute_mapping_from[elem[:aix_attr]] = { + :key => elem[:puppet_prop], + :method => elem[:from] + } + } + end + @attribute_mapping_from + end + + # This functions translates a key and value using the given mapping. + # Mapping can be nil (no translation) or a hash with this format + # {:key => new_key, :method => translate_method} + # It returns a list [key, value] + def self.translate_attr(key, value, mapping) + return [key, value] unless mapping + return nil unless mapping[key] + + if mapping[key][:method] + new_value = method(mapping[key][:method]).call(value) + else + new_value = value + end + [mapping[key][:key], new_value] + end + + #----- + # Convert a pair key-value using the + + # Parse AIX command attributes (string) and return provider hash + # If a mapping is provided, the keys are translated as defined in the + # mapping hash. Only values included in mapping will be added + # NOTE: it will ignore the items not including '=' + def self.attr2hash(str, mapping=attribute_mapping_from) + properties = {} + attrs = [] + if !str or (attrs = str.split()[0..-1]).empty? + return nil + end + + attrs.each { |i| + if i.include? "=" # Ignore if it does not include '=' + (key_str, val) = i.split('=') + # Check the key + if !key_str or key_str.empty? + info "Empty key in string 'i'?" + continue + end + key = key_str.to_sym + + if ret = self.translate_attr(key, val, mapping) + new_key = ret[0] + new_val = ret[1] + + properties[new_key] = new_val + end + end + } + properties.empty? ? nil : properties + end + + # Convert the provider properties to AIX command attributes (string) + def self.hash2attr(hash, mapping=attribute_mapping_to) + return "" unless hash + attr_list = [] + hash.each {|key, val| + + if ret = self.translate_attr(key, val, mapping) + new_key = ret[0] + new_val = ret[1] + + # Arrays are separated by commas + if new_val.is_a? Array + value = new_val.join(",") + else + value = new_val.to_s + end + + attr_list << (new_key.to_s + "=" + value ) + end + } + attr_list + end + + # Retrieve what we can about our object + def getinfo(refresh = false) + if @objectinfo.nil? or refresh == true + # Execute lsuser, split all attributes and add them to a dict. + begin + attrs = execute(self.lscmd).split("\n")[0] + @objectinfo = self.class.attr2hash(attrs) + rescue Puppet::ExecutionFailure => detail + # Print error if needed + Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" \ + unless detail.to_s.include? "User \"#{@resource.name}\" does not exist." + end + end + @objectinfo + end + + #------------- + # Provider API + # ------------ + + # Clear out the cached values. + def flush + @property_hash.clear if @property_hash + @object_info.clear if @object_info + end + + # Check that the user exists + def exists? + !!getinfo(true) # !! => converts to bool + end + + #- **ensure** + # The basic state that the object should be in. Valid values are + # `present`, `absent`, `role`. + # From ensurable: exists?, create, delete + def ensure + if exists? + :present + else + :absent + end + end + + # Return all existing instances + # The method for returning a list of provider instances. Note that it returns + # providers, preferably with values already filled in, not resources. + def self.instances + objects = [] + execute(lscmd("ALL")).each { |entry| + objects << new(:name => entry.split(" ")[0], :ensure => :present) + } + objects + end + + def create + if exists? + info "already exists" + # The object already exists + return nil + end + + begin + execute(self.addcmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}" + end + end + + def delete + unless exists? + info "already absent" + # the object already doesn't exist + return nil + end + + begin + execute(self.deletecmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not delete #{@resource.class.name} #{@resource.name}: #{detail}" + end + end + + #-------------------------------- + # Call this method when the object is initialized, + # create getter/setter methods for each property our resource type supports. + # If setter or getter already defined it will not be overwritten + def self.mk_resource_methods + [resource_type.validproperties, resource_type.parameters].flatten.each do |prop| + next if prop == :ensure + define_method(prop) { get(prop) || :absent} unless public_method_defined?(prop) + define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=") + end + end + # + + # Define the needed getters and setters as soon as we know the resource type + def self.resource_type=(resource_type) + super + mk_resource_methods + end + + # Retrieve a specific value by name. + def get(param) + (hash = getinfo(false)) ? hash[param] : nil + end + + # Set a property. + def set(param, value) + @property_hash[symbolize(param)] = value + # If value does not change, do not update. + if value == getinfo()[param.to_sym] + return + end + + #self.class.validate(param, value) + cmd = modifycmd({param => value}) + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + + # Refresh de info. + hash = getinfo(true) + end + + def initialize(resource) + super + @objectinfo = nil + # FIXME: Initiallize this properly. + self.class.ia_module="compat" + end + +end +#end diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb new file mode 100755 index 000000000..bcb81e1c5 --- /dev/null +++ b/lib/puppet/provider/group/aix.rb @@ -0,0 +1,76 @@ +# +# Group Puppet provider for AIX. It uses standard commands to manage groups: +# mkgroup, rmgroup, lsgroup, chgroup +# +# Author:: Hector Rivas Gandara +# +require 'puppet/provider/aixobject' + +Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject do + desc "Group management for AIX! Users are managed with mkgroup, rmgroup, lsgroup, chgroup" + + # Constants + # Default extra attributes to add when element is created + # registry=compat: Needed if you are using LDAP by default. + @DEFAULT_EXTRA_ATTRS = [ "registry=compat", ] + + + # This will the the default provider for this platform + defaultfor :operatingsystem => :aix + confine :operatingsystem => :aix + + # Provider features + has_features :manages_members + + # Commands that manage the element + commands :list => "/usr/sbin/lsgroup" + commands :add => "/usr/bin/mkgroup" + commands :delete => "/usr/sbin/rmgroup" + commands :modify => "/usr/bin/chgroup" + + # AIX attributes to properties mapping. + # + # Valid attributes to be managed by this provider. + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + self.attribute_mapping = [ + #:name => :name, + {:aix_attr => :id, :puppet_prop => :gid }, + {:aix_attr => :users, :puppet_prop => :members, + :from => :users_from_attr}, + ] + + #-------------- + # Command lines + def lscmd(value=@resource[:name]) + [self.class.command(:list), "-R", self.class.ia_module , value] + end + + def addcmd(extra_attrs = []) + # Here we use the @resource.to_hash to get the list of provided parameters + # Puppet does not call to self.= method if it does not exists. + # + # It gets an extra list of arguments to add to the user. + [self.class.command(:add), "-R", self.class.ia_module ]+ + self.class.hash2attr(@resource.to_hash) + + extra_attrs + [@resource[:name]] + end + + def modifycmd(hash = property_hash) + [self.class.command(:modify), "-R", self.class.ia_module ]+ + self.class.hash2attr(hash) + [@resource[:name]] + end + + def deletecmd + [self.class.command(:delete),"-R", self.class.ia_module, @resource[:name]] + end + + # Force convert users it a list. + def self.users_from_attr(value) + (value.is_a? String) ? value.split(',') : value + end + +end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb new file mode 100755 index 000000000..ba95dbc49 --- /dev/null +++ b/lib/puppet/provider/user/aix.rb @@ -0,0 +1,294 @@ +# +# User Puppet provider for AIX. It uses standar commands to manage users: +# mkuser, rmuser, lsuser, chuser +# +# Notes: +# - AIX users can have expiry date defined with minute granularity, +# but puppet does not allow it. There is a ticket open for that (#5431) +# - AIX maximum password age is in WEEKs, not days +# - I force the compat IA module. +# +# See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development +# for more information +# +# Author:: Hector Rivas Gandara +# +# TODO:: +# - Add new AIX specific attributes, specilly registry and SYSTEM. +# +require 'puppet/provider/aixobject' +require 'tempfile' +require 'date' + +Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do + desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" + + # Constants + # Default extra attributes to add when element is created + # registry=compat SYSTEM=compat: Needed if you are using LDAP by default. + @DEFAULT_EXTRA_ATTRS = [ "registry=compat", " SYSTEM=compat" ] + + # This will the the default provider for this platform + defaultfor :operatingsystem => :aix + confine :operatingsystem => :aix + + # Commands that manage the element + commands :lsgroup => "/usr/sbin/lsgroup" + + commands :list => "/usr/sbin/lsuser" + commands :add => "/usr/bin/mkuser" + commands :delete => "/usr/sbin/rmuser" + commands :modify => "/usr/bin/chuser" + commands :chpasswd => "/bin/chpasswd" + + # Provider features + has_features :manages_homedir, :manages_passwords, :manages_expiry, :manages_password_age + + # Attribute verification (TODO) + #verify :gid, "GID must be an string or int of a valid group" do |value| + # value.is_a? String || value.is_a? Integer + #end + # + #verify :groups, "Groups must be comma-separated" do |value| + # value !~ /\s/ + #end + + # AIX attributes to properties mapping. + # + # Valid attributes to be managed by this provider. + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + self.attribute_mapping = [ + #:name => :name, + {:aix_attr => :pgrp, :puppet_prop => :gid, + :to => :gid_to_attr, :from => :gid_from_attr}, + {:aix_attr => :id, :puppet_prop => :uid}, + {:aix_attr => :groups, :puppet_prop => :groups}, + {:aix_attr => :home, :puppet_prop => :home}, + {:aix_attr => :shell, :puppet_prop => :shell}, + {:aix_attr => :expires, :puppet_prop => :expiry, + :to => :expiry_to_attr, :from => :expiry_from_attr}, + {:aix_attr => :maxage, :puppet_prop => :password_max_age}, + {:aix_attr => :minage, :puppet_prop => :password_min_age}, + ] + + #-------------- + # Command lines + + def self.lsgroupscmd(value=@resource[:name]) + [command(:lsgroup),"-R", ia_module, "-a", "id", value] + end + + def lscmd(value=@resource[:name]) + [self.class.command(:list), "-R", self.class.ia_module , value] + end + + def addcmd(extra_attrs = []) + # Here we use the @resource.to_hash to get the list of provided parameters + # Puppet does not call to self.= method if it does not exists. + # + # It gets an extra list of arguments to add to the user. + [self.class.command(:add), "-R", self.class.ia_module ]+ + self.class.hash2attr(@resource.to_hash) + + extra_attrs + [@resource[:name]] + end + + def modifycmd(hash = property_hash) + [self.class.command(:modify), "-R", self.class.ia_module ]+ + self.class.hash2attr(hash) + [@resource[:name]] + end + + def deletecmd + [self.class.command(:delete),"-R", self.class.ia_module, @resource[:name]] + end + + #-------------- + # We overwrite the create function to change the password after creation. + def create + super + # Reset the password if needed + self.password = @resource[:password] if @resource[:password] + end + + + # Get the groupname from its id + def self.groupname_by_id(gid) + groupname=nil + execute(lsgroupscmd("ALL")).each { |entry| + attrs = attr2hash(entry, nil) + if attrs and attrs.include? :id and gid == attrs[:id].to_i + groupname = entry.split(" ")[0] + end + } + groupname + end + + # Get the groupname from its id + def self.groupid_by_name(groupname) + attrs = attr2hash(execute(lsgroupscmd(groupname)).split("\n")[0], nil) + attrs ? attrs[:id].to_i : nil + end + + # Check that a group exists and is valid + def self.verify_group(value) + if value.is_a? Integer or value.is_a? Fixnum + groupname = self.groupname_by_id(value) + raise ArgumentError, "AIX group must be a valid existing group" unless groupname + else + raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) + groupname = value + end + groupname + end + + # The user's primary group. Can be specified numerically or by name. + def self.gid_to_attr(value) + verify_group(value) + end + + def self.gid_from_attr(value) + groupid_by_name(value) + end + + # The expiry date for this user. Must be provided in + # a zero padded YYYY-MM-DD HH:MM format + def self.expiry_to_attr(value) + # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format + # that is,"%m%d%H%M%y" + newdate = '0' + if value.is_a? String and value!="0000-00-00" + d = DateTime.parse(value, "%Y-%m-%d %H:%M") + newdate = d.strftime("%m%d%H%M%y") + end + newdate + end + + def self.expiry_from_attr(value) + if value =~ /(..)(..)(..)(..)(..)/ + #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") + #expiry_date = d.strftime("%Y-%m-%d %H:%M") + #expiry_date = d.strftime("%Y-%m-%d") + expiry_date = "20#{$5}-#{$1}-#{$2}" + else + Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ + unless value == '0' + expiry_date = :absent + end + expiry_date + end + + #-------------------------------- + # Getter and Setter + # When the provider is initialized, create getter/setter methods for each + # property our resource type supports. + # If setter or getter already defined it will not be overwritten + + #- **password** + # The user's password, in whatever encrypted format the local machine + # requires. Be sure to enclose any value that includes a dollar sign ($) + # in single quotes ('). Requires features manages_passwords. + # + # Retrieve the password parsing directly the /etc/security/passwd + def password + password = :absent + user = @resource[:name] + f = File.open("/etc/security/passwd", 'r') + # Skip to the user + f.each { |l| break if l =~ /^#{user}:\s*$/ } + if ! f.eof? + f.each { |l| + # If there is a new user stanza, stop + break if l =~ /^\S*:\s*$/ + # If the password= entry is found, return it + if l =~ /^\s*password\s*=\s*(.*)$/ + password = $1; break; + end + } + end + f.close() + return password + end + + def password=(value) + user = @resource[:name] + + # Puppet execute does not support strings as input, only files. + tmpfile = Tempfile.new('puppet_#{user}_pw') + tmpfile << "#{user}:#{value}\n" + tmpfile.close() + + # Options '-e', '-c', use encrypted password and clear flags + # Must receibe "user:enc_password" as input + # command, arguments = {:failonfail => true, :combine => true} + cmd = [self.class.command(:chpasswd),"-R", ia_module, + '-e', '-c', user] + begin + execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + ensure + tmpfile.delete() + end + end + + #- **comment** + # A description of the user. Generally is a user's full name. + #def comment=(value) + #end + # + #def comment + #end + # UNSUPPORTED + #- **profile_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **profiles** + # The profiles the user has. Multiple profiles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **project** + # The name of the project associated with a user Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **role_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **roles** + # The roles the user has. Multiple roles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **key_membership** + # Whether specified key value pairs should be treated as the only + # attributes + # of the user or whether they should merely + # be treated as the minimum list. Valid values are `inclusive`, + # `minimum`. + # UNSUPPORTED + #- **keys** + # Specify user attributes in an array of keyvalue pairs Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **allowdupe** + # Whether to allow duplicate UIDs. Valid values are `true`, `false`. + # UNSUPPORTED + #- **auths** + # The auths the user has. Multiple auths should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **auth_membership** + # Whether specified auths should be treated as the only auths + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + +end -- cgit From 52f8dddf75e4611de4ea60cb29f4de3e11739226 Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Mon, 27 Dec 2010 11:20:26 +0100 Subject: (#5432) Use AIX native commands to manage users and groups Refactorized the aixobject.rb to allow new providers using commands with colon separated output. --- lib/puppet/provider/aixobject.rb | 244 +++++++++++++++++++++++++++++---------- lib/puppet/provider/group/aix.rb | 2 +- lib/puppet/provider/user/aix.rb | 33 +++--- 3 files changed, 201 insertions(+), 78 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index 98ac13bdf..ae5180d5e 100755 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb @@ -12,20 +12,13 @@ class Puppet::Provider::AixObject < Puppet::Provider # TODO:: add a type parameter to change this class << self attr_accessor :ia_module - end - - - # AIX attributes to properties mapping. Subclasses should rewrite them - # It is a list with of hash - # :aix_attr AIX command attribute name - # :puppet_prop Puppet propertie name - # :to Method to adapt puppet property to aix command value. Optional. - # :from Method to adapt aix command value to puppet property. Optional - class << self - attr_accessor :attribute_mapping + end + + # The real provider must implement these functions. + def lscmd(value=@resource[:name]) + raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" end - # Provider must implement these functions. def lscmd(value=@resource[:name]) raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" end @@ -42,7 +35,13 @@ class Puppet::Provider::AixObject < Puppet::Provider raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" end - # attribute_mapping class variable, + + # Valid attributes to be managed by this provider. + # It is a list of hashes + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Optional. Method name that adapts puppet property to aix command value. + # :from Optional. Method to adapt aix command line value to puppet property. Optional class << self attr_accessor :attribute_mapping end @@ -70,12 +69,12 @@ class Puppet::Provider::AixObject < Puppet::Provider end @attribute_mapping_from end - + # This functions translates a key and value using the given mapping. # Mapping can be nil (no translation) or a hash with this format # {:key => new_key, :method => translate_method} # It returns a list [key, value] - def self.translate_attr(key, value, mapping) + def translate_attr(key, value, mapping) return [key, value] unless mapping return nil unless mapping[key] @@ -86,18 +85,103 @@ class Puppet::Provider::AixObject < Puppet::Provider end [mapping[key][:key], new_value] end + + # Gets the given command line argument for the given key, value and mapping. + def get_arg(key, value, mapping) + arg = nil + if ret = self.translate_attr(key, val, mapping) + new_key = ret[0] + new_val = ret[1] + + # Arrays are separated by commas + if new_val.is_a? Array + value = new_val.join(",") + else + value = new_val.to_s + end + + # Get the needed argument + if mapping[key][:to_arg] + arg = method(mapping[key][:to_arg]).call(new_key, value) + else + arg = (new_key.to_s + "=" + value ) + end + end + return arg + end + - #----- - # Convert a pair key-value using the + # Reads and attribute. + # Here we implement the default behaviour. + # Subclasses must reimplement this. + def load_attribute(key, value, mapping, objectinfo) + if mapping.nil? + objectinfo[key] = value + elsif mapping[key].nil? + # is not present in mapping, ignore it. + true + elsif mapping[key][:method].nil? + objectinfo[mapping[key][:key]] = value + elsif + objectinfo[mapping[key][:key]] = method(mapping[key][:method]).call(value) + end + + return objectinfo + end - # Parse AIX command attributes (string) and return provider hash + def get_arguments(key, value, mapping, objectinfo) + if mapping.nil? + new_key = key + new_value = value + elsif mapping[key].nil? + # is not present in mapping, ignore it. + new_key = nil + new_value = nil + elsif mapping[key][:method].nil? + new_key = mapping[key][:key] + new_value = value + elsif + new_key = mapping[key][:key] + new_value = method(mapping[key][:method]).call(value) + end + + # convert it to string + if new_val.is_a? Array + new_val = new_val.join(",") + else + new_val = new_val.to_s + end + + if new_key? + return [ "#{new_key}=#{new_value}" ] + else + return [] + end + end + + # Convert the provider properties to AIX command arguments (string) + # This function will translate each value/key and generate the argument. + # By default, arguments are created as aix_key=aix_value + def hash2args(hash, mapping=self.class.attribute_mapping_to) + return "" unless hash + arg_list = [] + hash.each {|key, val| + arg_list += self.get_arguments(key, val, mapping, hash) + } + arg_list + end + + # Parse AIX command attributes in a format of space separated of key=value + # pairs: "uid=100 groups=a,b,c" + # It returns and return provider hash. + # # If a mapping is provided, the keys are translated as defined in the # mapping hash. Only values included in mapping will be added # NOTE: it will ignore the items not including '=' - def self.attr2hash(str, mapping=attribute_mapping_from) + def parse_attr_list(str, mapping=self.class.attribute_mapping_from) properties = {} attrs = [] - if !str or (attrs = str.split()[0..-1]).empty? + if !str or (attrs = str.split()).empty? return nil end @@ -109,40 +193,53 @@ class Puppet::Provider::AixObject < Puppet::Provider info "Empty key in string 'i'?" continue end - key = key_str.to_sym + key = key_str.downcase.to_sym - if ret = self.translate_attr(key, val, mapping) - new_key = ret[0] - new_val = ret[1] - - properties[new_key] = new_val - end + properties = self.load_attribute(key, val, mapping, properties) end } properties.empty? ? nil : properties end - # Convert the provider properties to AIX command attributes (string) - def self.hash2attr(hash, mapping=attribute_mapping_to) - return "" unless hash - attr_list = [] - hash.each {|key, val| - - if ret = self.translate_attr(key, val, mapping) - new_key = ret[0] - new_val = ret[1] - - # Arrays are separated by commas - if new_val.is_a? Array - value = new_val.join(",") - else - value = new_val.to_s - end - - attr_list << (new_key.to_s + "=" + value ) - end + # Parse AIX colon separated list of attributes, using given list of keys + # to name the attributes. This function is useful to parse the output + # of commands like lsfs -c: + # #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct + # /:/dev/hd4:jfs2::bootfs:557056:rw:yes:no + # /home:/dev/hd1:jfs2:::2129920:rw:yes:no + # /usr:/dev/hd2:jfs2::bootfs:9797632:rw:yes:no + # + # If a mapping is provided, the keys are translated as defined in the + # mapping hash. Only values included in mapping will be added + # NOTE: it will ignore the items not including '=' + def parse_colon_list(str, key_list, mapping=self.class.attribute_mapping_from) + properties = {} + attrs = [] + if !str or (attrs = str.split(':')).empty? + return nil + end + + attrs.each { |val| + key = key_list.shift.downcase.to_sym + properties = self.load_attribute(key, val, mapping, properties) } - attr_list + properties.empty? ? nil : properties + + end + + # Default parsing function for colon separated list or attributte list + # (key=val pairs). It will choose the method depending of the first line. + # For the colon separated list it will: + # 1. Get keys from first line. + # 2. Parse next line. + def parse_command_output(output) + lines = output.split("\n") + # if it begins with #something:... is a colon separated list. + if lines[0] =~ /^#.*:/ + self.parse_colon_list(lines[1], lines[0][1..-1].split(':')) + else + self.parse_attr_list(lines[0]) + end end # Retrieve what we can about our object @@ -150,17 +247,33 @@ class Puppet::Provider::AixObject < Puppet::Provider if @objectinfo.nil? or refresh == true # Execute lsuser, split all attributes and add them to a dict. begin - attrs = execute(self.lscmd).split("\n")[0] - @objectinfo = self.class.attr2hash(attrs) + @objectinfo = self.parse_command_output(execute(self.lscmd)) rescue Puppet::ExecutionFailure => detail - # Print error if needed - Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" \ - unless detail.to_s.include? "User \"#{@resource.name}\" does not exist." + # Print error if needed. FIXME: Do not check the user here. + Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" end end @objectinfo end + # List all elements of given type. It works for colon separated commands and + # list commands. + def list_all + names = [] + begin + output = execute(self.lsallcmd()).split('\n') + (output.select{ |l| l != /^#/ }).each { |v| + name = v.split(/[ :]/) + names << name if not name.empty? + } + rescue Puppet::ExecutionFailure => detail + # Print error if needed + Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}" + end + names + end + + #------------- # Provider API # ------------ @@ -168,7 +281,7 @@ class Puppet::Provider::AixObject < Puppet::Provider # Clear out the cached values. def flush @property_hash.clear if @property_hash - @object_info.clear if @object_info + @objectinfo.clear if @objectinfo end # Check that the user exists @@ -192,9 +305,9 @@ class Puppet::Provider::AixObject < Puppet::Provider # The method for returning a list of provider instances. Note that it returns # providers, preferably with values already filled in, not resources. def self.instances - objects = [] - execute(lscmd("ALL")).each { |entry| - objects << new(:name => entry.split(" ")[0], :ensure => :present) + objects=[] + self.list_all().each { |entry| + objects << new(:name => entry, :ensure => :present) } objects end @@ -254,17 +367,23 @@ class Puppet::Provider::AixObject < Puppet::Provider # Set a property. def set(param, value) @property_hash[symbolize(param)] = value - # If value does not change, do not update. + + if getinfo().nil? + # This is weird... + raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}" + end if value == getinfo()[param.to_sym] return end #self.class.validate(param, value) - cmd = modifycmd({param => value}) - begin - execute(cmd) - rescue Puppet::ExecutionFailure => detail - raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + + if cmd = modifycmd({param =>value}) + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end end # Refresh de info. @@ -279,4 +398,3 @@ class Puppet::Provider::AixObject < Puppet::Provider end end -#end diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb index bcb81e1c5..0c3122b86 100755 --- a/lib/puppet/provider/group/aix.rb +++ b/lib/puppet/provider/group/aix.rb @@ -69,7 +69,7 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d end # Force convert users it a list. - def self.users_from_attr(value) + def users_from_attr(value) (value.is_a? String) ? value.split(',') : value end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index ba95dbc49..4a5c4ee79 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -26,23 +26,24 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # Constants # Default extra attributes to add when element is created # registry=compat SYSTEM=compat: Needed if you are using LDAP by default. - @DEFAULT_EXTRA_ATTRS = [ "registry=compat", " SYSTEM=compat" ] + @DEFAULT_EXTRA_ATTRS = [ "registry=compat", "SYSTEM=compat" ] # This will the the default provider for this platform defaultfor :operatingsystem => :aix confine :operatingsystem => :aix # Commands that manage the element - commands :lsgroup => "/usr/sbin/lsgroup" - commands :list => "/usr/sbin/lsuser" commands :add => "/usr/bin/mkuser" commands :delete => "/usr/sbin/rmuser" commands :modify => "/usr/bin/chuser" + + commands :lsgroup => "/usr/sbin/lsgroup" commands :chpasswd => "/bin/chpasswd" # Provider features - has_features :manages_homedir, :manages_passwords, :manages_expiry, :manages_password_age + has_features :manages_homedir, :manages_passwords + has_features :manages_expiry, :manages_password_age # Attribute verification (TODO) #verify :gid, "GID must be an string or int of a valid group" do |value| @@ -78,14 +79,18 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do #-------------- # Command lines - def self.lsgroupscmd(value=@resource[:name]) - [command(:lsgroup),"-R", ia_module, "-a", "id", value] + def lsgroupscmd(value=@resource[:name]) + [command(:lsgroup),"-R", self.class.ia_module, "-a", "id", value] end def lscmd(value=@resource[:name]) [self.class.command(:list), "-R", self.class.ia_module , value] end + def lsallcmd() + lscmd("ALL") + end + def addcmd(extra_attrs = []) # Here we use the @resource.to_hash to get the list of provided parameters # Puppet does not call to self.= method if it does not exists. @@ -118,7 +123,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do def self.groupname_by_id(gid) groupname=nil execute(lsgroupscmd("ALL")).each { |entry| - attrs = attr2hash(entry, nil) + attrs = parse_attr_list(entry, nil) if attrs and attrs.include? :id and gid == attrs[:id].to_i groupname = entry.split(" ")[0] end @@ -127,13 +132,13 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end # Get the groupname from its id - def self.groupid_by_name(groupname) - attrs = attr2hash(execute(lsgroupscmd(groupname)).split("\n")[0], nil) + def groupid_by_name(groupname) + attrs = parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) attrs ? attrs[:id].to_i : nil end # Check that a group exists and is valid - def self.verify_group(value) + def verify_group(value) if value.is_a? Integer or value.is_a? Fixnum groupname = self.groupname_by_id(value) raise ArgumentError, "AIX group must be a valid existing group" unless groupname @@ -145,17 +150,17 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end # The user's primary group. Can be specified numerically or by name. - def self.gid_to_attr(value) + def gid_to_attr(value) verify_group(value) end - def self.gid_from_attr(value) + def gid_from_attr(value) groupid_by_name(value) end # The expiry date for this user. Must be provided in # a zero padded YYYY-MM-DD HH:MM format - def self.expiry_to_attr(value) + def expiry_to_attr(value) # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format # that is,"%m%d%H%M%y" newdate = '0' @@ -166,7 +171,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do newdate end - def self.expiry_from_attr(value) + def expiry_from_attr(value) if value =~ /(..)(..)(..)(..)(..)/ #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") #expiry_date = d.strftime("%Y-%m-%d %H:%M") -- cgit From d65e094c05739324b14a921152092e080aa32b4e Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Mon, 27 Dec 2010 14:20:24 +0100 Subject: (#5432) Use AIX native commands to manage users and groups Fixed providers for new aixobject.rb Fixed incorrect variable naming. --- lib/puppet/provider/aixobject.rb | 8 ++++---- lib/puppet/provider/group/aix.rb | 8 ++++++-- lib/puppet/provider/user/aix.rb | 10 +++++----- 3 files changed, 15 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index ae5180d5e..d98e36b18 100755 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb @@ -146,13 +146,13 @@ class Puppet::Provider::AixObject < Puppet::Provider end # convert it to string - if new_val.is_a? Array - new_val = new_val.join(",") + if new_value.is_a? Array + new_value = new_value.join(",") else - new_val = new_val.to_s + new_value = new_value.to_s end - if new_key? + if new_key return [ "#{new_key}=#{new_value}" ] else return [] diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb index 0c3122b86..6ae16b529 100755 --- a/lib/puppet/provider/group/aix.rb +++ b/lib/puppet/provider/group/aix.rb @@ -49,19 +49,23 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d [self.class.command(:list), "-R", self.class.ia_module , value] end + def lsallcmd() + lscmd("ALL") + end + def addcmd(extra_attrs = []) # Here we use the @resource.to_hash to get the list of provided parameters # Puppet does not call to self.= method if it does not exists. # # It gets an extra list of arguments to add to the user. [self.class.command(:add), "-R", self.class.ia_module ]+ - self.class.hash2attr(@resource.to_hash) + + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end def modifycmd(hash = property_hash) [self.class.command(:modify), "-R", self.class.ia_module ]+ - self.class.hash2attr(hash) + [@resource[:name]] + self.hash2args(hash) + [@resource[:name]] end def deletecmd diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index 4a5c4ee79..06dfd5240 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -97,13 +97,13 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # # It gets an extra list of arguments to add to the user. [self.class.command(:add), "-R", self.class.ia_module ]+ - self.class.hash2attr(@resource.to_hash) + + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end def modifycmd(hash = property_hash) [self.class.command(:modify), "-R", self.class.ia_module ]+ - self.class.hash2attr(hash) + [@resource[:name]] + self.hash2args(hash) + [@resource[:name]] end def deletecmd @@ -123,7 +123,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do def self.groupname_by_id(gid) groupname=nil execute(lsgroupscmd("ALL")).each { |entry| - attrs = parse_attr_list(entry, nil) + attrs = self.parse_attr_list(entry, nil) if attrs and attrs.include? :id and gid == attrs[:id].to_i groupname = entry.split(" ")[0] end @@ -133,7 +133,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # Get the groupname from its id def groupid_by_name(groupname) - attrs = parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) + attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) attrs ? attrs[:id].to_i : nil end @@ -228,7 +228,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # Options '-e', '-c', use encrypted password and clear flags # Must receibe "user:enc_password" as input # command, arguments = {:failonfail => true, :combine => true} - cmd = [self.class.command(:chpasswd),"-R", ia_module, + cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module, '-e', '-c', user] begin execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) -- cgit From aa8c09fbb0298adb48654a68778e46d9f68f9fb9 Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Tue, 28 Dec 2010 14:46:38 +0100 Subject: (#5432) Use AIX native commands to manage users and groups Specific providers should be created for AIX to manage users and groups. AIX bases the authentication management on a set of commands: mkuser, rmuser, chuser, lsuser, mkgroup, rmgroup, chgroup, lsg This commit implements such providers. The types where extended with: - feature: :manages_aix_lam - newparam(ia_load_module): authentication module to use in user and group AIX providers. - newproperty("attributes"): AIX attributes that are not managed by puppet directely. Notes:: - AIX users can have expiry date defined with minute granularity, but puppet does not allow it. There is a ticket open for that (#5431) - AIX maximum password age is in WEEKs, not days. Squashed commit of the following: commit dd9efff227f60dd7d11e33f754870f6344526097 Author: Hector Rivas Gandara Date: Tue Dec 28 14:42:35 2010 +0100 Added new property "attributes" in AIX group provider for AIX attributes that are not managed by puppet directely. commit cd23fff3a3a4963f12c0029d43db3c530a0e4fa3 Author: Hector Rivas Gandara Date: Tue Dec 28 14:33:50 2010 +0100 Converted file from Windows to Unix format commit c6929e9b557802fe908469ca51339ce249242c94 Author: Hector Rivas Gandara Date: Tue Dec 28 14:32:29 2010 +0100 Added new property "attributes" in AIX user provider for AIX attributes that are not managed by puppet directely. commit d2814dc9d7730635a17a9bdbbc6ef9518f23b39d Author: Hector Rivas Gandara Date: Tue Dec 28 12:48:07 2010 +0100 Added feature :ia_load_module and new parameter (newparam) ia_load_module, to define authentication module to use in user and group AIX providers. commit 95a4e960a93c1b75a4a0053c07fa192bf552d63b Author: Hector Rivas Gandara Date: Tue Dec 28 12:46:20 2010 +0100 Fixed typo in if sentence --- lib/puppet/provider/aixobject.rb | 23 +- lib/puppet/provider/group/aix.rb | 88 +++++- lib/puppet/provider/user/aix.rb | 650 +++++++++++++++++++++------------------ lib/puppet/type/group.rb | 36 +++ lib/puppet/type/user.rb | 37 +++ 5 files changed, 516 insertions(+), 318 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index d98e36b18..dcb3bff78 100755 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb @@ -193,7 +193,7 @@ class Puppet::Provider::AixObject < Puppet::Provider info "Empty key in string 'i'?" continue end - key = key_str.downcase.to_sym + key = key_str.to_sym properties = self.load_attribute(key, val, mapping, properties) end @@ -232,13 +232,13 @@ class Puppet::Provider::AixObject < Puppet::Provider # For the colon separated list it will: # 1. Get keys from first line. # 2. Parse next line. - def parse_command_output(output) + def parse_command_output(output, mapping=self.class.attribute_mapping_from) lines = output.split("\n") # if it begins with #something:... is a colon separated list. if lines[0] =~ /^#.*:/ - self.parse_colon_list(lines[1], lines[0][1..-1].split(':')) + self.parse_colon_list(lines[1], lines[0][1..-1].split(':'), mapping) else - self.parse_attr_list(lines[0]) + self.parse_attr_list(lines[0], mapping) end end @@ -247,7 +247,10 @@ class Puppet::Provider::AixObject < Puppet::Provider if @objectinfo.nil? or refresh == true # Execute lsuser, split all attributes and add them to a dict. begin + output = execute(self.lscmd) @objectinfo = self.parse_command_output(execute(self.lscmd)) + # All attributtes without translation + @objectosinfo = self.parse_command_output(execute(self.lscmd), nil) rescue Puppet::ExecutionFailure => detail # Print error if needed. FIXME: Do not check the user here. Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}" @@ -256,6 +259,15 @@ class Puppet::Provider::AixObject < Puppet::Provider @objectinfo end + # Retrieve what we can about our object, without translate the values. + def getosinfo(refresh = false) + if @objectosinfo .nil? or refresh == true + getinfo(refresh) + end + @objectosinfo + end + + # List all elements of given type. It works for colon separated commands and # list commands. def list_all @@ -369,7 +381,7 @@ class Puppet::Provider::AixObject < Puppet::Provider @property_hash[symbolize(param)] = value if getinfo().nil? - # This is weird... + # This is weird... raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}" end if value == getinfo()[param.to_sym] @@ -377,7 +389,6 @@ class Puppet::Provider::AixObject < Puppet::Provider end #self.class.validate(param, value) - if cmd = modifycmd({param =>value}) begin execute(cmd) diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb index 6ae16b529..ceb889840 100755 --- a/lib/puppet/provider/group/aix.rb +++ b/lib/puppet/provider/group/aix.rb @@ -9,17 +9,12 @@ require 'puppet/provider/aixobject' Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject do desc "Group management for AIX! Users are managed with mkgroup, rmgroup, lsgroup, chgroup" - # Constants - # Default extra attributes to add when element is created - # registry=compat: Needed if you are using LDAP by default. - @DEFAULT_EXTRA_ATTRS = [ "registry=compat", ] - - - # This will the the default provider for this platform + # This will the the default provider for this platform defaultfor :operatingsystem => :aix confine :operatingsystem => :aix # Provider features + has_features :manages_aix_lam has_features :manages_members # Commands that manage the element @@ -28,6 +23,11 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d commands :delete => "/usr/sbin/rmgroup" commands :modify => "/usr/bin/chgroup" + # Group attributes to ignore + def self.attribute_ignore + [] + end + # AIX attributes to properties mapping. # # Valid attributes to be managed by this provider. @@ -45,8 +45,20 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d #-------------- # Command lines + + def get_ia_module_args + if @resource[:ia_load_module] + ["-R", @resource[:ia_load_module].to_s] + else + [] + end + end + + def lscmd(value=@resource[:name]) - [self.class.command(:list), "-R", self.class.ia_module , value] + [self.class.command(:list)] + + self.get_ia_module_args + + [ value] end def lsallcmd() @@ -58,18 +70,40 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d # Puppet does not call to self.= method if it does not exists. # # It gets an extra list of arguments to add to the user. - [self.class.command(:add), "-R", self.class.ia_module ]+ + [self.class.command(:add) ] + + self.get_ia_module_args + self.hash2args(@resource.to_hash) + extra_attrs + [@resource[:name]] end - def modifycmd(hash = property_hash) - [self.class.command(:modify), "-R", self.class.ia_module ]+ - self.hash2args(hash) + [@resource[:name]] + def modifycmd(hash = property_hash, translate=true) + if translate + args = self.hash2args(hash) + else + args = self.hash2args(hash, nil) + end + return nil if args.empty? + + [self.class.command(:modify)] + + self.get_ia_module_args + + args + [@resource[:name]] end def deletecmd - [self.class.command(:delete),"-R", self.class.ia_module, @resource[:name]] + [self.class.command(:delete)] + + self.get_ia_module_args + + [@resource[:name]] + end + + + def get_arguments(key, value, mapping, objectinfo) + # In the case of attributes, return a list of key=vlaue + if key == :attributes + raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ + unless value and value.is_a? Hash + return value.select { |k,v| true }.map { |pair| pair.join("=") } + end + super(key, value, mapping, objectinfo) end # Force convert users it a list. @@ -77,4 +111,32 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d (value.is_a? String) ? value.split(',') : value end + def filter_attributes(hash) + # Return only not managed attributtes. + hash.select { + |k,v| !self.class.attribute_mapping_from.include?(k) and + !self.class.attribute_ignore.include?(k) + }.inject({}) { + |hash, array| hash[array[0]] = array[1]; hash + } + end + + def attributes + filter_attributes(getosinfo(refresh = false)) + end + + def attributes=(attr_hash) + #self.class.validate(param, value) + param = :attributes + cmd = modifycmd({param => filter_attributes(attr_hash)}, false) + if cmd + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + end + end + + end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index 06dfd5240..c231da816 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -1,299 +1,351 @@ -# -# User Puppet provider for AIX. It uses standar commands to manage users: -# mkuser, rmuser, lsuser, chuser -# -# Notes: -# - AIX users can have expiry date defined with minute granularity, -# but puppet does not allow it. There is a ticket open for that (#5431) -# - AIX maximum password age is in WEEKs, not days -# - I force the compat IA module. -# -# See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development -# for more information -# -# Author:: Hector Rivas Gandara -# -# TODO:: -# - Add new AIX specific attributes, specilly registry and SYSTEM. -# -require 'puppet/provider/aixobject' -require 'tempfile' -require 'date' - -Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do - desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" - - # Constants - # Default extra attributes to add when element is created - # registry=compat SYSTEM=compat: Needed if you are using LDAP by default. - @DEFAULT_EXTRA_ATTRS = [ "registry=compat", "SYSTEM=compat" ] - - # This will the the default provider for this platform - defaultfor :operatingsystem => :aix - confine :operatingsystem => :aix - - # Commands that manage the element - commands :list => "/usr/sbin/lsuser" - commands :add => "/usr/bin/mkuser" - commands :delete => "/usr/sbin/rmuser" - commands :modify => "/usr/bin/chuser" - - commands :lsgroup => "/usr/sbin/lsgroup" - commands :chpasswd => "/bin/chpasswd" - - # Provider features - has_features :manages_homedir, :manages_passwords - has_features :manages_expiry, :manages_password_age - - # Attribute verification (TODO) - #verify :gid, "GID must be an string or int of a valid group" do |value| - # value.is_a? String || value.is_a? Integer - #end - # - #verify :groups, "Groups must be comma-separated" do |value| - # value !~ /\s/ - #end - - # AIX attributes to properties mapping. - # - # Valid attributes to be managed by this provider. - # It is a list with of hash - # :aix_attr AIX command attribute name - # :puppet_prop Puppet propertie name - # :to Method to adapt puppet property to aix command value. Optional. - # :from Method to adapt aix command value to puppet property. Optional - self.attribute_mapping = [ - #:name => :name, - {:aix_attr => :pgrp, :puppet_prop => :gid, - :to => :gid_to_attr, :from => :gid_from_attr}, - {:aix_attr => :id, :puppet_prop => :uid}, - {:aix_attr => :groups, :puppet_prop => :groups}, - {:aix_attr => :home, :puppet_prop => :home}, - {:aix_attr => :shell, :puppet_prop => :shell}, - {:aix_attr => :expires, :puppet_prop => :expiry, - :to => :expiry_to_attr, :from => :expiry_from_attr}, - {:aix_attr => :maxage, :puppet_prop => :password_max_age}, - {:aix_attr => :minage, :puppet_prop => :password_min_age}, - ] - - #-------------- - # Command lines - - def lsgroupscmd(value=@resource[:name]) - [command(:lsgroup),"-R", self.class.ia_module, "-a", "id", value] - end - - def lscmd(value=@resource[:name]) - [self.class.command(:list), "-R", self.class.ia_module , value] - end - - def lsallcmd() - lscmd("ALL") - end - - def addcmd(extra_attrs = []) - # Here we use the @resource.to_hash to get the list of provided parameters - # Puppet does not call to self.= method if it does not exists. - # - # It gets an extra list of arguments to add to the user. - [self.class.command(:add), "-R", self.class.ia_module ]+ - self.hash2args(@resource.to_hash) + - extra_attrs + [@resource[:name]] - end - - def modifycmd(hash = property_hash) - [self.class.command(:modify), "-R", self.class.ia_module ]+ - self.hash2args(hash) + [@resource[:name]] - end - - def deletecmd - [self.class.command(:delete),"-R", self.class.ia_module, @resource[:name]] - end - - #-------------- - # We overwrite the create function to change the password after creation. - def create - super - # Reset the password if needed - self.password = @resource[:password] if @resource[:password] - end - - - # Get the groupname from its id - def self.groupname_by_id(gid) - groupname=nil - execute(lsgroupscmd("ALL")).each { |entry| - attrs = self.parse_attr_list(entry, nil) - if attrs and attrs.include? :id and gid == attrs[:id].to_i - groupname = entry.split(" ")[0] - end - } - groupname - end - - # Get the groupname from its id - def groupid_by_name(groupname) - attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) - attrs ? attrs[:id].to_i : nil - end - - # Check that a group exists and is valid - def verify_group(value) - if value.is_a? Integer or value.is_a? Fixnum - groupname = self.groupname_by_id(value) - raise ArgumentError, "AIX group must be a valid existing group" unless groupname - else - raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) - groupname = value - end - groupname - end - - # The user's primary group. Can be specified numerically or by name. - def gid_to_attr(value) - verify_group(value) - end - - def gid_from_attr(value) - groupid_by_name(value) - end - - # The expiry date for this user. Must be provided in - # a zero padded YYYY-MM-DD HH:MM format - def expiry_to_attr(value) - # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format - # that is,"%m%d%H%M%y" - newdate = '0' - if value.is_a? String and value!="0000-00-00" - d = DateTime.parse(value, "%Y-%m-%d %H:%M") - newdate = d.strftime("%m%d%H%M%y") - end - newdate - end - - def expiry_from_attr(value) - if value =~ /(..)(..)(..)(..)(..)/ - #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") - #expiry_date = d.strftime("%Y-%m-%d %H:%M") - #expiry_date = d.strftime("%Y-%m-%d") - expiry_date = "20#{$5}-#{$1}-#{$2}" - else - Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ - unless value == '0' - expiry_date = :absent - end - expiry_date - end - - #-------------------------------- - # Getter and Setter - # When the provider is initialized, create getter/setter methods for each - # property our resource type supports. - # If setter or getter already defined it will not be overwritten - - #- **password** - # The user's password, in whatever encrypted format the local machine - # requires. Be sure to enclose any value that includes a dollar sign ($) - # in single quotes ('). Requires features manages_passwords. - # - # Retrieve the password parsing directly the /etc/security/passwd - def password - password = :absent - user = @resource[:name] - f = File.open("/etc/security/passwd", 'r') - # Skip to the user - f.each { |l| break if l =~ /^#{user}:\s*$/ } - if ! f.eof? - f.each { |l| - # If there is a new user stanza, stop - break if l =~ /^\S*:\s*$/ - # If the password= entry is found, return it - if l =~ /^\s*password\s*=\s*(.*)$/ - password = $1; break; - end - } - end - f.close() - return password - end - - def password=(value) - user = @resource[:name] - - # Puppet execute does not support strings as input, only files. - tmpfile = Tempfile.new('puppet_#{user}_pw') - tmpfile << "#{user}:#{value}\n" - tmpfile.close() - - # Options '-e', '-c', use encrypted password and clear flags - # Must receibe "user:enc_password" as input - # command, arguments = {:failonfail => true, :combine => true} - cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module, - '-e', '-c', user] - begin - execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) - rescue Puppet::ExecutionFailure => detail - raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" - ensure - tmpfile.delete() - end - end - - #- **comment** - # A description of the user. Generally is a user's full name. - #def comment=(value) - #end - # - #def comment - #end - # UNSUPPORTED - #- **profile_membership** - # Whether specified roles should be treated as the only roles - # of which the user is a member or whether they should merely - # be treated as the minimum membership list. Valid values are - # `inclusive`, `minimum`. - # UNSUPPORTED - #- **profiles** - # The profiles the user has. Multiple profiles should be - # specified as an array. Requires features manages_solaris_rbac. - # UNSUPPORTED - #- **project** - # The name of the project associated with a user Requires features - # manages_solaris_rbac. - # UNSUPPORTED - #- **role_membership** - # Whether specified roles should be treated as the only roles - # of which the user is a member or whether they should merely - # be treated as the minimum membership list. Valid values are - # `inclusive`, `minimum`. - # UNSUPPORTED - #- **roles** - # The roles the user has. Multiple roles should be - # specified as an array. Requires features manages_solaris_rbac. - # UNSUPPORTED - #- **key_membership** - # Whether specified key value pairs should be treated as the only - # attributes - # of the user or whether they should merely - # be treated as the minimum list. Valid values are `inclusive`, - # `minimum`. - # UNSUPPORTED - #- **keys** - # Specify user attributes in an array of keyvalue pairs Requires features - # manages_solaris_rbac. - # UNSUPPORTED - #- **allowdupe** - # Whether to allow duplicate UIDs. Valid values are `true`, `false`. - # UNSUPPORTED - #- **auths** - # The auths the user has. Multiple auths should be - # specified as an array. Requires features manages_solaris_rbac. - # UNSUPPORTED - #- **auth_membership** - # Whether specified auths should be treated as the only auths - # of which the user is a member or whether they should merely - # be treated as the minimum membership list. Valid values are - # `inclusive`, `minimum`. - # UNSUPPORTED - -end +# +# User Puppet provider for AIX. It uses standar commands to manage users: +# mkuser, rmuser, lsuser, chuser +# +# Notes: +# - AIX users can have expiry date defined with minute granularity, +# but puppet does not allow it. There is a ticket open for that (#5431) +# - AIX maximum password age is in WEEKs, not days +# - I force the compat IA module. +# +# See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development +# for more information +# +# Author:: Hector Rivas Gandara +# +require 'puppet/provider/aixobject' +require 'tempfile' +require 'date' + +Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do + desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" + + # This will the the default provider for this platform + defaultfor :operatingsystem => :aix + confine :operatingsystem => :aix + + # Commands that manage the element + commands :list => "/usr/sbin/lsuser" + commands :add => "/usr/bin/mkuser" + commands :delete => "/usr/sbin/rmuser" + commands :modify => "/usr/bin/chuser" + + commands :lsgroup => "/usr/sbin/lsgroup" + commands :chpasswd => "/bin/chpasswd" + + # Provider features + has_features :manages_aix_lam + has_features :manages_homedir, :manages_passwords + has_features :manages_expiry, :manages_password_age + + # Attribute verification (TODO) + #verify :gid, "GID must be an string or int of a valid group" do |value| + # value.is_a? String || value.is_a? Integer + #end + # + #verify :groups, "Groups must be comma-separated" do |value| + # value !~ /\s/ + #end + + # User attributes to ignore + def self.attribute_ignore + [] + end + + # AIX attributes to properties mapping. + # + # Valid attributes to be managed by this provider. + # It is a list with of hash + # :aix_attr AIX command attribute name + # :puppet_prop Puppet propertie name + # :to Method to adapt puppet property to aix command value. Optional. + # :from Method to adapt aix command value to puppet property. Optional + self.attribute_mapping = [ + #:name => :name, + {:aix_attr => :pgrp, :puppet_prop => :gid, + :to => :gid_to_attr, :from => :gid_from_attr}, + {:aix_attr => :id, :puppet_prop => :uid}, + {:aix_attr => :groups, :puppet_prop => :groups}, + {:aix_attr => :home, :puppet_prop => :home}, + {:aix_attr => :shell, :puppet_prop => :shell}, + {:aix_attr => :expires, :puppet_prop => :expiry, + :to => :expiry_to_attr, :from => :expiry_from_attr}, + {:aix_attr => :maxage, :puppet_prop => :password_max_age}, + {:aix_attr => :minage, :puppet_prop => :password_min_age}, + ] + + #-------------- + # Command lines + def get_ia_module_args + if @resource[:ia_load_module] + ["-R", @resource[:ia_load_module].to_s] + else + [] + end + end + + def lsgroupscmd(value=@resource[:name]) + [command(:lsgroup)] + + self.get_ia_module_args + + ["-a", "id", value] + end + + def lscmd(value=@resource[:name]) + [self.class.command(:list)] + self.get_ia_module_args + [ value] + end + + def lsallcmd() + lscmd("ALL") + end + + def addcmd(extra_attrs = []) + # Here we use the @resource.to_hash to get the list of provided parameters + # Puppet does not call to self.= method if it does not exists. + # + # It gets an extra list of arguments to add to the user. + [self.class.command(:add)] + self.get_ia_module_args + + self.hash2args(@resource.to_hash) + + extra_attrs + [@resource[:name]] + end + + # Get modify command + def modifycmd(hash = property_hash, translate=true) + if translate + args = self.hash2args(hash) + else + args = self.hash2args(hash, nil) + end + return nil if args.empty? + + [self.class.command(:modify)] + self.get_ia_module_args + + args + [@resource[:name]] + end + + def deletecmd + [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]] + end + + #-------------- + # We overwrite the create function to change the password after creation. + def create + super + # Reset the password if needed + self.password = @resource[:password] if @resource[:password] + end + + + def get_arguments(key, value, mapping, objectinfo) + # In the case of attributes, return a list of key=vlaue + if key == :attributes + raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \ + unless value and value.is_a? Hash + return value.select { |k,v| true }.map { |pair| pair.join("=") } + end + super(key, value, mapping, objectinfo) + end + + # Get the groupname from its id + def self.groupname_by_id(gid) + groupname=nil + execute(lsgroupscmd("ALL")).each { |entry| + attrs = self.parse_attr_list(entry, nil) + if attrs and attrs.include? :id and gid == attrs[:id].to_i + groupname = entry.split(" ")[0] + end + } + groupname + end + + # Get the groupname from its id + def groupid_by_name(groupname) + attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil) + attrs ? attrs[:id].to_i : nil + end + + # Check that a group exists and is valid + def verify_group(value) + if value.is_a? Integer or value.is_a? Fixnum + groupname = self.groupname_by_id(value) + raise ArgumentError, "AIX group must be a valid existing group" unless groupname + else + raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value) + groupname = value + end + groupname + end + + # The user's primary group. Can be specified numerically or by name. + def gid_to_attr(value) + verify_group(value) + end + + def gid_from_attr(value) + groupid_by_name(value) + end + + # The expiry date for this user. Must be provided in + # a zero padded YYYY-MM-DD HH:MM format + def expiry_to_attr(value) + # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format + # that is,"%m%d%H%M%y" + newdate = '0' + if value.is_a? String and value!="0000-00-00" + d = DateTime.parse(value, "%Y-%m-%d %H:%M") + newdate = d.strftime("%m%d%H%M%y") + end + newdate + end + + def expiry_from_attr(value) + if value =~ /(..)(..)(..)(..)(..)/ + #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}") + #expiry_date = d.strftime("%Y-%m-%d %H:%M") + #expiry_date = d.strftime("%Y-%m-%d") + expiry_date = "20#{$5}-#{$1}-#{$2}" + else + Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \ + unless value == '0' + expiry_date = :absent + end + expiry_date + end + + #-------------------------------- + # Getter and Setter + # When the provider is initialized, create getter/setter methods for each + # property our resource type supports. + # If setter or getter already defined it will not be overwritten + + #- **password** + # The user's password, in whatever encrypted format the local machine + # requires. Be sure to enclose any value that includes a dollar sign ($) + # in single quotes ('). Requires features manages_passwords. + # + # Retrieve the password parsing directly the /etc/security/passwd + def password + password = :absent + user = @resource[:name] + f = File.open("/etc/security/passwd", 'r') + # Skip to the user + f.each { |l| break if l =~ /^#{user}:\s*$/ } + if ! f.eof? + f.each { |l| + # If there is a new user stanza, stop + break if l =~ /^\S*:\s*$/ + # If the password= entry is found, return it + if l =~ /^\s*password\s*=\s*(.*)$/ + password = $1; break; + end + } + end + f.close() + return password + end + + def password=(value) + user = @resource[:name] + + # Puppet execute does not support strings as input, only files. + tmpfile = Tempfile.new('puppet_#{user}_pw') + tmpfile << "#{user}:#{value}\n" + tmpfile.close() + + # Options '-e', '-c', use encrypted password and clear flags + # Must receibe "user:enc_password" as input + # command, arguments = {:failonfail => true, :combine => true} + cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module, + '-e', '-c', user] + begin + execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path }) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + ensure + tmpfile.delete() + end + end + + def filter_attributes(hash) + # Return only not managed attributtes. + hash.select { + |k,v| !self.class.attribute_mapping_from.include?(k) and + !self.class.attribute_ignore.include?(k) + }.inject({}) { + |hash, array| hash[array[0]] = array[1]; hash + } + end + + def attributes + filter_attributes(getosinfo(refresh = false)) + end + + def attributes=(attr_hash) + #self.class.validate(param, value) + param = :attributes + cmd = modifycmd({param => filter_attributes(attr_hash)}, false) + if cmd + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}" + end + end + end + + #- **comment** + # A description of the user. Generally is a user's full name. + #def comment=(value) + #end + # + #def comment + #end + # UNSUPPORTED + #- **profile_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **profiles** + # The profiles the user has. Multiple profiles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **project** + # The name of the project associated with a user Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **role_membership** + # Whether specified roles should be treated as the only roles + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + #- **roles** + # The roles the user has. Multiple roles should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **key_membership** + # Whether specified key value pairs should be treated as the only + # attributes + # of the user or whether they should merely + # be treated as the minimum list. Valid values are `inclusive`, + # `minimum`. + # UNSUPPORTED + #- **keys** + # Specify user attributes in an array of keyvalue pairs Requires features + # manages_solaris_rbac. + # UNSUPPORTED + #- **allowdupe** + # Whether to allow duplicate UIDs. Valid values are `true`, `false`. + # UNSUPPORTED + #- **auths** + # The auths the user has. Multiple auths should be + # specified as an array. Requires features manages_solaris_rbac. + # UNSUPPORTED + #- **auth_membership** + # Whether specified auths should be treated as the only auths + # of which the user is a member or whether they should merely + # be treated as the minimum membership list. Valid values are + # `inclusive`, `minimum`. + # UNSUPPORTED + +end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index cde1cfd65..aa96bd9c3 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -15,6 +15,9 @@ module Puppet feature :manages_members, "For directories where membership is an attribute of groups not users." + feature :manages_aix_lam, + "The provider can manage AIX Loadable Authentication Module (LAM) system." + ensurable do desc "Create or remove the group." @@ -95,5 +98,38 @@ module Puppet defaultto false end + + newparam(:ia_load_module, :required_features => :manages_aix_lam) do + desc "The name of the I&A module to use to manage this user" + + defaultto "compat" + end + + newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do + desc "Specify group AIX attributes in an array of keyvalue pairs" + + def membership + :attribute_membership + end + + def delimiter + " " + end + + validate do |value| + raise ArgumentError, "Attributes value pairs must be seperated by an =" unless value.include?("=") + end + end + + newparam(:attribute_membership) do + desc "Whether specified attribute value pairs should be treated as the only attributes + of the user or whether they should merely + be treated as the minimum list." + + newvalues(:inclusive, :minimum) + + defaultto :minimum + end + end end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 761d5d71b..b2cafadc0 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -34,6 +34,9 @@ module Puppet feature :manages_expiry, "The provider can manage the expiry date for a user." + feature :manages_aix_lam, + "The provider can manage AIX Loadable Authentication Module (LAM) system." + newproperty(:ensure, :parent => Puppet::Property::Ensure) do newvalue(:present, :event => :user_created) do provider.create @@ -434,5 +437,39 @@ module Puppet newproperty(:project, :required_features => :manages_solaris_rbac) do desc "The name of the project associated with a user" end + + newparam(:ia_load_module, :required_features => :manages_aix_lam) do + desc "The name of the I&A module to use to manage this user" + + defaultto "compat" + end + + newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do + desc "Specify user AIX attributes in an array of keyvalue pairs" + + def membership + :attribute_membership + end + + def delimiter + " " + end + + validate do |value| + raise ArgumentError, "Attributes value pairs must be seperated by an =" unless value.include?("=") + end + end + + newparam(:attribute_membership) do + desc "Whether specified attribute value pairs should be treated as the only attributes + of the user or whether they should merely + be treated as the minimum list." + + newvalues(:inclusive, :minimum) + + defaultto :minimum + end + + end end -- cgit From fb339cbbc17cd14ba34df624fe9a2b9c74640eb5 Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Mon, 10 Jan 2011 08:30:28 +0100 Subject: (#5432) Use AIX native commands to manage users and groups More elegant Array|String => String conversion in AixObject. Fixed some bugs and code/comments cleaned. --- lib/puppet/provider/aixobject.rb | 142 +++++++++++++++++---------------------- lib/puppet/provider/group/aix.rb | 31 +++++---- lib/puppet/provider/user/aix.rb | 44 ++++++------ 3 files changed, 100 insertions(+), 117 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index dcb3bff78..9506c67a2 100755 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb @@ -1,18 +1,10 @@ # -# Common code for AIX providers -# +# Common code for AIX providers. This class implements basic structure for +# AIX resources. # Author:: Hector Rivas Gandara # -# class Puppet::Provider::AixObject < Puppet::Provider - desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser" - - # Constants - # Loadable AIX I/A module for users and groups. By default we manage compat. - # TODO:: add a type parameter to change this - class << self - attr_accessor :ia_module - end + desc "Generic AIX resource provider" # The real provider must implement these functions. def lscmd(value=@resource[:name]) @@ -35,7 +27,6 @@ class Puppet::Provider::AixObject < Puppet::Provider raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}" end - # Valid attributes to be managed by this provider. # It is a list of hashes # :aix_attr AIX command attribute name @@ -45,6 +36,8 @@ class Puppet::Provider::AixObject < Puppet::Provider class << self attr_accessor :attribute_mapping end + + # Mapping from Puppet property to AIX attribute. def self.attribute_mapping_to if ! @attribute_mapping_to @attribute_mapping_to = {} @@ -57,6 +50,8 @@ class Puppet::Provider::AixObject < Puppet::Provider end @attribute_mapping_to end + + # Mapping from AIX attribute to Puppet property. def self.attribute_mapping_from if ! @attribute_mapping_from @attribute_mapping_from = {} @@ -73,7 +68,7 @@ class Puppet::Provider::AixObject < Puppet::Provider # This functions translates a key and value using the given mapping. # Mapping can be nil (no translation) or a hash with this format # {:key => new_key, :method => translate_method} - # It returns a list [key, value] + # It returns a list with the pair [key, value] def translate_attr(key, value, mapping) return [key, value] unless mapping return nil unless mapping[key] @@ -85,35 +80,12 @@ class Puppet::Provider::AixObject < Puppet::Provider end [mapping[key][:key], new_value] end - - # Gets the given command line argument for the given key, value and mapping. - def get_arg(key, value, mapping) - arg = nil - if ret = self.translate_attr(key, val, mapping) - new_key = ret[0] - new_val = ret[1] - - # Arrays are separated by commas - if new_val.is_a? Array - value = new_val.join(",") - else - value = new_val.to_s - end - - # Get the needed argument - if mapping[key][:to_arg] - arg = method(mapping[key][:to_arg]).call(new_key, value) - else - arg = (new_key.to_s + "=" + value ) - end - end - return arg - end - - # Reads and attribute. - # Here we implement the default behaviour. - # Subclasses must reimplement this. + # Loads an AIX attribute (key=value) and stores it in the given hash with + # puppet semantics. It translates the pair using the given mapping. + # + # This operation works with each property one by one, + # subclasses must reimplement this if more complex operations are needed def load_attribute(key, value, mapping, objectinfo) if mapping.nil? objectinfo[key] = value @@ -129,6 +101,15 @@ class Puppet::Provider::AixObject < Puppet::Provider return objectinfo end + # Gets the given command line argument for the given key and value, + # using the given mapping to translate key and value. + # All the objectinfo hash (@resource or @property_hash) is passed. + # + # This operation works with each property one by one, + # and default behaviour is return the arguments as key=value pairs. + # Subclasses must reimplement this if more complex operations/arguments + # are needed + # def get_arguments(key, value, mapping, objectinfo) if mapping.nil? new_key = key @@ -146,11 +127,7 @@ class Puppet::Provider::AixObject < Puppet::Provider end # convert it to string - if new_value.is_a? Array - new_value = new_value.join(",") - else - new_value = new_value.to_s - end + new_value = Array(new_value).join(',') if new_key return [ "#{new_key}=#{new_value}" ] @@ -159,9 +136,10 @@ class Puppet::Provider::AixObject < Puppet::Provider end end - # Convert the provider properties to AIX command arguments (string) - # This function will translate each value/key and generate the argument. - # By default, arguments are created as aix_key=aix_value + # Convert the provider properties (hash) to AIX command arguments + # (list of strings) + # This function will translate each value/key and generate the argument using + # the get_arguments function. def hash2args(hash, mapping=self.class.attribute_mapping_to) return "" unless hash arg_list = [] @@ -171,12 +149,14 @@ class Puppet::Provider::AixObject < Puppet::Provider arg_list end - # Parse AIX command attributes in a format of space separated of key=value - # pairs: "uid=100 groups=a,b,c" - # It returns and return provider hash. + # Parse AIX command attributes from the output of an AIX command, that + # which format is a list of space separated of key=value pairs: + # "uid=100 groups=a,b,c". + # It returns an hash. # # If a mapping is provided, the keys are translated as defined in the - # mapping hash. Only values included in mapping will be added + # mapping hash. And only values included in mapping will be added + # # NOTE: it will ignore the items not including '=' def parse_attr_list(str, mapping=self.class.attribute_mapping_from) properties = {} @@ -201,17 +181,15 @@ class Puppet::Provider::AixObject < Puppet::Provider properties.empty? ? nil : properties end - # Parse AIX colon separated list of attributes, using given list of keys - # to name the attributes. This function is useful to parse the output - # of commands like lsfs -c: + # Parse AIX command output in a colon separated list of attributes, + # This function is useful to parse the output of commands like lsfs -c: # #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct # /:/dev/hd4:jfs2::bootfs:557056:rw:yes:no # /home:/dev/hd1:jfs2:::2129920:rw:yes:no # /usr:/dev/hd2:jfs2::bootfs:9797632:rw:yes:no # # If a mapping is provided, the keys are translated as defined in the - # mapping hash. Only values included in mapping will be added - # NOTE: it will ignore the items not including '=' + # mapping hash. And only values included in mapping will be added def parse_colon_list(str, key_list, mapping=self.class.attribute_mapping_from) properties = {} attrs = [] @@ -227,8 +205,8 @@ class Puppet::Provider::AixObject < Puppet::Provider end - # Default parsing function for colon separated list or attributte list - # (key=val pairs). It will choose the method depending of the first line. + # Default parsing function for AIX commands. + # It will choose the method depending of the first line. # For the colon separated list it will: # 1. Get keys from first line. # 2. Parse next line. @@ -242,7 +220,9 @@ class Puppet::Provider::AixObject < Puppet::Provider end end - # Retrieve what we can about our object + # Retrieve all the information of an existing resource. + # It will execute 'lscmd' command and parse the output, using the mapping + # 'attribute_mapping_from' to translate the keys and values. def getinfo(refresh = false) if @objectinfo.nil? or refresh == true # Execute lsuser, split all attributes and add them to a dict. @@ -259,7 +239,8 @@ class Puppet::Provider::AixObject < Puppet::Provider @objectinfo end - # Retrieve what we can about our object, without translate the values. + # Like getinfo, but it will not use the mapping to translate the keys and values. + # It might be usefult to retrieve some raw information. def getosinfo(refresh = false) if @objectosinfo .nil? or refresh == true getinfo(refresh) @@ -269,7 +250,8 @@ class Puppet::Provider::AixObject < Puppet::Provider # List all elements of given type. It works for colon separated commands and - # list commands. + # list commands. + # It returns a list of names. def list_all names = [] begin @@ -301,6 +283,17 @@ class Puppet::Provider::AixObject < Puppet::Provider !!getinfo(true) # !! => converts to bool end + # Return all existing instances + # The method for returning a list of provider instances. Note that it returns + # providers, preferably with values already filled in, not resources. + def self.instances + objects=[] + self.list_all().each { |entry| + objects << new(:name => entry, :ensure => :present) + } + objects + end + #- **ensure** # The basic state that the object should be in. Valid values are # `present`, `absent`, `role`. @@ -313,17 +306,7 @@ class Puppet::Provider::AixObject < Puppet::Provider end end - # Return all existing instances - # The method for returning a list of provider instances. Note that it returns - # providers, preferably with values already filled in, not resources. - def self.instances - objects=[] - self.list_all().each { |entry| - objects << new(:name => entry, :ensure => :present) - } - objects - end - + # Create a new instance of the resource def create if exists? info "already exists" @@ -338,6 +321,7 @@ class Puppet::Provider::AixObject < Puppet::Provider end end + # Delete this instance of the resource def delete unless exists? info "already absent" @@ -353,8 +337,8 @@ class Puppet::Provider::AixObject < Puppet::Provider end #-------------------------------- - # Call this method when the object is initialized, - # create getter/setter methods for each property our resource type supports. + # Call this method when the object is initialized. + # It creates getter/setter methods for each property our resource type supports. # If setter or getter already defined it will not be overwritten def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.each do |prop| @@ -363,8 +347,7 @@ class Puppet::Provider::AixObject < Puppet::Provider define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=") end end - # - + # Define the needed getters and setters as soon as we know the resource type def self.resource_type=(resource_type) super @@ -404,8 +387,7 @@ class Puppet::Provider::AixObject < Puppet::Provider def initialize(resource) super @objectinfo = nil - # FIXME: Initiallize this properly. - self.class.ia_module="compat" + @objectosinfo = nil end end diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb index ceb889840..ecdef6070 100755 --- a/lib/puppet/provider/group/aix.rb +++ b/lib/puppet/provider/group/aix.rb @@ -9,7 +9,7 @@ require 'puppet/provider/aixobject' Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject do desc "Group management for AIX! Users are managed with mkgroup, rmgroup, lsgroup, chgroup" - # This will the the default provider for this platform + # This will the the default provider for this platform defaultfor :operatingsystem => :aix confine :operatingsystem => :aix @@ -35,17 +35,19 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Method to adapt puppet property to aix command value. Optional. - # :from Method to adapt aix command value to puppet property. Optional + # :from Method to adapt aix command value to puppet property. Optional self.attribute_mapping = [ #:name => :name, {:aix_attr => :id, :puppet_prop => :gid }, {:aix_attr => :users, :puppet_prop => :members, :from => :users_from_attr}, + {:aix_attr => :attributes, :puppet_prop => :attributes}, ] #-------------- - # Command lines + # Command definition + # Return the IA module arguments based on the resource param ia_load_module def get_ia_module_args if @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] @@ -54,7 +56,6 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d end end - def lscmd(value=@resource[:name]) [self.class.command(:list)] + self.get_ia_module_args + @@ -76,12 +77,8 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d extra_attrs + [@resource[:name]] end - def modifycmd(hash = property_hash, translate=true) - if translate - args = self.hash2args(hash) - else - args = self.hash2args(hash, nil) - end + def modifycmd(hash = property_hash) + args = self.hash2args(hash) return nil if args.empty? [self.class.command(:modify)] + @@ -96,6 +93,8 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d end + #-------------- + # Overwrite get_arguments to add the attributes arguments def get_arguments(key, value, mapping, objectinfo) # In the case of attributes, return a list of key=vlaue if key == :attributes @@ -106,11 +105,6 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d super(key, value, mapping, objectinfo) end - # Force convert users it a list. - def users_from_attr(value) - (value.is_a? String) ? value.split(',') : value - end - def filter_attributes(hash) # Return only not managed attributtes. hash.select { @@ -128,7 +122,7 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d def attributes=(attr_hash) #self.class.validate(param, value) param = :attributes - cmd = modifycmd({param => filter_attributes(attr_hash)}, false) + cmd = modifycmd({param => filter_attributes(attr_hash)}) if cmd begin execute(cmd) @@ -138,5 +132,10 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d end end + # Force convert users it a list. + def users_from_attr(value) + (value.is_a? String) ? value.split(',') : value + end + end diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index c231da816..032d2b536 100755 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb @@ -1,12 +1,11 @@ # -# User Puppet provider for AIX. It uses standar commands to manage users: +# User Puppet provider for AIX. It uses standard commands to manage users: # mkuser, rmuser, lsuser, chuser # # Notes: # - AIX users can have expiry date defined with minute granularity, # but puppet does not allow it. There is a ticket open for that (#5431) # - AIX maximum password age is in WEEKs, not days -# - I force the compat IA module. # # See http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development # for more information @@ -47,7 +46,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # value !~ /\s/ #end - # User attributes to ignore + # User attributes to ignore from AIX output. def self.attribute_ignore [] end @@ -59,23 +58,26 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do # :aix_attr AIX command attribute name # :puppet_prop Puppet propertie name # :to Method to adapt puppet property to aix command value. Optional. - # :from Method to adapt aix command value to puppet property. Optional + # :from Method to adapt aix command value to puppet property. Optional self.attribute_mapping = [ #:name => :name, - {:aix_attr => :pgrp, :puppet_prop => :gid, + {:aix_attr => :pgrp, :puppet_prop => :gid, :to => :gid_to_attr, :from => :gid_from_attr}, - {:aix_attr => :id, :puppet_prop => :uid}, - {:aix_attr => :groups, :puppet_prop => :groups}, - {:aix_attr => :home, :puppet_prop => :home}, - {:aix_attr => :shell, :puppet_prop => :shell}, - {:aix_attr => :expires, :puppet_prop => :expiry, + {:aix_attr => :id, :puppet_prop => :uid}, + {:aix_attr => :groups, :puppet_prop => :groups}, + {:aix_attr => :home, :puppet_prop => :home}, + {:aix_attr => :shell, :puppet_prop => :shell}, + {:aix_attr => :expires, :puppet_prop => :expiry, :to => :expiry_to_attr, :from => :expiry_from_attr}, - {:aix_attr => :maxage, :puppet_prop => :password_max_age}, - {:aix_attr => :minage, :puppet_prop => :password_min_age}, + {:aix_attr => :maxage, :puppet_prop => :password_max_age}, + {:aix_attr => :minage, :puppet_prop => :password_min_age}, + {:aix_attr => :attributes, :puppet_prop => :attributes}, ] #-------------- - # Command lines + # Command definition + + # Return the IA module arguments based on the resource param ia_load_module def get_ia_module_args if @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] @@ -84,6 +86,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do end end + # List groups and Ids def lsgroupscmd(value=@resource[:name]) [command(:lsgroup)] + self.get_ia_module_args + @@ -108,13 +111,10 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do extra_attrs + [@resource[:name]] end - # Get modify command - def modifycmd(hash = property_hash, translate=true) - if translate - args = self.hash2args(hash) - else - args = self.hash2args(hash, nil) - end + # Get modify command. Set translate=false if no mapping must be used. + # Needed for special properties like "attributes" + def modifycmd(hash = property_hash) + args = self.hash2args(hash) return nil if args.empty? [self.class.command(:modify)] + self.get_ia_module_args + @@ -141,6 +141,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do unless value and value.is_a? Hash return value.select { |k,v| true }.map { |pair| pair.join("=") } end + super(key, value, mapping, objectinfo) end @@ -179,6 +180,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do verify_group(value) end + # Get the group gid from its name def gid_from_attr(value) groupid_by_name(value) end @@ -281,7 +283,7 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do def attributes=(attr_hash) #self.class.validate(param, value) param = :attributes - cmd = modifycmd({param => filter_attributes(attr_hash)}, false) + cmd = modifycmd({param => filter_attributes(attr_hash)}) if cmd begin execute(cmd) -- cgit