| Class | Puppet::Provider::NameService::DirectoryService |
| In: |
lib/puppet/provider/nameservice/directoryservice.rb
|
| Parent: | Puppet::Provider::NameService |
| ds_path | [W] |
JJM: This allows us to pass information when calling
Puppet::Type.type
e.g. Puppet::Type.type(:user).provide :directoryservice, :ds_path => "Users"
This is referenced in the get_ds_path class method
|
# File lib/puppet/provider/nameservice/directoryservice.rb, line 96
96: def self.get_ds_path
97: # JJM: 2007-07-24 This method dynamically returns the DS path we're concerned with.
98: # For example, if we're working with an user type, this will be /Users
99: # with a group type, this will be /Groups.
100: # @ds_path is an attribute of the class itself.
101: if defined? @ds_path
102: return @ds_path
103: else
104: # JJM: "Users" or "Groups" etc ... (Based on the Puppet::Type)
105: # Remember this is a class method, so self.class is Class
106: # Also, @resource_type seems to be the reference to the
107: # Puppet::Type this class object is providing for.
108: return @resource_type.name.to_s.capitalize + "s"
109: end
110: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 178
178: def self.get_exec_preamble(ds_action, resource_name = nil)
179: # JJM 2007-07-24
180: # DSCL commands are often repetitive and contain the same positional
181: # arguments over and over. See http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix/additionalfeatures/chapter_10_section_9.html
182: # for an example of what I mean.
183: # This method spits out proper DSCL commands for us.
184: # We EXPECT name to be @resource[:name] when called from an instance object.
185:
186: # There are two ways to specify paths in 10.5. See man dscl.
187: command_vector = [ command(:dscl), "-plist", "." ]
188: # JJM: The actual action to perform. See "man dscl"
189: # Common actiosn: -create, -delete, -merge, -append, -passwd
190: command_vector << ds_action
191: # JJM: get_ds_path will spit back "Users" or "Groups",
192: # etc... Depending on the Puppet::Type of our self.
193: if resource_name
194: command_vector << "/%s/%s" % [ get_ds_path, resource_name ]
195: else
196: command_vector << "/%s" % [ get_ds_path ]
197: end
198: # JJM: This returns most of the preamble of the command.
199: # e.g. 'dscl / -create /Users/mccune'
200: return command_vector
201: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 234
234: def self.get_password(guid)
235: password_hash = nil
236: password_hash_file = "#{@@password_hash_dir}/#{guid}"
237: # TODO: sort out error conditions?
238: if File.exists?(password_hash_file)
239: if not File.readable?(password_hash_file)
240: raise Puppet::Error("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
241: end
242: f = File.new(password_hash_file)
243: password_hash = f.read
244: f.close
245: end
246: password_hash
247: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 83
83: def self.instances
84: # JJM Class method that provides an array of instance objects of this
85: # type.
86: # JJM: Properties are dependent on the Puppet::Type we're managine.
87: type_property_array = [:name] + @resource_type.validproperties
88:
89: # Create a new instance of this Puppet::Type for each object present
90: # on the system.
91: list_all_present.collect do |name_string|
92: self.new(single_report(name_string, *type_property_array))
93: end
94: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 112
112: def self.list_all_present
113: # JJM: List all objects of this Puppet::Type already present on the system.
114: begin
115: dscl_output = execute(get_exec_preamble("-list"))
116: rescue Puppet::ExecutionFailure => detail
117: raise Puppet::Error, "Could not get %s list from DirectoryService" % [ @resource_type.name.to_s ]
118: end
119: return dscl_output.split("\n")
120: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 203
203: def self.set_password(resource_name, guid, password_hash)
204: password_hash_file = "#{@@password_hash_dir}/#{guid}"
205: begin
206: File.open(password_hash_file, 'w') { |f| f.write(password_hash)}
207: rescue Errno::EACCES => detail
208: raise Puppet::Error, "Could not write to password hash file: #{detail}"
209: end
210:
211: # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of
212: # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it
213: # will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if
214: # missing. Thus we make sure we only set ;ShadowHash; if it is missing, and
215: # we can do this with the merge command. This allows people to continue to
216: # use other custom AuthenticationAuthority attributes without stomping on them.
217: #
218: # There is a potential problem here in that we're only doing this when setting
219: # the password, and the attribute could get modified at other times while the
220: # hash doesn't change and so this doesn't get called at all... but
221: # without switching all the other attributes to merge instead of create I can't
222: # see a simple enough solution for this that doesn't modify the user record
223: # every single time. This should be a rather rare edge case. (famous last words)
224:
225: dscl_vector = self.get_exec_preamble("-merge", resource_name)
226: dscl_vector << "AuthenticationAuthority" << ";ShadowHash;"
227: begin
228: dscl_output = execute(dscl_vector)
229: rescue Puppet::ExecutionFailure => detail
230: raise Puppet::Error, "Could not set AuthenticationAuthority."
231: end
232: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 122
122: def self.single_report(resource_name, *type_properties)
123: # JJM 2007-07-24:
124: # Given a the name of an object and a list of properties of that
125: # object, return all property values in a hash.
126: #
127: # This class method returns nil if the object doesn't exist
128: # Otherwise, it returns a hash of the object properties.
129:
130: all_present_str_array = list_all_present()
131:
132: # NBK: shortcut the process if the resource is missing
133: return nil unless all_present_str_array.include? resource_name
134:
135: dscl_vector = get_exec_preamble("-read", resource_name)
136: begin
137: dscl_output = execute(dscl_vector)
138: rescue Puppet::ExecutionFailure => detail
139: raise Puppet::Error, "Could not get report. command execution failed."
140: end
141:
142: # JJM: We need a new hash to return back to our caller.
143: attribute_hash = Hash.new
144:
145: dscl_plist = Plist.parse_xml(dscl_output)
146: dscl_plist.keys().each do |key|
147: ds_attribute = key.sub("dsAttrTypeStandard:", "")
148: next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute])
149: ds_value = dscl_plist[key]
150: case @@ds_to_ns_attribute_map[ds_attribute]
151: when :members:
152: ds_value = ds_value # only members uses arrays so far
153: when :gid, :uid:
154: # OS X stores objects like uid/gid as strings.
155: # Try casting to an integer for these cases to be
156: # consistent with the other providers and the group type
157: # validation
158: begin
159: ds_value = Integer(ds_value[0])
160: rescue ArgumentError
161: ds_value = ds_value[0]
162: end
163: else ds_value = ds_value[0]
164: end
165: attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
166: end
167:
168: # NBK: need to read the existing password here as it's not actually
169: # stored in the user record. It is stored at a path that involves the
170: # UUID of the user record for non-Mobile local acccounts.
171: # Mobile Accounts are out of scope for this provider for now
172: if @resource_type.validproperties.include?(:password)
173: attribute_hash[:password] = self.get_password(attribute_hash[:guid])
174: end
175: return attribute_hash
176: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 387
387: def add_members(current_members, new_members)
388: new_members.each do |user|
389: if current_members.nil? or not current_members.include?(user)
390: cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-a", user, @resource[:name]]
391: begin
392: execute(cmd)
393: rescue Puppet::ExecutionFailure => detail
394: raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
395: end
396: end
397: end
398: 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.
# File lib/puppet/provider/nameservice/directoryservice.rb, line 324
324: def create
325: if exists?
326: info "already exists"
327: # The object already exists
328: return nil
329: end
330:
331: # NBK: First we create the object with a known guid so we can set the contents
332: # of the password hash if required
333: # Shelling out sucks, but for a single use case it doesn't seem worth
334: # requiring people install a UUID library that doesn't come with the system.
335: # This should be revisited if Puppet starts managing UUIDs for other platform
336: # user records.
337: guid = %x{/usr/bin/uuidgen}.chomp
338:
339: exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
340: exec_arg_vector << @@ns_to_ds_attribute_map[:guid] << guid
341: begin
342: execute(exec_arg_vector)
343: rescue Puppet::ExecutionFailure => detail
344: raise Puppet::Error, "Could not set GeneratedUID for %s %s: %s" %
345: [@resource.class.name, @resource.name, detail]
346: end
347:
348: if value = @resource.should(:password) and value != ""
349: self.class.set_password(@resource[:name], guid, value)
350: end
351:
352: # Now we create all the standard properties
353: Puppet::Type.type(@resource.class.name).validproperties.each do |property|
354: next if property == :ensure
355: if value = @resource.should(property) and value != ""
356: if property == :members
357: add_members(nil, value)
358: else
359: exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
360: exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)]
361: next if property == :password # skip setting the password here
362: exec_arg_vector << value.to_s
363: begin
364: execute(exec_arg_vector)
365: rescue Puppet::ExecutionFailure => detail
366: raise Puppet::Error, "Could not create %s %s: %s" %
367: [@resource.class.name, @resource.name, detail]
368: end
369: end
370: end
371: end
372: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 400
400: def deletecmd
401: # JJM: Like addcmd, only called when deleting the object itself
402: # Note, this isn't used to delete properties of the object,
403: # at least that's how I understand it...
404: self.class.get_exec_preamble("-delete", @resource[:name])
405: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 249
249: def ensure=(ensure_value)
250: super
251: # JJM: Modeled after nameservice/netinfo.rb, we need to
252: # loop over all valid properties for the type we're managing
253: # and call the method which sets that property value
254: # Like netinfo, dscl can't create everything at once, afaik.
255: if ensure_value == :present
256: @resource.class.validproperties.each do |name|
257: next if name == :ensure
258: # LAK: We use property.sync here rather than directly calling
259: # the settor method because the properties might do some kind
260: # of conversion. In particular, the user gid property might
261: # have a string and need to convert it to a number
262: if @resource.should(name)
263: @resource.property(name).sync
264: elsif value = autogen(name)
265: self.send(name.to_s + "=", value)
266: else
267: next
268: end
269: end
270: end
271: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 407
407: def getinfo(refresh = false)
408: # JJM 2007-07-24:
409: # Override the getinfo method, which is also defined in nameservice.rb
410: # This method returns and sets @infohash, which looks like:
411: # (NetInfo provider, user type...)
412: # @infohash = {:comment=>"Jeff McCune", :home=>"/Users/mccune",
413: # :shell=>"/bin/zsh", :password=>"********", :uid=>502, :gid=>502,
414: # :name=>"mccune"}
415: #
416: # I'm not re-factoring the name "getinfo" because this method will be
417: # most likely called by nameservice.rb, which I didn't write.
418: if refresh or (! defined?(@property_value_cache_hash) or ! @property_value_cache_hash)
419: # JJM 2007-07-24: OK, there's a bit of magic that's about to
420: # happen... Let's see how strong my grip has become... =)
421: #
422: # self is a provider instance of some Puppet::Type, like
423: # Puppet::Type::User::ProviderDirectoryservice for the case of the
424: # user type and this provider.
425: #
426: # self.class looks like "user provider directoryservice", if that
427: # helps you ...
428: #
429: # self.class.resource_type is a reference to the Puppet::Type class,
430: # probably Puppet::Type::User or Puppet::Type::Group, etc...
431: #
432: # self.class.resource_type.validproperties is a class method,
433: # returning an Array of the valid properties of that specific
434: # Puppet::Type.
435: #
436: # So... something like [:comment, :home, :password, :shell, :uid,
437: # :groups, :ensure, :gid]
438: #
439: # Ultimately, we add :name to the list, delete :ensure from the
440: # list, then report on the remaining list. Pretty whacky, ehh?
441: type_properties = [:name] + self.class.resource_type.validproperties
442: type_properties.delete(:ensure) if type_properties.include? :ensure
443: type_properties << :guid # append GeneratedUID so we just get the report here
444: @property_value_cache_hash = self.class.single_report(@resource[:name], *type_properties)
445: [:uid, :gid].each do |param|
446: @property_value_cache_hash[param] = @property_value_cache_hash[param].to_i if @property_value_cache_hash and @property_value_cache_hash.include?(param)
447: end
448: end
449: return @property_value_cache_hash
450: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 273
273: def password=(passphrase)
274: exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name)
275: exec_arg_vector << @@ns_to_ds_attribute_map[:guid]
276: begin
277: guid_output = execute(exec_arg_vector)
278: guid_plist = Plist.parse_xml(guid_output)
279: # Although GeneratedUID like all DirectoryService values can be multi-valued
280: # according to the schema, in practice user accounts cannot have multiple UUIDs
281: # otherwise Bad Things Happen, so we just deal with the first value.
282: guid = guid_plist["dsAttrTypeStandard:#{@@ns_to_ds_attribute_map[:guid]}"][0]
283: self.class.set_password(@resource.name, guid, passphrase)
284: rescue Puppet::ExecutionFailure => detail
285: raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
286: end
287: end
# File lib/puppet/provider/nameservice/directoryservice.rb, line 374
374: def remove_unwanted_members(current_members, new_members)
375: current_members.each do |member|
376: if not value.include?(member)
377: cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-d", member, @resource[:name]]
378: begin
379: execute(cmd)
380: rescue Puppet::ExecutionFailure => detail
381: raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
382: end
383: end
384: end
385: 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.
# File lib/puppet/provider/nameservice/directoryservice.rb, line 293
293: def set(param, value)
294: self.class.validate(param, value)
295: current_members = @property_value_cache_hash[:members]
296: if param == :members
297: # If we are meant to be authoritative for the group membership
298: # then remove all existing members who haven't been specified
299: # in the manifest.
300: if @resource[:auth_membership] and not current_members.nil?
301: remove_unwanted_members(current_members, value)
302: end
303:
304: # if they're not a member, make them one.
305: add_members(current_members, value)
306: else
307: exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
308: # JJM: The following line just maps the NS name to the DS name
309: # e.g. { :uid => 'UniqueID' }
310: exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(param)]
311: # JJM: The following line sends the actual value to set the property to
312: exec_arg_vector << value.to_s
313: begin
314: execute(exec_arg_vector)
315: rescue Puppet::ExecutionFailure => detail
316: raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
317: end
318: end
319: end