Merge lp:~johnsca/charm-helpers/adduser-group into lp:charm-helpers

Proposed by Cory Johns
Status: Merged
Merged at revision: 492
Proposed branch: lp:~johnsca/charm-helpers/adduser-group
Merge into: lp:charm-helpers
Diff against target: 130 lines (+64/-4)
2 files modified
charmhelpers/core/host.py (+26/-2)
tests/core/test_host.py (+38/-2)
To merge this branch: bzr merge lp:~johnsca/charm-helpers/adduser-group
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+278649@code.launchpad.net

Description of the change

Add primary and secondary groups params to host.adduser

This was a change that the big data charms use but was never proposed upstream.

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :

Can we have better parameter names than 'group' and 'groups'? eg. primary_group and secondary_groups? The current naming will certainly cause confusion, particularly when the docstring contains no hints for the new parameters.

Some tests would be nice but this is a simple enough update.

492. By Cory Johns

Changed param names and added test

493. By Cory Johns

Add doc string and explicit -g default to avoid "group exists" error

Revision history for this message
Cory Johns (johnsca) wrote :

Thanks for the feedback. I added a test and docstring, changed the param names as suggested, and made it always include the default primary group to avoid errors like this: http://pastebin.ubuntu.com/13506551/

494. By Cory Johns

Improved default primary_group handling to avoid errors

Revision history for this message
Cory Johns (johnsca) wrote :

"Always include" wasn't quite right, either. Now, creating a user without specifying a primary group will always succeed, regardless of whether the user group already exists or not.

Revision history for this message
Stuart Bishop (stub) wrote :

All good. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/core/host.py'
2--- charmhelpers/core/host.py 2015-11-18 09:26:07 +0000
3+++ charmhelpers/core/host.py 2015-11-27 20:57:09 +0000
4@@ -146,8 +146,22 @@
5 return True
6
7
8-def adduser(username, password=None, shell='/bin/bash', system_user=False):
9- """Add a user to the system"""
10+def adduser(username, password=None, shell='/bin/bash', system_user=False,
11+ primary_group=None, secondary_groups=None):
12+ """
13+ Add a user to the system.
14+
15+ Will log but otherwise succeed if the user already exists.
16+
17+ :param str username: Username to create
18+ :param str password: Password for user; if ``None``, create a system user
19+ :param str shell: The default shell for the user
20+ :param bool system_user: Whether to create a login or system user
21+ :param str primary_group: Primary group for user; defaults to their username
22+ :param list secondary_groups: Optional list of additional groups
23+
24+ :returns: The password database entry struct, as returned by `pwd.getpwnam`
25+ """
26 try:
27 user_info = pwd.getpwnam(username)
28 log('user {0} already exists!'.format(username))
29@@ -162,6 +176,16 @@
30 '--shell', shell,
31 '--password', password,
32 ])
33+ if not primary_group:
34+ try:
35+ grp.getgrnam(username)
36+ primary_group = username # avoid "group exists" error
37+ except KeyError:
38+ pass
39+ if primary_group:
40+ cmd.extend(['-g', primary_group])
41+ if secondary_groups:
42+ cmd.extend(['-G', ','.join(secondary_groups)])
43 cmd.append(username)
44 subprocess.check_call(cmd)
45 user_info = pwd.getpwnam(username)
46
47=== modified file 'tests/core/test_host.py'
48--- tests/core/test_host.py 2015-11-17 20:21:31 +0000
49+++ tests/core/test_host.py 2015-11-27 20:57:09 +0000
50@@ -395,10 +395,11 @@
51 check_output.side_effect = exc
52 self.assertFalse(host.service_running('foo'))
53
54+ @patch('grp.getgrnam')
55 @patch('pwd.getpwnam')
56 @patch('subprocess.check_call')
57 @patch.object(host, 'log')
58- def test_adds_a_user_if_it_doesnt_exist(self, log, check_call, getpwnam):
59+ def test_adds_a_user_if_it_doesnt_exist(self, log, check_call, getpwnam, getgrnam):
60 username = 'johndoe'
61 password = 'eodnhoj'
62 shell = '/bin/bash'
63@@ -415,6 +416,7 @@
64 '--create-home',
65 '--shell', shell,
66 '--password', password,
67+ '-g', username,
68 username
69 ])
70 getpwnam.assert_called_with(username)
71@@ -436,10 +438,12 @@
72 self.assertFalse(check_call.called)
73 getpwnam.assert_called_with(username)
74
75+ @patch('grp.getgrnam')
76 @patch('pwd.getpwnam')
77 @patch('subprocess.check_call')
78 @patch.object(host, 'log')
79- def test_adds_a_user_with_different_shell(self, log, check_call, getpwnam):
80+ def test_adds_a_user_with_different_shell(self, log, check_call, getpwnam,
81+ getgrnam):
82 username = 'johndoe'
83 password = 'eodnhoj'
84 shell = '/bin/zsh'
85@@ -447,6 +451,7 @@
86 new_user_pwnam = 'some user pwnam'
87
88 getpwnam.side_effect = [existing_user_pwnam, new_user_pwnam]
89+ getgrnam.side_effect = KeyError('group not found')
90
91 result = host.adduser(username, password, shell=shell)
92
93@@ -460,6 +465,37 @@
94 ])
95 getpwnam.assert_called_with(username)
96
97+ @patch('grp.getgrnam')
98+ @patch('pwd.getpwnam')
99+ @patch('subprocess.check_call')
100+ @patch.object(host, 'log')
101+ def test_adduser_with_groups(self, log, check_call, getpwnam, getgrnam):
102+ username = 'johndoe'
103+ password = 'eodnhoj'
104+ shell = '/bin/bash'
105+ existing_user_pwnam = KeyError('user not found')
106+ new_user_pwnam = 'some user pwnam'
107+
108+ getpwnam.side_effect = [existing_user_pwnam, new_user_pwnam]
109+
110+ result = host.adduser(username, password,
111+ primary_group='foo', secondary_groups=[
112+ 'bar', 'qux',
113+ ])
114+
115+ self.assertEqual(result, new_user_pwnam)
116+ check_call.assert_called_with([
117+ 'useradd',
118+ '--create-home',
119+ '--shell', shell,
120+ '--password', password,
121+ '-g', 'foo',
122+ '-G', 'bar,qux',
123+ username
124+ ])
125+ getpwnam.assert_called_with(username)
126+ assert not getgrnam.called
127+
128 @patch('pwd.getpwnam')
129 @patch('subprocess.check_call')
130 @patch.object(host, 'log')

Subscribers

People subscribed via source and target branches