mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
ab7e4ee578
* initial development of homectl module * botmeta * fix some linting * Update .github/BOTMETA.yml Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * use array form of run_command Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * added mofifying user record and cleaned up based on comments * added updating records/multiple changes regarding options, examples doc, return doc * add integration tests and more overall improvements * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * removed modify handle within present * adding more options and better checking of user records when updating * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/system/homectl.py Co-authored-by: Felix Fontein <felix@fontein.de> * Add code review changes - remove unsafe_shell with run_command. - use dict.pop() in user_metadata dict. - consistent quoting to single quotes. - change logic to determine check mode better - fix integration tests and added check_mode tests * Fix handling of mount opts When a user is created without mountopts homed will use nodev and nosuid by default, however the user record metadata will not contain these values. This commit takes extra care that correct value is being set to true or false. So if a user gives mountopts with just nodev we need to make sure the nosuid and noexec gets set to false, etc. If mountopts are same as currently in user record make sure nothing would be changed and outputs correctly. Also fixed some tests. * change fmethod modify_user to prepare_modify_user_command * Code review fixes and add existing user pw checking - Added methods to check existing users password is correct by comparing the hash stored in homed user record and the hash of given password - Updated integration tests for above case - Added aliases file so CI can run * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
650 lines
25 KiB
Python
650 lines
25 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2022, James Livulpi
|
|
# 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
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: homectl
|
|
author:
|
|
- "James Livulpi (@jameslivulpi)"
|
|
short_description: Manage user accounts with systemd-homed
|
|
version_added: 4.4.0
|
|
description:
|
|
- Manages a user's home directory managed by systemd-homed.
|
|
options:
|
|
name:
|
|
description:
|
|
- The user name to create, remove, or update.
|
|
required: true
|
|
aliases: [ 'user', 'username' ]
|
|
type: str
|
|
password:
|
|
description:
|
|
- Set the user's password to this.
|
|
- Homed requires this value to be in cleartext on user creation and updating a user.
|
|
- The module takes the password and generates a password hash in SHA-512 with 10000 rounds of salt generation using crypt.
|
|
- See U(https://systemd.io/USER_RECORD/).
|
|
- This is required for I(state=present). When an existing user is updated this is checked against the stored hash in homed.
|
|
type: str
|
|
state:
|
|
description:
|
|
- The operation to take on the user.
|
|
choices: [ 'absent', 'present' ]
|
|
default: present
|
|
type: str
|
|
storage:
|
|
description:
|
|
- Indicates the storage mechanism for the user's home directory.
|
|
- If the storage type is not specified, ``homed.conf(5)`` defines which default storage to use.
|
|
- Only used when a user is first created.
|
|
choices: [ 'classic', 'luks', 'directory', 'subvolume', 'fscrypt', 'cifs' ]
|
|
type: str
|
|
disksize:
|
|
description:
|
|
- The intended home directory disk space.
|
|
- Human readable value such as C(10G), C(10M), or C(10B).
|
|
type: str
|
|
resize:
|
|
description:
|
|
- When used with I(disksize) this will attempt to resize the home directory immediately.
|
|
default: false
|
|
type: bool
|
|
realname:
|
|
description:
|
|
- The user's real ('human') name.
|
|
- This can also be used to add a comment to maintain compatability with C(useradd).
|
|
aliases: [ 'comment' ]
|
|
type: str
|
|
realm:
|
|
description:
|
|
- The 'realm' a user is defined in.
|
|
type: str
|
|
email:
|
|
description:
|
|
- The email address of the user.
|
|
type: str
|
|
location:
|
|
description:
|
|
- A free-form location string describing the location of the user.
|
|
type: str
|
|
iconname:
|
|
description:
|
|
- The name of an icon picked by the user, for example for the purpose of an avatar.
|
|
- Should follow the semantics defined in the Icon Naming Specification.
|
|
- See U(https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html) for specifics.
|
|
type: str
|
|
homedir:
|
|
description:
|
|
- Path to use as home directory for the user.
|
|
- This is the directory the user's home directory is mounted to while the user is logged in.
|
|
- This is not where the user's data is actually stored, see I(imagepath) for that.
|
|
- Only used when a user is first created.
|
|
type: path
|
|
imagepath:
|
|
description:
|
|
- Path to place the user's home directory.
|
|
- See U(https://www.freedesktop.org/software/systemd/man/homectl.html#--image-path=PATH) for more information.
|
|
- Only used when a user is first created.
|
|
type: path
|
|
uid:
|
|
description:
|
|
- Sets the UID of the user.
|
|
- If using I(gid) homed requires the value to be the same.
|
|
- Only used when a user is first created.
|
|
type: int
|
|
gid:
|
|
description:
|
|
- Sets the gid of the user.
|
|
- If using I(uid) homed requires the value to be the same.
|
|
- Only used when a user is first created.
|
|
type: int
|
|
mountopts:
|
|
description:
|
|
- String separated by comma each indicating mount options for a users home directory.
|
|
- Valid options are C(nosuid), C(nodev) or C(noexec).
|
|
- Homed by default uses C(nodev) and C(nosuid) while C(noexec) is off.
|
|
type: str
|
|
umask:
|
|
description:
|
|
- Sets the umask for the user's login sessions
|
|
- Value from C(0000) to C(0777).
|
|
type: int
|
|
memberof:
|
|
description:
|
|
- String separated by comma each indicating a UNIX group this user shall be a member of.
|
|
- Groups the user should be a member of should be supplied as comma separated list.
|
|
aliases: [ 'groups' ]
|
|
type: str
|
|
skeleton:
|
|
description:
|
|
- The absolute path to the skeleton directory to populate a new home directory from.
|
|
- This is only used when a home directory is first created.
|
|
- If not specified homed by default uses C(/etc/skel).
|
|
aliases: [ 'skel' ]
|
|
type: path
|
|
shell:
|
|
description:
|
|
- Shell binary to use for terminal logins of given user.
|
|
- If not specified homed by default uses C(/bin/bash).
|
|
type: str
|
|
environment:
|
|
description:
|
|
- String separated by comma each containing an environment variable and its value to
|
|
set for the user's login session, in a format compatible with ``putenv()``.
|
|
- Any environment variable listed here is automatically set by pam_systemd for all
|
|
login sessions of the user.
|
|
aliases: [ 'setenv' ]
|
|
type: str
|
|
timezone:
|
|
description:
|
|
- Preferred timezone to use for the user.
|
|
- Should be a tzdata compatible location string such as C(America/New_York).
|
|
type: str
|
|
locked:
|
|
description:
|
|
- Whether the user account should be locked or not.
|
|
type: bool
|
|
language:
|
|
description:
|
|
- The preferred language/locale for the user.
|
|
- This should be in a format compatible with the C($LANG) environment variable.
|
|
type: str
|
|
passwordhint:
|
|
description:
|
|
- Password hint for the given user.
|
|
type: str
|
|
sshkeys:
|
|
description:
|
|
- String separated by comma each listing a SSH public key that is authorized to access the account.
|
|
- The keys should follow the same format as the lines in a traditional C(~/.ssh/authorized_key) file.
|
|
type: str
|
|
notbefore:
|
|
description:
|
|
- A time since the UNIX epoch before which the record should be considered invalid for the purpose of logging in.
|
|
type: int
|
|
notafter:
|
|
description:
|
|
- A time since the UNIX epoch after which the record should be considered invalid for the purpose of logging in.
|
|
type: int
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Add the user 'james'
|
|
community.general.homectl:
|
|
name: johnd
|
|
password: myreallysecurepassword1!
|
|
state: present
|
|
|
|
- name: Add the user 'alice' with a zsh shell, uid of 1000, and gid of 2000
|
|
community.general.homectl:
|
|
name: alice
|
|
password: myreallysecurepassword1!
|
|
state: present
|
|
shell: /bin/zsh
|
|
uid: 1000
|
|
gid: 1000
|
|
|
|
- name: Modify an existing user 'frank' to have 10G of diskspace and resize usage now
|
|
community.general.homectl:
|
|
name: frank
|
|
password: myreallysecurepassword1!
|
|
state: present
|
|
disksize: 10G
|
|
resize: yes
|
|
|
|
- name: Remove an existing user 'janet'
|
|
community.general.homectl:
|
|
name: janet
|
|
state: absent
|
|
'''
|
|
|
|
RETURN = '''
|
|
data:
|
|
description: A json dictionary returned from C(homectl inspect -j).
|
|
returned: success
|
|
type: dict
|
|
sample: {
|
|
"data": {
|
|
"binding": {
|
|
"e9ed2a5b0033427286b228e97c1e8343": {
|
|
"fileSystemType": "btrfs",
|
|
"fileSystemUuid": "7bd59491-2812-4642-a492-220c3f0c6c0b",
|
|
"gid": 60268,
|
|
"imagePath": "/home/james.home",
|
|
"luksCipher": "aes",
|
|
"luksCipherMode": "xts-plain64",
|
|
"luksUuid": "7f05825a-2c38-47b4-90e1-f21540a35a81",
|
|
"luksVolumeKeySize": 32,
|
|
"partitionUuid": "5a906126-d3c8-4234-b230-8f6e9b427b2f",
|
|
"storage": "luks",
|
|
"uid": 60268
|
|
}
|
|
},
|
|
"diskSize": 3221225472,
|
|
"disposition": "regular",
|
|
"lastChangeUSec": 1641941238208691,
|
|
"lastPasswordChangeUSec": 1641941238208691,
|
|
"privileged": {
|
|
"hashedPassword": [
|
|
"$6$ov9AKni.trf76inT$tTtfSyHgbPTdUsG0CvSSQZXGqFGdHKQ9Pb6e0BTZhDmlgrL/vA5BxrXduBi8u/PCBiYUffGLIkGhApjKMK3bV."
|
|
]
|
|
},
|
|
"signature": [
|
|
{
|
|
"data": "o6zVFbymcmk4YTVaY6KPQK23YCp+VkXdGEeniZeV1pzIbFzoaZBvVLPkNKMoPAQbodY5BYfBtuy41prNL78qAg==",
|
|
"key": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAbs7ELeiEYBxkUQhxZ+5NGyu6J7gTtZtZ5vmIw3jowcY=\n-----END PUBLIC KEY-----\n"
|
|
}
|
|
],
|
|
"status": {
|
|
"e9ed2a5b0033427286b228e97c1e8343": {
|
|
"diskCeiling": 21845405696,
|
|
"diskFloor": 268435456,
|
|
"diskSize": 3221225472,
|
|
"service": "io.systemd.Home",
|
|
"signedLocally": true,
|
|
"state": "inactive"
|
|
}
|
|
},
|
|
"userName": "james",
|
|
}
|
|
}
|
|
'''
|
|
|
|
import crypt
|
|
import json
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.basic import jsonify
|
|
from ansible.module_utils.common.text.formatters import human_to_bytes
|
|
|
|
|
|
class Homectl(object):
|
|
'''#TODO DOC STRINGS'''
|
|
|
|
def __init__(self, module):
|
|
self.module = module
|
|
self.state = module.params['state']
|
|
self.name = module.params['name']
|
|
self.password = module.params['password']
|
|
self.storage = module.params['storage']
|
|
self.disksize = module.params['disksize']
|
|
self.resize = module.params['resize']
|
|
self.realname = module.params['realname']
|
|
self.realm = module.params['realm']
|
|
self.email = module.params['email']
|
|
self.location = module.params['location']
|
|
self.iconname = module.params['iconname']
|
|
self.homedir = module.params['homedir']
|
|
self.imagepath = module.params['imagepath']
|
|
self.uid = module.params['uid']
|
|
self.gid = module.params['gid']
|
|
self.umask = module.params['umask']
|
|
self.memberof = module.params['memberof']
|
|
self.skeleton = module.params['skeleton']
|
|
self.shell = module.params['shell']
|
|
self.environment = module.params['environment']
|
|
self.timezone = module.params['timezone']
|
|
self.locked = module.params['locked']
|
|
self.passwordhint = module.params['passwordhint']
|
|
self.sshkeys = module.params['sshkeys']
|
|
self.language = module.params['language']
|
|
self.notbefore = module.params['notbefore']
|
|
self.notafter = module.params['notafter']
|
|
self.mountopts = module.params['mountopts']
|
|
|
|
self.result = {}
|
|
|
|
# Cannot run homectl commands if service is not active
|
|
def homed_service_active(self):
|
|
is_active = True
|
|
cmd = ['systemctl', 'show', 'systemd-homed.service', '-p', 'ActiveState']
|
|
rc, show_service_stdout, stderr = self.module.run_command(cmd)
|
|
if rc == 0:
|
|
state = show_service_stdout.rsplit('=')[1]
|
|
if state.strip() != 'active':
|
|
is_active = False
|
|
return is_active
|
|
|
|
def user_exists(self):
|
|
exists = False
|
|
valid_pw = False
|
|
# Get user properties if they exist in json
|
|
rc, stdout, stderr = self.get_user_metadata()
|
|
if rc == 0:
|
|
exists = True
|
|
# User exists now compare password given with current hashed password stored in the user metadata.
|
|
if self.state != 'absent': # Don't need checking on remove user
|
|
stored_pwhash = json.loads(stdout)['privileged']['hashedPassword'][0]
|
|
if self._check_password(stored_pwhash):
|
|
valid_pw = True
|
|
return exists, valid_pw
|
|
|
|
def create_user(self):
|
|
record = self.create_json_record(create=True)
|
|
cmd = [self.module.get_bin_path('homectl', True)]
|
|
cmd.append('create')
|
|
cmd.append('--identity=-') # Read the user record from standard input.
|
|
return(self.module.run_command(cmd, data=record))
|
|
|
|
def _hash_password(self, password):
|
|
method = crypt.METHOD_SHA512
|
|
salt = crypt.mksalt(method, rounds=10000)
|
|
pw_hash = crypt.crypt(password, salt)
|
|
return pw_hash
|
|
|
|
def _check_password(self, pwhash):
|
|
hash = crypt.crypt(self.password, pwhash)
|
|
return pwhash == hash
|
|
|
|
def remove_user(self):
|
|
cmd = [self.module.get_bin_path('homectl', True)]
|
|
cmd.append('remove')
|
|
cmd.append(self.name)
|
|
return self.module.run_command(cmd)
|
|
|
|
def prepare_modify_user_command(self):
|
|
record = self.create_json_record()
|
|
cmd = [self.module.get_bin_path('homectl', True)]
|
|
cmd.append('update')
|
|
cmd.append(self.name)
|
|
cmd.append('--identity=-') # Read the user record from standard input.
|
|
# Resize disksize now resize = true
|
|
# This is not valid in user record (json) and requires it to be passed on command.
|
|
if self.disksize and self.resize:
|
|
cmd.append('--and-resize')
|
|
cmd.append('true')
|
|
self.result['changed'] = True
|
|
return cmd, record
|
|
|
|
def get_user_metadata(self):
|
|
cmd = [self.module.get_bin_path('homectl', True)]
|
|
cmd.append('inspect')
|
|
cmd.append(self.name)
|
|
cmd.append('-j')
|
|
cmd.append('--no-pager')
|
|
rc, stdout, stderr = self.module.run_command(cmd)
|
|
return rc, stdout, stderr
|
|
|
|
# Build up dictionary to jsonify for homectl commands.
|
|
def create_json_record(self, create=False):
|
|
record = {}
|
|
user_metadata = {}
|
|
self.result['changed'] = False
|
|
# Get the current user record if not creating a new user record.
|
|
if not create:
|
|
rc, user_metadata, stderr = self.get_user_metadata()
|
|
user_metadata = json.loads(user_metadata)
|
|
# Remove elements that are not meant to be updated from record.
|
|
# These are always part of the record when a user exists.
|
|
user_metadata.pop('signature', None)
|
|
user_metadata.pop('binding', None)
|
|
user_metadata.pop('status', None)
|
|
# Let last change Usec be updated by homed when command runs.
|
|
user_metadata.pop('lastChangeUSec', None)
|
|
# Now only change fields that are called on leaving whats currently in the record intact.
|
|
record = user_metadata
|
|
|
|
record['userName'] = self.name
|
|
record['secret'] = {'password': [self.password]}
|
|
|
|
if create:
|
|
password_hash = self._hash_password(self.password)
|
|
record['privileged'] = {'hashedPassword': [password_hash]}
|
|
self.result['changed'] = True
|
|
|
|
if self.uid and self.gid and create:
|
|
record['uid'] = self.uid
|
|
record['gid'] = self.gid
|
|
self.result['changed'] = True
|
|
|
|
if self.memberof:
|
|
member_list = list(self.memberof.split(','))
|
|
if member_list != record.get('memberOf', [None]):
|
|
record['memberOf'] = member_list
|
|
self.result['changed'] = True
|
|
|
|
if self.realname:
|
|
if self.realname != record.get('realName'):
|
|
record['realName'] = self.realname
|
|
self.result['changed'] = True
|
|
|
|
# Cannot update storage unless were creating a new user.
|
|
# See 'Fields in the binding section' at https://systemd.io/USER_RECORD/
|
|
if self.storage and create:
|
|
record['storage'] = self.storage
|
|
self.result['changed'] = True
|
|
|
|
# Cannot update homedir unless were creating a new user.
|
|
# See 'Fields in the binding section' at https://systemd.io/USER_RECORD/
|
|
if self.homedir and create:
|
|
record['homeDirectory'] = self.homedir
|
|
self.result['changed'] = True
|
|
|
|
# Cannot update imagepath unless were creating a new user.
|
|
# See 'Fields in the binding section' at https://systemd.io/USER_RECORD/
|
|
if self.imagepath and create:
|
|
record['imagePath'] = self.imagepath
|
|
self.result['changed'] = True
|
|
|
|
if self.disksize:
|
|
# convert humand readble to bytes
|
|
if self.disksize != record.get('diskSize'):
|
|
record['diskSize'] = human_to_bytes(self.disksize)
|
|
self.result['changed'] = True
|
|
|
|
if self.realm:
|
|
if self.realm != record.get('realm'):
|
|
record['realm'] = self.realm
|
|
self.result['changed'] = True
|
|
|
|
if self.email:
|
|
if self.email != record.get('emailAddress'):
|
|
record['emailAddress'] = self.email
|
|
self.result['changed'] = True
|
|
|
|
if self.location:
|
|
if self.location != record.get('location'):
|
|
record['location'] = self.location
|
|
self.result['changed'] = True
|
|
|
|
if self.iconname:
|
|
if self.iconname != record.get('iconName'):
|
|
record['iconName'] = self.iconname
|
|
self.result['changed'] = True
|
|
|
|
if self.skeleton:
|
|
if self.skeleton != record.get('skeletonDirectory'):
|
|
record['skeletonDirectory'] = self.skeleton
|
|
self.result['changed'] = True
|
|
|
|
if self.shell:
|
|
if self.shell != record.get('shell'):
|
|
record['shell'] = self.shell
|
|
self.result['changed'] = True
|
|
|
|
if self.umask:
|
|
if self.umask != record.get('umask'):
|
|
record['umask'] = self.umask
|
|
self.result['changed'] = True
|
|
|
|
if self.environment:
|
|
if self.environment != record.get('environment', [None]):
|
|
record['environment'] = list(self.environment.split(','))
|
|
self.result['changed'] = True
|
|
|
|
if self.timezone:
|
|
if self.timezone != record.get('timeZone'):
|
|
record['timeZone'] = self.timezone
|
|
self.result['changed'] = True
|
|
|
|
if self.locked:
|
|
if self.locked != record.get('locked'):
|
|
record['locked'] = self.locked
|
|
self.result['changed'] = True
|
|
|
|
if self.passwordhint:
|
|
if self.passwordhint != record.get('privileged', {}).get('passwordHint'):
|
|
record['privileged']['passwordHint'] = self.passwordhint
|
|
self.result['changed'] = True
|
|
|
|
if self.sshkeys:
|
|
if self.sshkeys != record.get('privileged', {}).get('sshAuthorizedKeys'):
|
|
record['privileged']['sshAuthorizedKeys'] = list(self.sshkeys.split(','))
|
|
self.result['changed'] = True
|
|
|
|
if self.language:
|
|
if self.locked != record.get('preferredLanguage'):
|
|
record['preferredLanguage'] = self.language
|
|
self.result['changed'] = True
|
|
|
|
if self.notbefore:
|
|
if self.locked != record.get('notBeforeUSec'):
|
|
record['notBeforeUSec'] = self.notbefore
|
|
self.result['changed'] = True
|
|
|
|
if self.notafter:
|
|
if self.locked != record.get('notAfterUSec'):
|
|
record['notAfterUSec'] = self.notafter
|
|
self.result['changed'] = True
|
|
|
|
if self.mountopts:
|
|
opts = list(self.mountopts.split(','))
|
|
if 'nosuid' in opts:
|
|
if record.get('mountNoSuid') is not True:
|
|
record['mountNoSuid'] = True
|
|
self.result['changed'] = True
|
|
else:
|
|
if record.get('mountNoSuid') is not False:
|
|
record['mountNoSuid'] = False
|
|
self.result['changed'] = True
|
|
|
|
if 'nodev' in opts:
|
|
if record.get('mountNoDevices') is not True:
|
|
record['mountNoDevices'] = True
|
|
self.result['changed'] = True
|
|
else:
|
|
if record.get('mountNoDevices') is not False:
|
|
record['mountNoDevices'] = False
|
|
self.result['changed'] = True
|
|
|
|
if 'noexec' in opts:
|
|
if record.get('mountNoExecute') is not True:
|
|
record['mountNoExecute'] = True
|
|
self.result['changed'] = True
|
|
else:
|
|
if record.get('mountNoExecute') is not False:
|
|
record['mountNoExecute'] = False
|
|
self.result['changed'] = True
|
|
|
|
return jsonify(record)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
|
name=dict(type='str', required=True, aliases=['user', 'username']),
|
|
password=dict(type='str', no_log=True),
|
|
storage=dict(type='str', choices=['classic', 'luks', 'directory', 'subvolume', 'fscrypt', 'cifs']),
|
|
disksize=dict(type='str'),
|
|
resize=dict(type='bool', default=False),
|
|
realname=dict(type='str', aliases=['comment']),
|
|
realm=dict(type='str'),
|
|
email=dict(type='str'),
|
|
location=dict(type='str'),
|
|
iconname=dict(type='str'),
|
|
homedir=dict(type='path'),
|
|
imagepath=dict(type='path'),
|
|
uid=dict(type='int'),
|
|
gid=dict(type='int'),
|
|
umask=dict(type='int'),
|
|
environment=dict(type='str', aliases=['setenv']),
|
|
timezone=dict(type='str'),
|
|
memberof=dict(type='str', aliases=['groups']),
|
|
skeleton=dict(type='path', aliases=['skel']),
|
|
shell=dict(type='str'),
|
|
locked=dict(type='bool'),
|
|
passwordhint=dict(type='str', no_log=True),
|
|
sshkeys=dict(type='str', no_log=True),
|
|
language=dict(type='str'),
|
|
notbefore=dict(type='int'),
|
|
notafter=dict(type='int'),
|
|
mountopts=dict(type='str'),
|
|
),
|
|
supports_check_mode=True,
|
|
|
|
required_if=[
|
|
('state', 'present', ['password']),
|
|
('resize', True, ['disksize']),
|
|
]
|
|
)
|
|
|
|
homectl = Homectl(module)
|
|
homectl.result['state'] = homectl.state
|
|
|
|
# First we need to make sure homed service is active
|
|
if not homectl.homed_service_active():
|
|
module.fail_json(msg='systemd-homed.service is not active')
|
|
|
|
# handle removing user
|
|
if homectl.state == 'absent':
|
|
user_exists, valid_pwhash = homectl.user_exists()
|
|
if user_exists:
|
|
if module.check_mode:
|
|
module.exit_json(changed=True)
|
|
rc, stdout, stderr = homectl.remove_user()
|
|
if rc != 0:
|
|
module.fail_json(name=homectl.name, msg=stderr, rc=rc)
|
|
homectl.result['changed'] = True
|
|
homectl.result['rc'] = rc
|
|
homectl.result['msg'] = 'User %s removed!' % homectl.name
|
|
else:
|
|
homectl.result['changed'] = False
|
|
homectl.result['msg'] = 'User does not exist!'
|
|
|
|
# Handle adding a user
|
|
if homectl.state == 'present':
|
|
user_exists, valid_pwhash = homectl.user_exists()
|
|
if not user_exists:
|
|
if module.check_mode:
|
|
module.exit_json(changed=True)
|
|
rc, stdout, stderr = homectl.create_user()
|
|
if rc != 0:
|
|
module.fail_json(name=homectl.name, msg=stderr, rc=rc)
|
|
rc, user_metadata, stderr = homectl.get_user_metadata()
|
|
homectl.result['data'] = json.loads(user_metadata)
|
|
homectl.result['rc'] = rc
|
|
homectl.result['msg'] = 'User %s created!' % homectl.name
|
|
else:
|
|
if valid_pwhash:
|
|
# Run this to see if changed would be True or False which is useful for check_mode
|
|
cmd, record = homectl.prepare_modify_user_command()
|
|
else:
|
|
# User gave wrong password fail with message
|
|
homectl.result['changed'] = False
|
|
homectl.result['msg'] = 'User exists but password is incorrect!'
|
|
module.fail_json(**homectl.result)
|
|
|
|
if module.check_mode:
|
|
module.exit_json(**homectl.result)
|
|
|
|
# Now actually modify the user if changed was set to true at any point.
|
|
if homectl.result['changed']:
|
|
rc, stdout, stderr = module.run_command(cmd, data=record)
|
|
if rc != 0:
|
|
module.fail_json(name=homectl.name, msg=stderr, rc=rc, changed=False)
|
|
rc, user_metadata, stderr = homectl.get_user_metadata()
|
|
homectl.result['data'] = json.loads(user_metadata)
|
|
homectl.result['rc'] = rc
|
|
if homectl.result['changed']:
|
|
homectl.result['msg'] = 'User %s modified' % homectl.name
|
|
|
|
module.exit_json(**homectl.result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|