[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, experimental, updated. debian/2.6.8-1-844-g7ec39d5

Hector Rivas Gandara hrivas at caixagalicia.es
Tue May 10 08:03:30 UTC 2011


The following commit has been merged in the experimental branch:
commit 9032898eb32080bfae66a707d55976e9f984a512
Author: Hector Rivas Gandara <keymon at gmail.com>
Date:   Wed Dec 1 13:47:57 2010 +0100

    (#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.

diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb
new file mode 100755
index 0000000..98ac13b
--- /dev/null
+++ b/lib/puppet/provider/aixobject.rb
@@ -0,0 +1,282 @@
+#
+# Common code for AIX providers
+#
+# Author::    Hector Rivas Gandara <keymon at gmail.com>
+#
+#  
+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 0000000..bcb81e1
--- /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 <keymon at gmail.com>
+#  
+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.<parameter>= 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 0000000..ba95dbc
--- /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 <keymon at gmail.com>
+#
+# 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.<parameter>= 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

-- 
Puppet packaging for Debian



More information about the Pkg-puppet-devel mailing list