diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py index 6208d2394ea..96d45173d75 100644 --- a/lib/ansible/modules/system/user.py +++ b/lib/ansible/modules/system/user.py @@ -1,22 +1,21 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# (c) 2012, Stephen Fromm +# Copyright: (c) 2012, Stephen Fromm # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type - ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['stableinterface'], 'supported_by': 'core'} - DOCUMENTATION = ''' --- module: user -author: "Stephen Fromm (@sfromm)" +author: +- Stephen Fromm (@sfromm) version_added: "0.2" short_description: Manage user accounts notes: @@ -29,67 +28,56 @@ description: - For Windows targets, use the M(win_user) module instead. options: name: - required: true - aliases: [ "user" ] description: - Name of the user to create, remove or modify. + required: true + aliases: [ user ] comment: - required: false description: - Optionally sets the description (aka I(GECOS)) of user account. uid: - required: false description: - Optionally sets the I(UID) of the user. non_unique: - required: false - default: "no" - choices: [ "yes", "no" ] description: - Optionally when used with the -u option, this option allows to change the user ID to a non-unique value. + type: bool + default: "no" version_added: "1.1" seuser: - required: false description: - Optionally sets the seuser type (user_u) on selinux enabled systems. version_added: "2.1" group: - required: false description: - Optionally sets the user's primary group (takes a group name). groups: - required: false description: - Puts the user in list of groups. When set to the empty string ('groups='), the user is removed from all groups except the primary group. - Before version 2.3, the only input format allowed was a 'comma separated string', now it should be able to accept YAML lists also. append: - required: false - default: "no" - choices: [ "yes", "no" ] description: - If C(yes), will only add groups, not set them to just the list in I(groups). + type: bool + default: "no" shell: - required: false description: - Optionally set the user's shell. - On Mac OS X, before version 2.5, the default shell for non-system users was /usr/bin/false. Since 2.5, the default shell for non-system users on Mac OS X is /bin/bash. home: - required: false description: - Optionally set the user's home directory. skeleton: - required: false description: - Optionally set a home skeleton directory. Requires create_home option! version_added: "2.0" password: - required: false description: - Optionally set the user's password to this crypted value. See the user example in the github examples directory for what this looks @@ -98,172 +86,157 @@ options: Note on Darwin system, this value has to be cleartext. Beware of security issues. state: - required: false - default: "present" - choices: [ present, absent ] description: - Whether the account should exist or not, taking action if the state is different from what is stated. + choices: [ absent, present ] + default: present create_home: description: - Unless set to C(no), a home directory will be made for the user when the account is created or if the home directory does not exist. - Changed from C(createhome) to C(create_home) in version 2.5. - default: yes type: bool + default: 'yes' aliases: ['createhome'] move_home: - required: false - default: "no" - choices: [ "yes", "no" ] description: - If set to C(yes) when used with C(home=), attempt to move the user's home directory to the specified directory if it isn't there already. - system: - required: false + type: bool default: "no" - choices: [ "yes", "no" ] + system: description: - When creating an account, setting this to C(yes) makes the user a system account. This setting cannot be changed on existing users. - force: - required: false + type: bool default: "no" - choices: [ "yes", "no" ] + force: description: - - When used with C(state=absent), behavior is as with - C(userdel --force). + - When used with C(state=absent), behavior is as with C(userdel --force). + type: bool + default: "no" login_class: - required: false description: - Optionally sets the user's login class for FreeBSD, OpenBSD and NetBSD systems. remove: - required: false - default: "no" - choices: [ "yes", "no" ] description: - - When used with C(state=absent), behavior is as with - C(userdel --remove). - generate_ssh_key: - required: false + - When used with C(state=absent), behavior is as with C(userdel --remove). + type: bool default: "no" - choices: [ "yes", "no" ] - version_added: "0.9" + generate_ssh_key: description: - Whether to generate a SSH key for the user in question. This will B(not) overwrite an existing SSH key. - ssh_key_bits: - required: false - default: default set by ssh-keygen + type: bool + default: "no" version_added: "0.9" + ssh_key_bits: description: - Optionally specify number of bits in SSH key to create. - ssh_key_type: - required: false - default: rsa + default: default set by ssh-keygen version_added: "0.9" + ssh_key_type: description: - Optionally specify the type of SSH key to generate. Available SSH key types will depend on implementation present on target host. - ssh_key_file: - required: false - default: .ssh/id_rsa + default: rsa version_added: "0.9" + ssh_key_file: description: - Optionally specify the SSH key filename. If this is a relative filename then it will be relative to the user's home directory. - ssh_key_comment: - required: false - default: ansible-generated on $HOSTNAME + default: .ssh/id_rsa version_added: "0.9" + ssh_key_comment: description: - Optionally define the comment for the SSH key. - ssh_key_passphrase: - required: false + default: ansible-generated on $HOSTNAME version_added: "0.9" + ssh_key_passphrase: description: - Set a passphrase for the SSH key. If no passphrase is provided, the SSH key will default to having no passphrase. + version_added: "0.9" update_password: - required: false - default: always - choices: ['always', 'on_create'] - version_added: "1.3" description: - C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users. + choices: [ always, on_create ] + default: always + version_added: "1.3" expires: - version_added: "1.9" - required: false - default: "None" description: - An expiry time for the user in epoch, it will be ignored on platforms that do not support this. Currently supported on Linux and FreeBSD. + version_added: "1.9" local: - version_added: "2.4" - required: false - default: "False" description: - Forces the use of "local" command alternatives on platforms that implement it. This is useful in environments that use centralized authentification when you want to manipulate the local users. I.E. it uses `luseradd` instead of `useradd`. - This requires that these commands exist on the targeted host, otherwise it will be a fatal error. + type: bool + default: 'no' + version_added: "2.4" ''' EXAMPLES = ''' -# Add the user 'johnd' with a specific uid and a primary group of 'admin' -- user: +- name: Add the user 'johnd' with a specific uid and a primary group of 'admin' + user: name: johnd - comment: "John Doe" + comment: John Doe uid: 1040 group: admin -# Add the user 'james' with a bash shell, appending the group 'admins' and 'developers' to the user's groups -- user: +- name: Add the user 'james' with a bash shell, appending the group 'admins' and 'developers' to the user's groups + user: name: james shell: /bin/bash groups: admins,developers append: yes -# Remove the user 'johnd' -- user: +- name: Remove the user 'johnd' + user: name: johnd state: absent remove: yes -# Create a 2048-bit SSH key for user jsmith in ~jsmith/.ssh/id_rsa -- user: +- name: Create a 2048-bit SSH key for user jsmith in ~jsmith/.ssh/id_rsa + user: name: jsmith generate_ssh_key: yes ssh_key_bits: 2048 ssh_key_file: .ssh/id_rsa -# added a consultant whose account you want to expire -- user: +- name: Added a consultant whose account you want to expire + user: name: james18 shell: /bin/zsh groups: developers expires: 1422403387 ''' -import os -import pwd import grp +import os import platform +import pwd +import shutil import socket import time -import shutil + from ansible.module_utils._text import to_native from ansible.module_utils.basic import load_platform_subclass, AnsibleModule from ansible.module_utils.pycompat24 import get_exception try: import spwd - HAVE_SPWD=True + HAVE_SPWD = True except: - HAVE_SPWD=False + HAVE_SPWD = False + class User(object): """ @@ -290,31 +263,31 @@ class User(object): return load_platform_subclass(User, args, kwargs) def __init__(self, module): - self.module = module - self.state = module.params['state'] - self.name = module.params['name'] - self.uid = module.params['uid'] - self.non_unique = module.params['non_unique'] - self.seuser = module.params['seuser'] - self.group = module.params['group'] - self.comment = module.params['comment'] - self.shell = module.params['shell'] - self.password = module.params['password'] - self.force = module.params['force'] - self.remove = module.params['remove'] + self.module = module + self.state = module.params['state'] + self.name = module.params['name'] + self.uid = module.params['uid'] + self.non_unique = module.params['non_unique'] + self.seuser = module.params['seuser'] + self.group = module.params['group'] + self.comment = module.params['comment'] + self.shell = module.params['shell'] + self.password = module.params['password'] + self.force = module.params['force'] + self.remove = module.params['remove'] self.create_home = module.params['create_home'] - self.move_home = module.params['move_home'] - self.skeleton = module.params['skeleton'] - self.system = module.params['system'] + self.move_home = module.params['move_home'] + self.skeleton = module.params['skeleton'] + self.system = module.params['system'] self.login_class = module.params['login_class'] - self.append = module.params['append'] - self.sshkeygen = module.params['generate_ssh_key'] - self.ssh_bits = module.params['ssh_key_bits'] - self.ssh_type = module.params['ssh_key_type'] + self.append = module.params['append'] + self.sshkeygen = module.params['generate_ssh_key'] + self.ssh_bits = module.params['ssh_key_bits'] + self.ssh_type = module.params['ssh_key_type'] self.ssh_comment = module.params['ssh_key_comment'] self.ssh_passphrase = module.params['ssh_key_passphrase'] self.update_password = module.params['update_password'] - self.home = module.params['home'] + self.home = module.params['home'] self.expires = None self.groups = None self.local = module.params['local'] @@ -327,18 +300,17 @@ class User(object): self.expires = time.gmtime(module.params['expires']) except Exception: e = get_exception() - module.fail_json(msg="Invalid expires time %s: %s" %(self.expires, str(e))) + module.fail_json(msg="Invalid expires time %s: %s" % (self.expires, e)) if module.params['ssh_key_file'] is not None: self.ssh_file = module.params['ssh_key_file'] else: self.ssh_file = os.path.join('.ssh', 'id_%s' % self.ssh_type) - def execute_command(self, cmd, use_unsafe_shell=False, data=None, obey_checkmode=True): if self.module.check_mode and obey_checkmode: self.module.debug('In check mode, would have run: "%s"' % cmd) - return (0, '','') + return (0, '', '') else: # cast all args to strings ansible-modules-core/issues/4397 cmd = [str(x) for x in cmd] @@ -438,7 +410,6 @@ class User(object): cmd.append(self.name) return self.execute_command(cmd) - def _check_usermod_append(self): # check if this version of usermod can append groups @@ -466,8 +437,6 @@ class User(object): return False - - def modify_user_usermod(self): if self.local: @@ -555,7 +524,7 @@ class User(object): cmd.append(self.name) return self.execute_command(cmd) - def group_exists(self,group): + def group_exists(self, group): try: # Try group as a gid first grp.getgrgid(int(group)) @@ -693,7 +662,7 @@ class User(object): ssh_key_file = self.get_ssh_key_path() if not os.path.exists(ssh_key_file): return (1, 'SSH Key file %s does not exist' % ssh_key_file, '') - cmd = [ self.module.get_bin_path('ssh-keygen', True) ] + cmd = [self.module.get_bin_path('ssh-keygen', True)] cmd.append('-l') cmd.append('-f') cmd.append(ssh_key_file) @@ -835,7 +804,7 @@ class FreeBsdUser(User): cmd.append(self.login_class) if self.expires: - days =( time.mktime(self.expires) - time.time() ) // 86400 + days = (time.mktime(self.expires) - time.time()) // 86400 cmd.append('-e') cmd.append(str(int(days))) @@ -933,7 +902,7 @@ class FreeBsdUser(User): cmd.append(','.join(new_groups)) if self.expires: - days = ( time.mktime(self.expires) - time.time() ) // 86400 + days = (time.mktime(self.expires) - time.time()) // 86400 cmd.append('-e') cmd.append(str(int(days))) @@ -957,7 +926,6 @@ class FreeBsdUser(User): return (rc, out, err) -# =========================================== class OpenBSDUser(User): """ @@ -1502,7 +1470,7 @@ class SunOS(User): return (rc, out, err) -# =========================================== + class DarwinUser(User): """ This is a Darwin Mac OS X User manipulation class. @@ -1535,11 +1503,11 @@ class DarwinUser(User): ] def _get_dscl(self): - return [ self.module.get_bin_path('dscl', True), self.dscl_directory ] + return [self.module.get_bin_path('dscl', True), self.dscl_directory] def _list_user_groups(self): cmd = self._get_dscl() - cmd += [ '-search', '/Groups', 'GroupMembership', self.name ] + cmd += ['-search', '/Groups', 'GroupMembership', self.name] (rc, out, err) = self.execute_command(cmd, obey_checkmode=False) groups = [] for line in out.splitlines(): @@ -1551,7 +1519,7 @@ class DarwinUser(User): def _get_user_property(self, property): '''Return user PROPERTY as given my dscl(1) read or None if not found.''' cmd = self._get_dscl() - cmd += [ '-read', '/Users/%s' % self.name, property ] + cmd += ['-read', '/Users/%s' % self.name, property] (rc, out, err) = self.execute_command(cmd, obey_checkmode=False) if rc != 0: return None @@ -1559,12 +1527,12 @@ class DarwinUser(User): # if property contains embedded spaces, the list will instead be # displayed one entry per line, starting on the line after the key. lines = out.splitlines() - #sys.stderr.write('*** |%s| %s -> %s\n' % (property, out, lines)) + # sys.stderr.write('*** |%s| %s -> %s\n' % (property, out, lines)) if len(lines) == 1: return lines[0].split(': ')[1] else: if len(lines) > 2: - return '\n'.join([ lines[1].strip() ] + lines[2:]) + return '\n'.join([lines[1].strip()] + lines[2:]) else: if len(lines) == 2: return lines[1].strip() @@ -1614,9 +1582,9 @@ class DarwinUser(User): # https://gist.github.com/nueh/8252572 cmd = self._get_dscl() if self.password: - cmd += [ '-passwd', '/Users/%s' % self.name, self.password] + cmd += ['-passwd', '/Users/%s' % self.name, self.password] else: - cmd += [ '-create', '/Users/%s' % self.name, 'Password', '*'] + cmd += ['-create', '/Users/%s' % self.name, 'Password', '*'] (rc, out, err) = self.execute_command(cmd) if rc != 0: self.module.fail_json(msg='Error when changing password', err=err, out=out, rc=rc) @@ -1640,7 +1608,7 @@ class DarwinUser(User): option = '-a' else: option = '-d' - cmd = [ 'dseditgroup', '-o', 'edit', option, self.name, '-t', 'user', group ] + cmd = ['dseditgroup', '-o', 'edit', option, self.name, '-t', 'user', group] (rc, out, err) = self.execute_command(cmd) if rc != 0: self.module.fail_json(msg='Cannot %s user "%s" to group "%s".' @@ -1687,7 +1655,7 @@ class DarwinUser(User): plist_file = '/Library/Preferences/com.apple.loginwindow.plist' # http://support.apple.com/kb/HT5017?viewlocale=en_US - cmd = [ 'defaults', 'read', plist_file, 'HiddenUsersList' ] + cmd = ['defaults', 'read', plist_file, 'HiddenUsersList'] (rc, out, err) = self.execute_command(cmd, obey_checkmode=False) # returned value is # ( @@ -1704,27 +1672,26 @@ class DarwinUser(User): hidden_users.append(x) if self.system: - if not self.name in hidden_users: - cmd = [ 'defaults', 'write', plist_file, - 'HiddenUsersList', '-array-add', self.name ] + if self.name not in hidden_users: + cmd = ['defaults', 'write', plist_file, 'HiddenUsersList', '-array-add', self.name] (rc, out, err) = self.execute_command(cmd) if rc != 0: - self.module.fail_json( msg='Cannot user "%s" to hidden user list.' % self.name, err=err, out=out, rc=rc) + self.module.fail_json(msg='Cannot user "%s" to hidden user list.' % self.name, err=err, out=out, rc=rc) return 0 else: if self.name in hidden_users: del(hidden_users[hidden_users.index(self.name)]) - cmd = [ 'defaults', 'write', plist_file, 'HiddenUsersList', '-array' ] + hidden_users + cmd = ['defaults', 'write', plist_file, 'HiddenUsersList', '-array'] + hidden_users (rc, out, err) = self.execute_command(cmd) if rc != 0: - self.module.fail_json( msg='Cannot remove user "%s" from hidden user list.' % self.name, err=err, out=out, rc=rc) + self.module.fail_json(msg='Cannot remove user "%s" from hidden user list.' % self.name, err=err, out=out, rc=rc) return 0 def user_exists(self): '''Check is SELF.NAME is a known user on the system.''' cmd = self._get_dscl() - cmd += [ '-list', '/Users/%s' % self.name] + cmd += ['-list', '/Users/%s' % self.name] (rc, out, err) = self.execute_command(cmd, obey_checkmode=False) return rc == 0 @@ -1733,11 +1700,11 @@ class DarwinUser(User): info = self.user_info() cmd = self._get_dscl() - cmd += [ '-delete', '/Users/%s' % self.name] + cmd += ['-delete', '/Users/%s' % self.name] (rc, out, err) = self.execute_command(cmd) if rc != 0: - self.module.fail_json( msg='Cannot delete user "%s".' % self.name, err=err, out=out, rc=rc) + self.module.fail_json(msg='Cannot delete user "%s".' % self.name, err=err, out=out, rc=rc) if self.force: if os.path.exists(info[5]): @@ -1748,11 +1715,10 @@ class DarwinUser(User): def create_user(self, command_name='dscl'): cmd = self._get_dscl() - cmd += [ '-create', '/Users/%s' % self.name] + cmd += ['-create', '/Users/%s' % self.name] (rc, err, out) = self.execute_command(cmd) if rc != 0: - self.module.fail_json( msg='Cannot create user "%s".' % self.name, err=err, out=out, rc=rc) - + self.module.fail_json(msg='Cannot create user "%s".' % self.name, err=err, out=out, rc=rc) self._make_group_numerical() if self.uid is None: @@ -1776,18 +1742,16 @@ class DarwinUser(User): if field[0] in self.__dict__ and self.__dict__[field[0]]: cmd = self._get_dscl() - cmd += [ '-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]] + cmd += ['-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]] (rc, _err, _out) = self.execute_command(cmd) if rc != 0: - self.module.fail_json( msg='Cannot add property "%s" to user "%s".' - % (field[0], self.name), err=err, out=out, rc=rc) + self.module.fail_json(msg='Cannot add property "%s" to user "%s".' % (field[0], self.name), err=err, out=out, rc=rc) out += _out err += _err if rc != 0: return (rc, _err, _out) - (rc, _err, _out) = self._change_user_password() out += _out err += _err @@ -1814,7 +1778,7 @@ class DarwinUser(User): current = self._get_user_property(field[1]) if current is None or current != self.__dict__[field[0]]: cmd = self._get_dscl() - cmd += [ '-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]] + cmd += ['-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]] (rc, _err, _out) = self.execute_command(cmd) if rc != 0: self.module.fail_json( @@ -1843,7 +1807,6 @@ class DarwinUser(User): return (changed, out, err) -# =========================================== class AIX(User): """ @@ -1990,11 +1953,10 @@ class AIX(User): (rc2, out2, err2) = (None, '', '') if rc is not None: - return (rc, out+out2, err+err2) + return (rc, out + out2, err + err2) else: - return (rc2, out+out2, err+err2) + return (rc2, out + out2, err + err2) -# =========================================== class HPUX(User): """ @@ -2115,7 +2077,6 @@ class HPUX(User): new_groups = groups | set(current_groups) cmd.append(','.join(new_groups)) - if self.comment is not None and info[4] != self.comment: cmd.append('-c') cmd.append(self.comment) @@ -2141,49 +2102,48 @@ class HPUX(User): cmd.append(self.name) return self.execute_command(cmd) -# =========================================== def main(): - ssh_defaults = { - 'bits': 0, - 'type': 'rsa', - 'passphrase': None, - 'comment': 'ansible-generated on %s' % socket.gethostname() - } + ssh_defaults = dict( + bits=0, + type='rsa', + passphrase=None, + comment='ansible-generated on %s' % socket.gethostname() + ) module = AnsibleModule( - argument_spec = dict( - state=dict(default='present', choices=['present', 'absent'], type='str'), - name=dict(required=True, aliases=['user'], type='str'), - uid=dict(default=None, type='str'), - non_unique=dict(default='no', type='bool'), - group=dict(default=None, type='str'), - groups=dict(default=None, type='list'), - comment=dict(default=None, type='str'), - home=dict(default=None, type='path'), - shell=dict(default=None, type='str'), - password=dict(default=None, type='str', no_log=True), - login_class=dict(default=None, type='str'), + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + name=dict(type='str', required=True, aliases=['user']), + uid=dict(type='str'), + non_unique=dict(type='bool', default=False), + group=dict(type='str'), + groups=dict(type='list'), + comment=dict(type='str'), + home=dict(type='path'), + shell=dict(type='str'), + password=dict(type='str', no_log=True), + login_class=dict(type='str'), # following options are specific to selinux - seuser=dict(default=None, type='str'), + seuser=dict(type='str'), # following options are specific to userdel - force=dict(default='no', type='bool'), - remove=dict(default='no', type='bool'), + force=dict(type='bool', default=False), + remove=dict(type='bool', default=False), # following options are specific to useradd - create_home=dict(default='yes', aliases=['createhome'], type='bool'), - skeleton=dict(default=None, type='str'), - system=dict(default='no', type='bool'), + create_home=dict(type='bool', default=True, aliases=['createhome']), + skeleton=dict(type='str'), + system=dict(type='bool', default=False), # following options are specific to usermod - move_home=dict(default='no', type='bool'), - append=dict(default='no', type='bool'), + move_home=dict(type='bool', default=False), + append=dict(type='bool', default=False), # following are specific to ssh key generation generate_ssh_key=dict(type='bool'), - ssh_key_bits=dict(default=ssh_defaults['bits'], type='int'), - ssh_key_type=dict(default=ssh_defaults['type'], type='str'), - ssh_key_file=dict(default=None, type='path'), - ssh_key_comment=dict(default=ssh_defaults['comment'], type='str'), - ssh_key_passphrase=dict(default=None, type='str', no_log=True), - update_password=dict(default='always',choices=['always','on_create'],type='str'), - expires=dict(default=None, type='float'), + ssh_key_bits=dict(type='int', default=ssh_defaults['bits']), + ssh_key_type=dict(type='str', default=ssh_defaults['type']), + ssh_key_file=dict(type='path'), + ssh_key_comment=dict(type='str', default=ssh_defaults['comment']), + ssh_key_passphrase=dict(type='str', no_log=True), + update_password=dict(type='str', default='always', choices=['always', 'on_create']), + expires=dict(type='float'), local=dict(type='bool'), ), supports_check_mode=True diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 5ea08683b46..47dd3516737 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -362,5 +362,4 @@ lib/ansible/modules/system/solaris_zone.py lib/ansible/modules/system/svc.py lib/ansible/modules/system/timezone.py lib/ansible/modules/system/ufw.py -lib/ansible/modules/system/user.py lib/ansible/playbook/base.py