automateit 0.71021 → 0.71030

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ # == AccountManager::PasswdExpect
2
+ #
3
+ # An AccountManager driver for the +passwd+ command found on Unix-like systems
4
+ # using the +expect+ program as a wrapper because the Ruby PTY implementation
5
+ # is unreliable.
6
+ class ::AutomateIt::AccountManager::PasswdExpect < ::AutomateIt::AccountManager::BaseDriver
7
+ depends_on :programs => %w(passwd expect)
8
+
9
+ def suitability(method, *args) # :nodoc:
10
+ # Level must be higher than PasswdPTY
11
+ return available? ? 9 : 0
12
+ end
13
+
14
+ # See AccountManager#passwd
15
+ def passwd(user, password, opts={})
16
+ _passwd_helper(user, password, opts) do |user, password, opts|
17
+ log.silence(Logger::WARN) do
18
+ interpreter.mktemp do |filename|
19
+ # Script derived from /usr/share/doc/expect/examples/autopasswd
20
+ interpreter.render(:text => <<-HERE, :to => filename)
21
+ set password "#{password}"
22
+ spawn passwd "#{user}"
23
+ expect "assword:"
24
+ sleep 0.1
25
+ send "$password\\r"
26
+ expect "assword:"
27
+ sleep 0.1
28
+ send "$password\\r"
29
+ expect eof
30
+ HERE
31
+
32
+ cmd = "expect #{filename} #{user} #{password}"
33
+ cmd << " > /dev/null" if opts[:quiet]
34
+ return(interpreter.sh cmd)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,67 @@
1
+ # == AccountManager::PasswdPTY
2
+ #
3
+ # An AccountManager driver for +passwd+ command found on Unix-like systems
4
+ # using the Ruby PTY implementation.
5
+ #
6
+ # *WARNING*: The Ruby PTY module is unreliable or unavailable on most
7
+ # platforms. It may hang indefinitely or report incorrect results. Every
8
+ # attempt has been made to work around these problems, but this is a low-level
9
+ # problem. You are strongly encouraged to install the +expect+ program, which
10
+ # works flawlessly. Once the +expect+ program is installed, passwords will be
11
+ # changed using the AccountManager::PasswdExpect driver, which works properly.
12
+ class ::AutomateIt::AccountManager::PasswdPTY < ::AutomateIt::AccountManager::BaseDriver
13
+ depends_on \
14
+ :programs => %w(passwd),
15
+ :libraries => %w(open3 expect pty)
16
+
17
+ def suitability(method, *args) # :nodoc:
18
+ # Level must be higher than Linux
19
+ return available? ? 3 : 0
20
+ end
21
+
22
+ # See AccountManager#passwd
23
+ def passwd(user, password, opts={})
24
+ log.info(PERROR+"Setting password with flaky Ruby PTY, which hangs or fails randomly. Install 'expect' (http://expect.nist.gov/) for reliable operation.")
25
+ _passwd_helper(user, password, opts) do
26
+ log.silence(Logger::WARN) do
27
+ interpreter.mktemp do |filename|
28
+ tries = 5
29
+ exitstatus = nil
30
+ begin
31
+ exitstruct = _passwd_raw(user, password, opts)
32
+ if exitstatus and not exitstruct.exitstatus.zero?
33
+ # FIXME AccountManager::Linux#passwd -- The `passwd` command randomly returns exit status 10 even when it succeeds. What does this mean and how to deal with it?! Temporary workaround is to throw an error and force a retry.
34
+ raise Errno::EPIPE.new("bad exitstatus %s" % exitstruct.exitstatus)
35
+ end
36
+ rescue Errno::EPIPE => e
37
+ # FIXME AccountManager::Linux#passwd -- EPIPE exception randomly thrown even when `passwd` succeeds. How to eliminate it? How to differentiate between this false error and a real one?
38
+ if tries <= 0
39
+ raise e
40
+ else
41
+ tries -= 1
42
+ retry
43
+ end
44
+ end
45
+ return exitstruct.exitstatus.zero?
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ def _passwd_raw(user, password, opts={})
53
+ quiet = (opts[:quiet] or not log.info?)
54
+
55
+ require 'open4'
56
+ return Open4::popen4("passwd %s 2>&1" % user) do |pid, sin, sout, serr|
57
+ $expect_verbose = ! quiet
58
+ 2.times do
59
+ sout.expect(/:/)
60
+ sleep 0.1 # Reduce chance of passwd thinking we're a robot :(
61
+ sin.puts password
62
+ puts "*" * 12 unless quiet
63
+ end
64
+ end
65
+ end
66
+ protected :_passwd_raw
67
+ end
@@ -0,0 +1,126 @@
1
+ # == AccountManager::POSIX
2
+ #
3
+ # A POSIX driver for the AccountManager.
4
+ class ::AutomateIt::AccountManager::POSIX < ::AutomateIt::AccountManager::Etc
5
+ depends_on :programs => %w(useradd usermod userdel groupadd groupmod groupdel)
6
+
7
+ def suitability(method, *args) # :nodoc:
8
+ # Level must be higher than Portable
9
+ return available? ? 2 : 0
10
+ end
11
+
12
+ #.......................................................................
13
+
14
+ # See AccountManager#add_user
15
+ def add_user(username, opts={})
16
+ return _add_user_helper(username, opts) do |username, opts|
17
+ cmd = "useradd"
18
+ cmd << " -c #{opts[:description] || username}"
19
+ cmd << " -d #{opts[:home]}" if opts[:home]
20
+ cmd << " -m" unless opts[:create_home] == false
21
+ cmd << " -G #{opts[:groups].join(',')}" if opts[:groups]
22
+ cmd << " -s #{opts[:shell] || "/bin/bash"}"
23
+ cmd << " -u #{opts[:uid]}" if opts[:uid]
24
+ cmd << " -g #{opts[:gid]}" if opts[:gid]
25
+ cmd << " #{username} < /dev/null"
26
+ cmd << " > /dev/null 2>&1 | grep -v blocks" if opts[:quiet]
27
+ interpreter.sh(cmd)
28
+ end
29
+ end
30
+
31
+ # TODO AccountManager#update_user -- implement
32
+ ### def update_user(username, opts={}) dispatch(username, opts) end
33
+
34
+ # See AccountManager#remove_user
35
+ def remove_user(username, opts={})
36
+ return _remove_user_helper(username, opts) do |username, opts|
37
+ # Options: -r -- remove the home directory and mail spool
38
+ cmd = "userdel"
39
+ cmd << " -r" unless opts[:remove_home] == false
40
+ cmd << " #{username}"
41
+ cmd << " > /dev/null" if opts[:quiet]
42
+ interpreter.sh(cmd)
43
+ end
44
+ end
45
+
46
+ # See AccountManager#add_groups_to_user
47
+ def add_groups_to_user(groups, username)
48
+ return _add_groups_to_user_helper(groups, username) do |missing, username|
49
+ targets = (groups_for_user(username) + missing).uniq
50
+
51
+ cmd = "usermod -G #{targets.join(',')} #{username}"
52
+ interpreter.sh(cmd)
53
+ end
54
+ end
55
+
56
+ # See AccountManager#remove_groups_from_user
57
+ def remove_groups_from_user(groups, username)
58
+ return _remove_groups_from_user_helper(groups, username) do |present, username|
59
+ matches = (groups_for_user(username) - [groups].flatten).uniq
60
+ cmd = "usermod -G #{matches.join(',')} #{username}"
61
+ interpreter.sh(cmd)
62
+ end
63
+ end
64
+
65
+ #.......................................................................
66
+
67
+ # See AccountManager#add_group
68
+ def add_group(groupname, opts={})
69
+ modified = false
70
+ unless has_group?(groupname)
71
+ modified = true
72
+
73
+ cmd = "groupadd"
74
+ cmd << " -g #{opts[:gid]}" if opts[:gid]
75
+ cmd << " #{groupname}"
76
+ interpreter.sh(cmd)
77
+
78
+ manager.invalidate(:groups)
79
+ end
80
+
81
+ if opts[:members]
82
+ modified = true
83
+ add_users_to_group(opts[:members], groupname)
84
+ end
85
+
86
+ return modified ? groups[groupname] : false
87
+ end
88
+
89
+ # TODO AccountManager#update_group -- implement
90
+ ### def update_group(groupname, opts={}) dispatch(groupname, opts) end
91
+
92
+ # See AccountManager#remove_group
93
+ def remove_group(groupname, opts={})
94
+ return false unless has_group?(groupname)
95
+ cmd = "groupdel #{groupname}"
96
+ interpreter.sh(cmd)
97
+
98
+ manager.invalidate(:groups)
99
+
100
+ return true
101
+ end
102
+
103
+ # See AccountManager#add_users_to_group
104
+ def add_users_to_group(users, groupname)
105
+ _add_users_to_group_helper(users, groupname) do |missing, groupname|
106
+ for username in missing
107
+ targets = (groups_for_user(username) + [groupname]).uniq
108
+ cmd = "usermod -G #{targets.join(',')} #{username}"
109
+ interpreter.sh(cmd)
110
+ end
111
+ end
112
+ end
113
+
114
+ # See AccountManager#remove_users_from_group
115
+ def remove_users_from_group(users, groupname)
116
+ _remove_users_from_group_helper(users, groupname) do |present, groupname|
117
+ u2g = users_to_groups
118
+ for username in present
119
+ user_groups = u2g[username]
120
+ # FIXME tries to include non-present groups, should use some variant of present
121
+ cmd = "usermod -G #{(user_groups.to_a-[groupname]).join(',')} #{username}"
122
+ interpreter.sh(cmd)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -51,6 +51,37 @@ module AutomateIt
51
51
  #
