Class Puppet::Provider::NameService::DirectoryService
In: lib/puppet/provider/nameservice/directoryservice.rb
Parent: Puppet::Provider::NameService

Methods

Attributes

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

Public Class methods

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

    # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

Public Instance methods

[Source]

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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

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

[Source]

     # 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

[Validate]