salt/modules/groupadd.py
# -*- coding: utf-8 -*-
'''
Manage groups on Linux, OpenBSD and NetBSD
.. important::
If you feel that Salt should be using this module to manage groups on a
minion, and it is using a different module (or gives an error similar to
*'group.info' is not available*), see :ref:`here
<module-provider-override>`.
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import functools
import os
from salt.ext import six
import salt.utils.files
import salt.utils.stringutils
try:
import grp
except ImportError:
pass
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'group'
def __virtual__():
'''
Set the user module if the kernel is Linux or OpenBSD
'''
if __grains__.get('kernel') in ('Linux', 'OpenBSD', 'NetBSD'):
return __virtualname__
return (False, 'The groupadd execution module cannot be loaded: '
' only available on Linux, OpenBSD and NetBSD')
def add(name, gid=None, system=False, root=None):
'''
Add the specified group
name
Name of the new group
gid
Use GID for the new group
system
Create a system account
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.add foo 3456
'''
cmd = ['groupadd']
if gid:
cmd.append('-g {0}'.format(gid))
if system and __grains__['kernel'] != 'OpenBSD':
cmd.append('-r')
if root is not None:
cmd.extend(('-R', root))
cmd.append(name)
ret = __salt__['cmd.run_all'](cmd, python_shell=False)
return not ret['retcode']
def delete(name, root=None):
'''
Remove the named group
name
Name group to delete
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.delete foo
'''
cmd = ['groupdel']
if root is not None:
cmd.extend(('-R', root))
cmd.append(name)
ret = __salt__['cmd.run_all'](cmd, python_shell=False)
return not ret['retcode']
def info(name, root=None):
'''
Return information about a group
name
Name of the group
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.info foo
'''
if root is not None:
getgrnam = functools.partial(_getgrnam, root=root)
else:
getgrnam = functools.partial(grp.getgrnam)
try:
grinfo = getgrnam(name)
except KeyError:
return {}
else:
return _format_info(grinfo)
def _format_info(data):
'''
Return formatted information in a pretty way.
'''
return {'name': data.gr_name,
'passwd': data.gr_passwd,
'gid': data.gr_gid,
'members': data.gr_mem}
def getent(refresh=False, root=None):
'''
Return info on all groups
refresh
Force a refresh of group information
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.getent
'''
if 'group.getent' in __context__ and not refresh:
return __context__['group.getent']
ret = []
if root is not None:
getgrall = functools.partial(_getgrall, root=root)
else:
getgrall = functools.partial(grp.getgrall)
for grinfo in getgrall():
ret.append(_format_info(grinfo))
__context__['group.getent'] = ret
return ret
def _chattrib(name, key, value, param, root=None):
'''
Change an attribute for a named user
'''
pre_info = info(name, root=root)
if not pre_info:
return False
if value == pre_info[key]:
return True
cmd = ['groupmod']
if root is not None:
cmd.extend(('-R', root))
cmd.extend((param, value, name))
__salt__['cmd.run'](cmd, python_shell=False)
return info(name, root=root).get(key) == value
def chgid(name, gid, root=None):
'''
Change the gid for a named group
name
Name of the group to modify
gid
Change the group ID to GID
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.chgid foo 4376
'''
return _chattrib(name, 'gid', gid, '-g', root=root)
def adduser(name, username, root=None):
'''
Add a user in the group.
name
Name of the group to modify
username
Username to add to the group
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.adduser foo bar
Verifies if a valid username 'bar' as a member of an existing group 'foo',
if not then adds it.
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
on_suse_11 = __grains__.get('os_family') == 'Suse' and __grains__.get('osmajorrelease') == '11'
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ['gpasswd', '-a', username, name]
elif on_suse_11:
cmd = ['usermod', '-A', name, username]
else:
cmd = ['gpasswd', '--add', username, name]
if root is not None:
cmd.extend(('--root', root))
else:
cmd = ['usermod', '-G', name, username]
if root is not None:
cmd.extend(('-R', root))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
return not retcode
def deluser(name, username, root=None):
'''
Remove a user from the group.
name
Name of the group to modify
username
Username to delete from the group
root
Directory to chroot into
CLI Example:
.. code-block:: bash
salt '*' group.deluser foo bar
Removes a member user 'bar' from a group 'foo'. If group is not present
then returns True.
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
on_suse_11 = __grains__.get('os_family') == 'Suse' and __grains__.get('osmajorrelease') == '11'
grp_info = __salt__['group.info'](name)
try:
if username in grp_info['members']:
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ['gpasswd', '-d', username, name]
elif on_suse_11:
cmd = ['usermod', '-R', name, username]
else:
cmd = ['gpasswd', '--del', username, name]
if root is not None:
cmd.extend(('--root', root))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
elif __grains__['kernel'] == 'OpenBSD':
out = __salt__['cmd.run_stdout']('id -Gn {0}'.format(username),
python_shell=False)
cmd = ['usermod', '-S']
cmd.append(','.join([g for g in out.split() if g != six.text_type(name)]))
cmd.append('{0}'.format(username))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
else:
log.error('group.deluser is not yet supported on this platform')
return False
return not retcode
else:
return True
except Exception:
return True
def members(name, members_list, root=None):
'''
Replaces members of the group with a provided list.
name
Name of the group to modify
members_list
Username list to set into the group
root
Directory to chroot into
CLI Example:
salt '*' group.members foo 'user1,user2,user3,...'
Replaces a membership list for a local group 'foo'.
foo:x:1234:user1,user2,user3,...
'''
on_redhat_5 = __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '5'
on_suse_11 = __grains__.get('os_family') == 'Suse' and __grains__.get('osmajorrelease') == '11'
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ['gpasswd', '-M', members_list, name]
elif on_suse_11:
for old_member in __salt__['group.info'](name).get('members'):
__salt__['cmd.run']('groupmod -R {0} {1}'.format(old_member, name), python_shell=False)
cmd = ['groupmod', '-A', members_list, name]
else:
cmd = ['gpasswd', '--members', members_list, name]
if root is not None:
cmd.extend(('--root', root))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
elif __grains__['kernel'] == 'OpenBSD':
retcode = 1
grp_info = __salt__['group.info'](name)
if grp_info and name in grp_info['name']:
__salt__['cmd.run']('groupdel {0}'.format(name),
python_shell=False)
__salt__['cmd.run']('groupadd -g {0} {1}'.format(
grp_info['gid'], name), python_shell=False)
for user in members_list.split(","):
if user:
retcode = __salt__['cmd.retcode'](
['usermod', '-G', name, user],
python_shell=False)
if not retcode == 0:
break
# provided list is '': users previously deleted from group
else:
retcode = 0
else:
log.error('group.members is not yet supported on this platform')
return False
return not retcode
def _getgrnam(name, root=None):
'''
Alternative implementation for getgrnam, that use only /etc/group
'''
root = root or '/'
passwd = os.path.join(root, 'etc/group')
with salt.utils.files.fopen(passwd) as fp_:
for line in fp_:
line = salt.utils.stringutils.to_unicode(line)
comps = line.strip().split(':')
if len(comps) < 4:
log.debug('Ignoring group line: %s', line)
continue
if comps[0] == name:
# Generate a getpwnam compatible output
comps[2] = int(comps[2])
comps[3] = comps[3].split(',') if comps[3] else []
return grp.struct_group(comps)
raise KeyError('getgrnam(): name not found: {}'.format(name))
def _getgrall(root=None):
'''
Alternative implemetantion for getgrall, that use only /etc/group
'''
root = root or '/'
passwd = os.path.join(root, 'etc/group')
with salt.utils.files.fopen(passwd) as fp_:
for line in fp_:
line = salt.utils.stringutils.to_unicode(line)
comps = line.strip().split(':')
if len(comps) < 4:
log.debug('Ignoring group line: %s', line)
continue
# Generate a getgrall compatible output
comps[2] = int(comps[2])
comps[3] = comps[3].split(',') if comps[3] else []
yield grp.struct_group(comps)