52
52
  # Hello world!
53
53
  #
54
+ # === Partitioning recipes
55
+ #
56
+ # You should split up your recipe code into different recipe files. This will
57
+ # improve the clarity of your code because each file can perform one task,
58
+ # and you'll also be able to easily execute a specific recipe.
59
+ #
60
+ # For example, you can use a task-specific <tt>recipes/postgresql.rb</tt> to
61
+ # set up the PostgreSQL database server, and a <tt>recipes/apache.rb</tt> to
62
+ # setup the Apache web server.
63
+ #
64
+ # === Running recipes from other recipes
65
+ #
66
+ # You can run one recipe from another. It's a good idea to create a top-level
67
+ # recipe that invokes the other recipes. This lets you run a single recipe
68
+ # that will in turn run all your other recipes in the correct order, such as
69
+ # setting up the database server before the web server so that websites.
70
+ #
71
+ # For example, consider a <tt>recipes/all.rb</tt> file with these lines:
72
+ #
73
+ # invoke 'postgresql' if tagged? :postgresql_server
74
+ # invoke 'nginx' if tagged? :nginx_server
75
+ # invoke 'apache' if tagged? :apache_server
76
+ #
77
+ # The first line above checks to see if the current host has the
78
+ # <tt>postgresql_server</tt> tag, and if it does, invokes the
79
+ # <tt>recipes/postgresql.rb</tt> recipe.
80
+ #
81
+ # You must run recipes from other recipes using AutomateIt's +invoke+ method
82
+ # and not Ruby's +require+, because the +invoke+ passes along the AutomateIt
83
+ # interpreter to the other recipes so they can continue execution.
84
+ #
54
85
  # === Using project libraries
