> This patch has basic support for users and groups on Windows. > > * It for version 0.24.7. > * It does not include support for active directory. > * It does not work with the 0.24.8 beta.
Cool, can I just say this is really awesome! But you really should target master for new features like this. Also rather than a monolithic patch could you post individual patches, see http://reductivelabs.com/trac/puppet/wiki/DevelopmentLifecycle > The code is up at http://github.com/finalprefix/puppet/tree/0.24.7-win. > > I'd love any feedback you might have! How are you finding running it on windows, there are a bunch of path issues I know that need to be cleaned up to run puppet. If I get a chance this week I'll try and produce a combined tree cherry-picking your stuff against master. Further comments inline > diff --git a/lib/puppet.rb b/lib/puppet.rb > index 4b0091c..77dbdfe 100644 > --- a/lib/puppet.rb > +++ b/lib/puppet.rb > @@ -190,53 +190,57 @@ module Puppet > # Trap a couple of the main signals. This should probably be > handled > + begin > + [:INT, :TERM].each do |signal| > + trap(signal) do > + Puppet.notice "Caught #{signal}; shutting down" > + Puppet.debug "Signal caught here:" > + caller.each { |l| Puppet.debug l } > + Puppet.shutdown > + end > + end ... > + rescue ArgumentError => e > + raise if e.to_s.index('SIGHUP') == nil > end > end What's the issue you're fixing here, I'm assuming its due to SIGHUP not being defined on windows. There is probably a more explicit way of handling this, as it's such a long method it makes it hard to see the intent. > diff --git a/lib/puppet/provider/group/groupadd_win.rb b/lib/puppet/ > provider/group/groupadd_win.rb > new file mode 100644 > index 0000000..c1fa7f4 > --- /dev/null > +++ b/lib/puppet/provider/group/groupadd_win.rb > @@ -0,0 +1,30 @@ > +require 'puppet/util/windows_system' > + > +Puppet::Type.type(:group).provide :groupadd_win do > + desc "Group management for windows" > + > + defaultfor :operatingsystem => :windows > + > + has_features :manages_members > + > + def members > + Windows::Group.new(@resource[:name]).members > + end > + > + def members=(members) > + Windows::Group.new(@resource[:name]).set_members(members) > + end > + > + def create > + group = Windows::Group.create(@resource[:name]) > + group.set_members(@resource[:members]) > + end > + > + def exists? > + Windows::Group.exists?(@resource[:name]) > + end > + > + def delete > + Windows::Group.delete(@resource[:name]) > + end > +end Looks quite nice simple set of providers sitting on top of your utils class > diff --git a/lib/puppet/util/windows_system.rb b/lib/puppet/util/ > windows_system.rb > new file mode 100644 > index 0000000..83bc52e > --- /dev/null > +++ b/lib/puppet/util/windows_system.rb > @@ -0,0 +1,205 @@ > +if Facter.operatingsystem == "windows" > + require 'win32ole' > + require 'Win32API' > +end > + > +module ADSI > + def ADSI.connectable?(uri) > + begin > + adsi_obj = WIN32OLE.connect(uri) > + return adsi_obj != nil; > + rescue > + end > + > + return false > + end > +end > + > +module Windows > + class Resource > + def Resource.uri(resource_name) > + "#{Computer.resource_uri}/#{resource_name}" > + end > + end > + > + class User > + def initialize(username, native_adsi_obj = nil) > + @username = username > + @user = native_adsi_obj > + end > + > + def user > + @user = > WIN32OLE.connect(User.resource_uri(@username)) if @user == > nil > + return @user > + end > + > + def password_is?(password) > + fLOGON32_LOGON_NETWORK_CLEARTEXT = 8 > + fLOGON32_PROVIDER_DEFAULT = 0 > + > + logon_user = Win32API.new("advapi32", "LogonUser", > ['P', 'P', 'P', > 'L', 'L', 'P'], 'L') > + close_handle = Win32API.new("kernel32", > "CloseHandle", ['P'], 'V') > + > + token = ' ' * 4 > + if logon_user.call(@username, "", password, > fLOGON32_LOGON_NETWORK_CLEARTEXT, fLOGON32_PROVIDER_DEFAULT, token) == > 1 > + close_handle.call(token.unpack('L')[0]) > + return true > + end > + > + return false > + end > + > + def add_flag(flag_name, value) > + flag = 0 > + > + begin > + flag = user.Get(flag_name) > + rescue > + end > + > + user.Put(flag_name, flag | value) > + user.SetInfo > + end > + > + def password=(password) > + user.SetPassword(password) > + user.SetInfo > + > + fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 > + add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD) > + end > + > + def groups > + groups = [] > + user.Groups.each {|group| groups << group.name } > + return groups > + end > + > + def add_to_groups(group_names) > + group_names.each {|name| > Group.new(name).add_user(@username) } if > group_names.length > 0 > + end > + > + def remove_from_groups(group_names) > + group_names.each {|name| > Group.new(name).remove_user(@username) } > if group_names.length > 0 > + end > + > + def set_groups(names, minimal = true) > + return if names == nil || names.strip.length == 0 > + > + names = names.strip.split(',') > + current_groups = groups > + > + names.find_all {|name| !current_groups.include?(name) > }.tap {| > names_to_add| add_to_groups(names_to_add) } > + current_groups.find_all {|name| !names.include?(name) > }.tap {| > names_to_remove| remove_from_groups(names_to_remove) } if minimal == > false > + end > + > + def User.resource_uri(username) > + return "#{Resource.uri(username)},user" > + end > + > + def User.exists?(username) > + return ADSI::connectable?(User.resource_uri(username)) > + end > + > + def User.create(username, password) > + User.new(username, Computer.create("user", > username)).tap {|user| > user.password = password; yield user if block_given? } > + end > + > + def User.delete(username) > + Computer.delete("user", username) > + end > + end > + > + class Group > + def initialize(groupname, native_adsi_obj = nil) > + @groupname = groupname > + @group = native_adsi_obj > + end > + > + def resource_uri > + Group.resource_uri(@groupname) > + end > + > + def Group.resource_uri(name) > + "#{Resource.uri(name)},group" > + end > + > + def group > + @group = WIN32OLE.connect(resource_uri) if @group == > nil > + return @group > + end > + > + def add_user(username) > + group.Add(User.resource_uri(username)) > + group.SetInfo > + end > + > + def remove_user(username) > + group.Remove(User.resource_uri(username)) > + group.SetInfo > + end > + > + def add_member(name) > + group.Add(Resource.uri(name)) > + group.SetInfo > + end > + > + def remove_member(name) > + group.Remove(Resource.uri(name)) > + group.SetInfo > + end > + > + def members > + list = [] > + group.Members.each {|member| list << member.Name } > + list > + end > + > + def set_members(members) > + return nil if members == nil || members.length == 0 > + > + current_members = self.members > + > + members.inject([]) {|members_to_add, member| > current_members.include?(member) ? members_to_add : members_to_add << > member }.each {|member| add_member(member) } > + current_members.inject([]) {|members_to_remove, > member| > members.include?(member) ? members_to_remove : members_to_remove << > member }.each {|member| remove_member(member) } > + end > + > + def Group.create(name) > + Windows::Group.new(name, Computer.create("group", > name)).tap {| > group| yield group if block_given? } > + end > + > + def Group.exists?(name) > + return ADSI::connectable?(Group.resource_uri(name)) > + end > + > + def Group.delete(name) > + Computer.delete("group", name) > + end > + end > + > + class Computer > + def Computer.name > + name = " " * 128 > + size = "128" > + > Win32API.new('kernel32','GetComputerName',['P','P'],'I').call > (name,size) > + return name.unpack("A*") > + end > + > + def Computer.resource_uri > + computer_name = Computer.name > + return "WinNT://#{computer_name}" > + end > + > + def Computer.api > + return WIN32OLE.connect(Computer.resource_uri) > + end > + > + def Computer.create(resource_type, name) > + Computer.api.create(resource_type, name).SetInfo > + end > + > + def Computer.delete(resource_type, name) > + Computer.api.Delete(resource_type, name) > + end > + end > +end Looks good at initial pass, I'll need to setup a windows box again in order to test. > diff --git a/test/ral/providers/group_win.rb b/test/ral/providers/ OK we're moving to rspec for testing all new functionality should be tested using rspec rather than Test::Unit, although tests appreciated. If you need a hand with this I'm often on irc during UTC working hours (nick nasrat) > diff --git a/test/ral/providers/syslog.rb b/test/ral/providers/ > syslog.rb > new file mode 100644 > index 0000000..396b45c > --- /dev/null > +++ b/test/ral/providers/syslog.rb > @@ -0,0 +1,4 @@ > +# The purpose of this file is merely to fake the syslog module on > windows. > +# I placed it here since I was running the tests from here. > +# I'll probably leave the file here until I wrap the windows event > log in a syslogish module for windows. > +# I haven't thought about it much. I'm open to better ideas too. I have an actual fix for not forcing loading syslog on windows, so we can just force the log type in config for now. Again thanks Paul --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en -~----------~----~----~----~------~----~------~--~---
