lib/puppet/provider/nameservice/directoryservice.rb in puppet-0.24.9 vs lib/puppet/provider/nameservice/directoryservice.rb in puppet-0.25.0
- old
+ new
@@ -28,20 +28,12 @@
# This is referenced in the get_ds_path class method
attr_writer :ds_path
attr_writer :macosx_version_major
end
-
- # JJM 2007-07-24: Not yet sure what initvars() does. I saw it in netinfo.rb
- # I do know, however, that it makes methods "work" =)
- # e.g. addcmd isn't available if this method call isn't present.
- #
- # JJM: Also, where this method is defined seems to impact the visibility
- # of methods. If I put initvars after commands, confine and defaultfor,
- # then getinfo is called from the parent class, not this class.
initvars()
-
+
commands :dscl => "/usr/bin/dscl"
commands :dseditgroup => "/usr/sbin/dseditgroup"
commands :sw_vers => "/usr/bin/sw_vers"
confine :operatingsystem => :darwin
defaultfor :operatingsystem => :darwin
@@ -78,81 +70,91 @@
:guid => 'GeneratedUID',
:en_address => 'ENetAddress',
:ip_address => 'IPAddress',
:members => 'GroupMembership',
}
-
+
@@password_hash_dir = "/var/db/shadow/hash"
-
+
def self.instances
# JJM Class method that provides an array of instance objects of this
# type.
# JJM: Properties are dependent on the Puppet::Type we're managine.
type_property_array = [:name] + @resource_type.validproperties
-
+
# Create a new instance of this Puppet::Type for each object present
# on the system.
list_all_present.collect do |name_string|
self.new(single_report(name_string, *type_property_array))
end
end
-
+
def self.get_ds_path
# JJM: 2007-07-24 This method dynamically returns the DS path we're concerned with.
# For example, if we're working with an user type, this will be /Users
# with a group type, this will be /Groups.
- # @ds_path is an attribute of the class itself.
+ # @ds_path is an attribute of the class itself.
if defined? @ds_path
return @ds_path
end
# JJM: "Users" or "Groups" etc ... (Based on the Puppet::Type)
# Remember this is a class method, so self.class is Class
# Also, @resource_type seems to be the reference to the
# Puppet::Type this class object is providing for.
return @resource_type.name.to_s.capitalize + "s"
end
-
+
def self.get_macosx_version_major
if defined? @macosx_version_major
return @macosx_version_major
end
begin
- product_version = Facter.value(:macosx_productversion)
- if product_version.nil?
- raise Puppet::Error, "Could not determine OS X version"
+ # Make sure we've loaded all of the facts
+ Facter.loadfacts
+
+ if Facter.value(:macosx_productversion_major)
+ product_version_major = Facter.value(:macosx_productversion_major)
+ else
+ # TODO: remove this code chunk once we require Facter 1.5.5 or higher.
+ Puppet.warning("DEPRECATION WARNING: Future versions of the directoryservice provider will require Facter 1.5.5 or newer.")
+ product_version = Facter.value(:macosx_productversion)
+ if product_version.nil?
+ fail("Could not determine OS X version from Facter")
+ end
+ product_version_major = product_version.scan(/(\d+)\.(\d+)./).join(".")
end
- product_version_major = product_version.scan(/(\d+)\.(\d+)./).join(".")
if %w{10.0 10.1 10.2 10.3}.include?(product_version_major)
- raise Puppet::Error, "%s is not supported by the directoryservice provider" % product_version_major
+ fail("%s is not supported by the directoryservice provider" % product_version_major)
end
@macosx_version_major = product_version_major
return @macosx_version_major
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not determine OS X version: %s" % detail
+ fail("Could not determine OS X version: %s" % detail)
end
end
+
def self.list_all_present
# JJM: List all objects of this Puppet::Type already present on the system.
begin
dscl_output = execute(get_exec_preamble("-list"))
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not get %s list from DirectoryService" % [ @resource_type.name.to_s ]
+ fail("Could not get %s list from DirectoryService" % [ @resource_type.name.to_s ])
end
return dscl_output.split("\n")
end
-
+
def self.parse_dscl_url_data(dscl_output)
# we need to construct a Hash from the dscl -url output to match
# that returned by the dscl -plist output for 10.5+ clients.
#
# Nasty assumptions:
# a) no values *end* in a colon ':', only keys
# b) if a line ends in a colon and the next line does start with
# a space, then the second line is a value of the first.
# c) (implied by (b)) keys don't start with spaces.
-
+
dscl_plist = {}
dscl_output.split("\n").inject([]) do |array, line|
if line =~ /^\s+/ # it's a value
array[-1] << line # add the value to the previous key
else
@@ -178,25 +180,25 @@
dscl_plist[key] = [value]
end
end
return dscl_plist
end
-
+
def self.parse_dscl_plist_data(dscl_output)
return Plist.parse_xml(dscl_output)
end
-
+
def self.generate_attribute_hash(input_hash, *type_properties)
attribute_hash = {}
input_hash.keys().each do |key|
ds_attribute = key.sub("dsAttrTypeStandard:", "")
next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute])
ds_value = input_hash[key]
case @@ds_to_ns_attribute_map[ds_attribute]
- when :members:
+ when :members
ds_value = ds_value # only members uses arrays so far
- when :gid, :uid:
+ when :gid, :uid
# OS X stores objects like uid/gid as strings.
# Try casting to an integer for these cases to be
# consistent with the other providers and the group type
# validation
begin
@@ -206,55 +208,55 @@
end
else ds_value = ds_value[0]
end
attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
end
-
+
# NBK: need to read the existing password here as it's not actually
# stored in the user record. It is stored at a path that involves the
- # UUID of the user record for non-Mobile local acccounts.
+ # UUID of the user record for non-Mobile local acccounts.
# Mobile Accounts are out of scope for this provider for now
if @resource_type.validproperties.include?(:password)
attribute_hash[:password] = self.get_password(attribute_hash[:guid])
end
return attribute_hash
end
-
+
def self.single_report(resource_name, *type_properties)
# JJM 2007-07-24:
# Given a the name of an object and a list of properties of that
# object, return all property values in a hash.
- #
+ #
# This class method returns nil if the object doesn't exist
# Otherwise, it returns a hash of the object properties.
-
+
all_present_str_array = list_all_present()
-
+
# NBK: shortcut the process if the resource is missing
return nil unless all_present_str_array.include? resource_name
-
+
dscl_vector = get_exec_preamble("-read", resource_name)
begin
dscl_output = execute(dscl_vector)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not get report. command execution failed."
+ fail("Could not get report. command execution failed.")
end
-
+
# Two code paths is ugly, but until we can drop 10.4 support we don't
# have a lot of choice. Ultimately this should all be done using Ruby
# to access the DirectoryService APIs directly, but that's simply not
# feasible for a while yet.
case self.get_macosx_version_major
when "10.4"
dscl_plist = self.parse_dscl_url_data(dscl_output)
when "10.5", "10.6"
dscl_plist = self.parse_dscl_plist_data(dscl_output)
end
-
+
return self.generate_attribute_hash(dscl_plist, *type_properties)
end
-
+
def self.get_exec_preamble(ds_action, resource_name = nil)
# JJM 2007-07-24
# DSCL commands are often repetitive and contain the same positional
# arguments over and over. See http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix/additionalfeatures/chapter_10_section_9.html
# for an example of what I mean.
@@ -283,62 +285,61 @@
end
# JJM: This returns most of the preamble of the command.
# e.g. 'dscl / -create /Users/mccune'
return command_vector
end
-
+
def self.set_password(resource_name, guid, password_hash)
password_hash_file = "#{@@password_hash_dir}/#{guid}"
begin
File.open(password_hash_file, 'w') { |f| f.write(password_hash)}
rescue Errno::EACCES => detail
- raise Puppet::Error, "Could not write to password hash file: #{detail}"
+ fail("Could not write to password hash file: #{detail}")
end
-
+
# NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of
# ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it
# will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if
# missing. Thus we make sure we only set ;ShadowHash; if it is missing, and
# we can do this with the merge command. This allows people to continue to
# use other custom AuthenticationAuthority attributes without stomping on them.
#
# There is a potential problem here in that we're only doing this when setting
- # the password, and the attribute could get modified at other times while the
+ # the password, and the attribute could get modified at other times while the
# hash doesn't change and so this doesn't get called at all... but
# without switching all the other attributes to merge instead of create I can't
# see a simple enough solution for this that doesn't modify the user record
# every single time. This should be a rather rare edge case. (famous last words)
-
+
dscl_vector = self.get_exec_preamble("-merge", resource_name)
dscl_vector << "AuthenticationAuthority" << ";ShadowHash;"
begin
dscl_output = execute(dscl_vector)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not set AuthenticationAuthority."
+ fail("Could not set AuthenticationAuthority.")
end
end
-
+
def self.get_password(guid)
password_hash = nil
password_hash_file = "#{@@password_hash_dir}/#{guid}"
if File.exists?(password_hash_file) and File.file?(password_hash_file)
if not File.readable?(password_hash_file)
- raise Puppet::Error("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
+ fail("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
end
f = File.new(password_hash_file)
password_hash = f.read
f.close
end
password_hash
end
def ensure=(ensure_value)
super
- # JJM: Modeled after nameservice/netinfo.rb, we need to
- # loop over all valid properties for the type we're managing
- # and call the method which sets that property value
- # Like netinfo, dscl can't create everything at once, afaik.
+ # We need to loop over all valid properties for the type we're
+ # managing and call the method which sets that property value
+ # dscl can't create everything at once unfortunately.
if ensure_value == :present
@resource.class.validproperties.each do |name|
next if name == :ensure
# LAK: We use property.sync here rather than directly calling
# the settor method because the properties might do some kind
@@ -350,13 +351,13 @@
self.send(name.to_s + "=", value)
else
next
end
end
- end
+ end
end
-
+
def password=(passphrase)
exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name)
exec_arg_vector << @@ns_to_ds_attribute_map[:guid]
begin
guid_output = execute(exec_arg_vector)
@@ -365,29 +366,29 @@
# according to the schema, in practice user accounts cannot have multiple UUIDs
# otherwise Bad Things Happen, so we just deal with the first value.
guid = guid_plist["dsAttrTypeStandard:#{@@ns_to_ds_attribute_map[:guid]}"][0]
self.class.set_password(@resource.name, guid, passphrase)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
+ fail("Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail])
end
end
-
+
# NBK: we override @parent.set as we need to execute a series of commands
# to deal with array values, rather than the single command nameservice.rb
# expects to be returned by modifycmd. Thus we don't bother defining modifycmd.
-
+
def set(param, value)
self.class.validate(param, value)
current_members = @property_value_cache_hash[:members]
if param == :members
# If we are meant to be authoritative for the group membership
# then remove all existing members who haven't been specified
# in the manifest.
if @resource[:auth_membership] and not current_members.nil?
remove_unwanted_members(current_members, value)
end
-
+
# if they're not a member, make them one.
add_members(current_members, value)
else
exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
# JJM: The following line just maps the NS name to the DS name
@@ -396,45 +397,45 @@
# JJM: The following line sends the actual value to set the property to
exec_arg_vector << value.to_s
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
+ fail("Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail])
end
end
end
-
+
# NBK: we override @parent.create as we need to execute a series of commands
# to create objects with dscl, rather than the single command nameservice.rb
# expects to be returned by addcmd. Thus we don't bother defining addcmd.
def create
if exists?
info "already exists"
return nil
end
-
+
# NBK: First we create the object with a known guid so we can set the contents
# of the password hash if required
# Shelling out sucks, but for a single use case it doesn't seem worth
# requiring people install a UUID library that doesn't come with the system.
# This should be revisited if Puppet starts managing UUIDs for other platform
# user records.
guid = %x{/usr/bin/uuidgen}.chomp
-
+
exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
exec_arg_vector << @@ns_to_ds_attribute_map[:guid] << guid
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not set GeneratedUID for %s %s: %s" %
- [@resource.class.name, @resource.name, detail]
+ fail("Could not set GeneratedUID for %s %s: %s" %
+ [@resource.class.name, @resource.name, detail])
end
-
+
if value = @resource.should(:password) and value != ""
self.class.set_password(@resource[:name], guid, value)
end
-
+
# Now we create all the standard properties
Puppet::Type.type(@resource.class.name).validproperties.each do |property|
next if property == :ensure
if value = @resource.should(property) and value != ""
if property == :members
@@ -445,83 +446,78 @@
next if property == :password # skip setting the password here
exec_arg_vector << value.to_s
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not create %s %s: %s" %
- [@resource.class.name, @resource.name, detail]
+ fail("Could not create %s %s: %s" %
+ [@resource.class.name, @resource.name, detail])
end
end
end
end
end
-
+
def remove_unwanted_members(current_members, new_members)
current_members.each do |member|
if not new_members.include?(member)
cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-d", member, @resource[:name]]
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not remove %s from group: %s, %s" % [member, @resource.name, detail]
+ fail("Could not remove %s from group: %s, %s" % [member, @resource.name, detail])
end
end
end
end
-
+
def add_members(current_members, new_members)
new_members.each do |new_member|
if current_members.nil? or not current_members.include?(new_member)
cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-a", new_member, @resource[:name]]
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
- raise Puppet::Error, "Could not add %s to group: %s, %s" % [new_member, @resource.name, detail]
+ fail("Could not add %s to group: %s, %s" % [new_member, @resource.name, detail])
end
end
end
end
-
+
def deletecmd
# JJM: Like addcmd, only called when deleting the object itself
# Note, this isn't used to delete properties of the object,
# at least that's how I understand it...
self.class.get_exec_preamble("-delete", @resource[:name])
end
-
+
def getinfo(refresh = false)
- # JJM 2007-07-24:
+ # JJM 2007-07-24:
# Override the getinfo method, which is also defined in nameservice.rb
- # This method returns and sets @infohash, which looks like:
- # (NetInfo provider, user type...)
- # @infohash = {:comment=>"Jeff McCune", :home=>"/Users/mccune",
- # :shell=>"/bin/zsh", :password=>"********", :uid=>502, :gid=>502,
- # :name=>"mccune"}
- #
+ # This method returns and sets @infohash
# I'm not re-factoring the name "getinfo" because this method will be
# most likely called by nameservice.rb, which I didn't write.
if refresh or (! defined?(@property_value_cache_hash) or ! @property_value_cache_hash)
# JJM 2007-07-24: OK, there's a bit of magic that's about to
# happen... Let's see how strong my grip has become... =)
- #
+ #
# self is a provider instance of some Puppet::Type, like
# Puppet::Type::User::ProviderDirectoryservice for the case of the
# user type and this provider.
- #
+ #
# self.class looks like "user provider directoryservice", if that
# helps you ...
- #
+ #
# self.class.resource_type is a reference to the Puppet::Type class,
# probably Puppet::Type::User or Puppet::Type::Group, etc...
- #
+ #
# self.class.resource_type.validproperties is a class method,
# returning an Array of the valid properties of that specific
# Puppet::Type.
- #
+ #
# So... something like [:comment, :home, :password, :shell, :uid,
# :groups, :ensure, :gid]
- #
+ #
# Ultimately, we add :name to the list, delete :ensure from the
# list, then report on the remaining list. Pretty whacky, ehh?
type_properties = [:name] + self.class.resource_type.validproperties
type_properties.delete(:ensure) if type_properties.include? :ensure
type_properties << :guid # append GeneratedUID so we just get the report here
@@ -531,6 +527,6 @@
end
end
return @property_value_cache_hash
end
end
-end
\ No newline at end of file
+end