55
86
  #
56
87
  # Any files ending with <tt>.rb</tt> that you put into the project's
@@ -133,8 +164,10 @@ module AutomateIt
133
164
  # aifield -p /tmp/hello_project greeting
134
165
  #
135
166
  # The <tt>-p</tt> specifies the project path (its an alias for
136
- # <tt>--project</tt>). More commands are available. You can see the
137
- # documentation and examples for these commands by running:
167
+ # <tt>--project</tt>).
168
+ #
169
+ # More commands are available. For documentation and examples run the
170
+ # following commands from the Unix shell:
138
171
  #
139
172
  # aifield --help
140
173
  # aitag --help
@@ -151,21 +184,21 @@ module AutomateIt
151
184
  # If you want to share a project between different hosts, you're responsible for distributing the files between them. This isn't a big deal though because these are just text files and your OS has dozens of excellent ways to distribute these.
152
185
  #
153
186
  # Common approaches to distribution:
154
- # * *Shared directory*: Your hosts mount a shared network directory (e.g., +nfs+ or +smb+) with your project. This is very easy if your hosts already have a shared directory, but can be a nuisance otherwise because it opens potential security holes and risks having you hosts hang if the master goes offline.
155
- # * *Client pull*: Your hosts download the latest copy of your project from a master repository using a remote copy tool (e.g., +rsync+) or a revision control system (e.g., +cvs+, +svn+, +hg+). This is a safe, simple and secure option.
156
- # * *Server push*: You have a master push out the project files to clients using a remote copy tool. This can be awkward and time-consuming because the server must go through a list of all hosts and copy files to them individually.
187
+ # * <b>Shared directory</b>: Your hosts mount a shared network directory (e.g., +nfs+ or +smb+) with your project. This is very easy if your hosts already have a shared directory, but can be a nuisance otherwise because it opens potential security holes and risks having you hosts hang if the master goes offline.
188
+ # * <b>Client pull</b>: Your hosts download the latest copy of your project from a master repository using a remote copy tool (e.g., +rsync+) or a revision control system (e.g., +cvs+, +svn+, +hg+). This is a safe, simple and secure option.
189
+ # * <b>Server push</b>: You have a master push out the project files to clients using a remote copy tool and then invoke +automateit+ on them via SSH. This can be awkward and time-consuming because the server must go through a list of all hosts and copy files to them individually.
157
190
  #
158
191
  # An example of a complete solution for distributing system configuration management files:
159
- # * Setup an +svn+ or +hg+ repository to store your project and create a special account for the hosts to use to checkout code.
160
- # * Write a wrapper script for running the recipes, for example, write a "/usr/bin/myautomateit" shell script like:
192
+ # 1. Setup an +svn+ or other version control repository to store your project and create a special account for the hosts to use to checkout code.
193
+ # 2. Write a wrapper script for running the recipes, for example, write a "/usr/bin/myautomateit" shell script like:
161
194
  #
162
195
  # #!/bin/sh
163
196
  # cd /var/local/myautomateit
164
197
  # svn update --quiet
165
198
  # automateit recipe/default.rb
166
- # * Run this wrapper once an hour using cron so that your systems are always up to date. AutomateIt only prints output when it makes a change, so cron will only email you when you commit new code to the repository and the hosts make changes.
167
- # * If you need to run a recipe on the machine right now, SSH into it and run the wrapper.
168
- # * If you need to run the script early on a bunch of machines and don't want to manually SSH into each one, you can leverage the +aitag+ (see <tt>aitag --help</tt>) to execute a Unix command across multiple systems. For example, you could use a Unix shell command like this to execute the wrapper on all hosts tagged with +apache_servers+:
199
+ # 3. Run this wrapper once an hour using cron so that your systems are always up to date. AutomateIt only prints output when it makes a change, so cron will only email you when you commit new code to the repository and the hosts make changes.
200
+ # 4. If you need to run a recipe on the machine right now, SSH into it and run the wrapper.
201
+ # 5. If you need to run the script early on a bunch of machines and don't want to manually SSH into each one, you can leverage the +aitag+ (see <tt>aitag --help</tt>) to execute a Unix command across multiple systems. For example, you could use a Unix shell command like this to execute the wrapper on all hosts tagged with +apache_servers+:
169
202
  #
170
203
  # for host in `aitag -p /var/local/myautomateit -w apache_server`; do
171
204
  # echo "# $host"
@@ -1,7 +1,7 @@
1
1
  # See AutomateIt::Interpreter for usage information.
2
2
  module AutomateIt # :nodoc:
3
3
  # AutomateIt version
4
- VERSION=Gem::Version.new("0.71021")
4
+ VERSION=Gem::Version.new("0.71030")
5
5
 
6
6
  # Instantiates a new Interpreter. See documentation for
7
7
  # Interpreter#setup.
@@ -7,22 +7,65 @@ elsif not INTERPRETER.superuser?
7
7
  elsif not INTERPRETER.account_manager.available?(:add_user)
8
8
  puts "NOTE: Can't find AccountManager for this platform, #{__FILE__}"
9
9
  else
10
- describe "AutomateIt::AccountManager" do
10
+ describe AutomateIt::AccountManager do
11
11
  before(:all) do
12
+ ### @independent = true
13
+ @independent = false
14
+
15
+ ### @a = AutomateIt.new(:verbosity => Logger::INFO)
12
16
  @a = AutomateIt.new(:verbosity => Logger::WARN)
13
17
  @m = @a.account_manager
18
+ @quiet = ! @a.log.info?
14
19
 
15
- @username = "automateit_testuser"
16
- @groupname = "automateit_testgroup"
20
+ # Some OSes are limited to 8 character names :(
21
+ @username = "aitestus"
22
+ @groupname = "aitestgr"
17
23
 
18
- raise "User named '#{@username}' found. If this isn't a real user, delete it so that the test can contineu. If this is a real user, change the spec to test with a user that shouldn't exist." if @m.users[@username]
19
- raise "Group named '#{@groupname}' found. If this isn't a real group, delete it so that the test can contineu. If this is a real group, change the spec to test with a group that shouldn't exist." if @m.groups[@groupname]
24
+ begin
25
+ raise "User named '#{@username}' found. If this isn't a real user, delete it so that the test can contineu. If this is a real user, change the spec to test with a user that shouldn't exist." if @m.users[@username]
26
+ raise "Group named '#{@groupname}' found. If this isn't a real group, delete it so that the test can contineu. If this is a real group, change the spec to test with a group that shouldn't exist." if @m.groups[@groupname]
27
+ rescue Exception => e
28
+ @fail = true
29
+ raise e
30
+ end
20
31
  end
21
32
 
22
33
  after(:all) do
23
- @m.remove_user(@username, :quiet => true)
24
- @m.remove_group(@username, :quiet => true)
25
- @m.remove_group(@groupname, :quiet => true)
34
+ unless @fail
35
+ @m.remove_user(@username, :quiet => true)
36
+ @m.remove_group(@username, :quiet => true)
37
+ @m.remove_group(@groupname, :quiet => true)
38
+ end
39
+ end
40
+
41
+ after(:each) do
42
+ unless @fail
43
+ if @independent
44
+ @m.remove_user(@username, :quiet => true)
45
+ @m.remove_group(@username, :quiet => true)
46
+ @m.remove_group(@groupname, :quiet => true)
47
+ end
48
+ end
49
+ end
50
+
51
+ def add_user(opts={})
52
+ # SunOS /home entries don't exist until you add them to auto_home, so
53
+ # work around this by using a directory we know can be used
54
+ home = INTERPRETER.tagged?(:sunos) ? "/var/tmp/#{@username}" : nil
55
+
56
+ defaults = { :passwd => "asdf", :shell => "/bin/false", :home => home,
57
+ :quiet => @quiet }
58
+
59
+ return @m.add_user(@username, defaults.merge(opts))
60
+ end
61
+
62
+ def add_group
63
+ return @m.add_group(@groupname)
64
+ end
65
+
66
+ def add_user_with_group
67
+ add_user
68
+ return @m.add_group(@groupname, :members => @username)
26
69
  end
27
70
 
28
71
  it "should find root user" do
@@ -36,27 +79,29 @@ else
36
79
  end
37
80
 
38
81
  it "should create a user" do
39
- entry = @m.add_user(@username, :passwd => "asdf", :shell => "/bin/false")
82
+ entry = add_user
40
83
 
41
84
  entry.should_not be_nil
42
85
  entry.name.should == @username
43
- # Leaves behind user for further tests
44
86
  end
45
87
 
46
88
  it "should have a user after one is created" do
47
- # Depends on user to be created by previous tests
89
+ add_user if @independent
90
+
48
91
  @m.has_user?(@username).should be_true
49
92
  end
50
93
 
51
94
  it "should query user data by name" do
52
- # Depends on user to be created by previous tests
95
+ add_user if @independent
96
+
53
97
  entry = @m.users[@username]
54
98
  entry.should_not be_nil
55
99
  entry.name.should == @username
56
100
  end
57
101
 
58
102
  it "should query user data by id" do
59
- # Depends on user to be created by previous tests
103
+ add_user if @independent
104
+
60
105
  uid = @m.users[@username].uid
61
106
 
62
107
  entry = @m.users[uid]
@@ -69,12 +114,14 @@ else
69
114
  end
70
115
 
71
116
  it "should create user group" do
72
- # Depends on user to be created by previous tests
117
+ add_user if @independent
118
+
73
119
  @m.groups[@username].should_not be_nil
74
120
  end
75
121
 
76
122
  it "should not re-add an existing user" do
77
- # Depends on user to be created by previous tests
123
+ add_user if @independent
124
+
78
125
  @m.add_user(@username).should be_false
79
126
  end
80
127
 
@@ -83,23 +130,28 @@ else
83
130
  end
84
131
 
85
132
  it "should add a group" do
86
- entry = @m.add_group(@groupname)
133
+ entry = add_group
134
+
87
135
  entry.should_not be_nil
88
136
  entry.name.should == @groupname
89
- # Leaves behind group for further tests
90
137
  end
91
138
 
92
139
  it "should not re-add a group" do
140
+ add_group if @independent
141
+
93
142
  @m.add_group(@groupname).should be_false
94
143
  end
95
144
 
96
145
  it "should query group data by name" do
146
+ add_group if @independent
147
+
97
148
  entry = @m.groups[@groupname]
98
149
  entry.should_not be_nil
99
150
  entry.name.should == @groupname
100
151
  end
101
152
 
102
153
  it "should query group data by id" do
154
+ add_group if @independent
103
155
  gid = @m.groups[@groupname].gid
104
156
 
105
157
  entry = @m.groups[gid]
@@ -112,7 +164,8 @@ else
112
164
  end
113
165
 
114
166
  it "should remove a group" do
115
- # Depends on group to be created by previous tests
167
+ add_group if @independent
168
+
116
169
  @m.remove_group(@groupname).should be_true
117
170
  end
118
171
 
@@ -124,41 +177,53 @@ else
124
177
  @m.users_for_group(@groupname).should == []
125
178
  end
126
179
 
127
- it "should add a group with members" do
128
- # Depends on user to be created by previous tests
129
- @m.add_group(@groupname, :members => @username)
130
- # Leaves behind group for further tests
131
- end
180
+ it "should add a group with members" do
181
+ add_user_with_group.should_not be_nil
182
+ end
132
183
 
133
- it "should query users in a group" do
134
- # Depends on group to be created by previous tests
135
- @m.users_for_group(@groupname).should == [@username]
136
- end
184
+ it "should query users in a group" do
185
+ add_user_with_group if @independent
137
186
 
138
- it "should query groups for a user" do
139
- # Depends on user to be created by previous tests
140
- # Depends on group to be created by previous tests
141
- @m.groups_for_user(@username).should include(@groupname)
142
- end
187
+ @m.users_for_group(@groupname).should == [@username]
188
+ end
143
189
 
144
- it "should remove users from a group" do
145
- # Depends on user to be created by previous tests
146
- # Depends on group to be created by previous tests
147
- @m.remove_users_from_group(@username, @groupname).should == [@username]
148
- end
190
+ it "should query groups for a user" do
191
+ add_user_with_group if @independent
192
+
193
+ @m.groups_for_user(@username).should include(@groupname)
194
+ end
195
+
196
+ it "should remove users from a group" do
197
+ add_user_with_group if @independent
198
+
199
+ @m.remove_users_from_group(@username, @groupname).should == [@username]
200
+ end
149
201
 
150
202
  it "should add groups to a user" do
151
- # Depends on user to be created by previous tests
203
+ add_user if @independent
204
+ add_group if @independent
205
+
152
206
  @m.add_groups_to_user(@groupname, @username).should == [@groupname]
207
+
208
+ end
209
+
210
+ it "should add users to group" do
211
+ @m.remove_groups_from_user(@groupname, @username) unless @independent
212
+ add_user if @independent
213
+ add_group if @independent
214
+
215
+ @m.add_users_to_group(@username, @groupname).should == [@username]
153
216
  end
154
217
 
155
218
  it "should remove groups from user" do
156
- # Depends on user to be created by previous tests
219
+ add_user_with_group if @independent
220
+
157
221
  @m.remove_groups_from_user(@groupname, @username).should == [@groupname]
158
222
  end
159
223
 
160
224
  it "should remove a group with members" do
161
- # Depends on group to be created by previous tests
225
+ add_group if @independent
226
+
162
227
  @m.remove_group(@groupname).should be_true
163
228
  end
164
229
 
@@ -189,7 +254,7 @@ else
189
254
  end
190
255
 
191
256
  it "should change password" do
192
- # Depends on user to be created by previous tests
257
+ add_user if @independent
193
258
  pass = "automateit"
194
259
 
195
260
  # TODO This isn't portable
@@ -201,18 +266,38 @@ else
201
266
  end
202
267
 
203
268
  before = extract_pwent(@username)
204
- @m.passwd(@username, pass).should be_true
269
+ @m.passwd(@username, pass, :quiet => @quiet).should be_true
205
270
  after = extract_pwent(@username)
206
271
  before.should_not eql(after)
207
272
  end
208
273
 
209
274
  it "should remove a user" do
210
- # Depends on user to be created by previous tests
275
+ add_user if @independent
211
276
  @m.remove_user(@username, :quiet => true).should be_true
212
277
  end
213
278
 
214
279
  it "should not remove a non-existent user" do
215
280
  @m.remove_user(@username).should be_false
216
281
  end
282
+
283
+ it "should add user with multiple groups" do
284
+ # Find the first few users
285
+ groups_expected = []
286
+ size = 3
287
+ Etc.group do |group|
288
+ groups_expected << group.name
289
+ break if groups_expected.size >= size
290
+ end
291
+
292
+ # Create a user
293
+ (user = add_user(:groups => groups_expected)).should_not be_true
294
+
295
+ # Make sure they have the right number of groups
296
+ groups_found = @m.groups_for_user(@username)
297
+ for group in groups_expected
298
+ ### puts "%s : %s" % [group, groups_found.include?(group)]
299
+ groups_found.should include(group)
300
+ end
301
+ end
217
302
  end
218
303
  end