ipauser: Add support for renaming users

FreeIPA suports renaming user objects with the CLI parameter "rename",
and this parameter was missing in ansible-freeipa ipauser module.

This patch adds support for a new state 'renamed' and the 'rename'
parameter.

Tests were updated to cope with the changes.

Related to RHBZ#2234379, RHBZ#2234380

Fixes #1103
This commit is contained in:
Rafael Guterres Jeffman
2023-11-14 17:47:58 -03:00
parent 0f2c37612e
commit 3eb86b2c2d
4 changed files with 169 additions and 26 deletions

View File

@@ -279,7 +279,6 @@ Example playbook to disable a user:
This can also be done as an alternative with the `users` variable containing only names.
Example playbook to enable users:
```yaml
@@ -298,6 +297,22 @@ Example playbook to enable users:
This can also be done as an alternative with the `users` variable containing only names.
Example playbook to rename users:
```yaml
---
- name: Playbook to handle users
hosts: ipaserver
become: true
tasks:
# Rename user pinky to reddy
- ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
rename: reddy
state: enabled
```
Example playbook to unlock users:
@@ -401,7 +416,7 @@ Variable | Description | Required
`update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`preserve` | Delete a user, keeping the entry available for future use. (bool) | no
`action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
@@ -458,10 +473,10 @@ Variable | Description | Required
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
Return Values
=============
@@ -477,5 +492,5 @@ Variable | Description | Returned When
Authors
=======
Thomas Woerner
Rafael Jeffman
- Thomas Woerner
- Rafael Jeffman

View File

@@ -319,6 +319,11 @@ options:
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
required: false
first:
description: The first name. Required if user does not exist.
@@ -586,6 +591,11 @@ options:
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
preserve:
description: Delete a user, keeping the entry available for future use
required: false
@@ -607,7 +617,8 @@ options:
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
@@ -694,6 +705,13 @@ EXAMPLES = """
smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:"
# Rename an existing user
- ipauser:
ipaadmin_password: SomeADMINpassword
name: someuser
rename: anotheruser
state: renamed
"""
RETURN = """
@@ -857,7 +875,7 @@ def check_parameters( # pylint: disable=unused-argument
employeenumber, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, update_password,
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
idp, ipa_user_id,
idp, ipa_user_id, rename
):
if state == "present" and action == "user":
invalid = ["preserve"]
@@ -885,6 +903,19 @@ def check_parameters( # pylint: disable=unused-argument
module.fail_json(
msg="Preserve is only possible for state=absent")
if state != "renamed":
invalid.append("rename")
else:
invalid.extend([
"preserve", "principal", "manager", "certificate", "certmapdata",
])
if not rename:
module.fail_json(
msg="A value for attribute 'rename' must be provided.")
if action == "member":
module.fail_json(
msg="Action member can not be used with state: renamed.")
module.params_fail_used_invalid(invalid, state, action)
if certmapdata is not None:
@@ -1097,6 +1128,8 @@ def main():
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
idp_user_id=dict(type="str", default=None,
aliases=['ipaidpconfiglink']),
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
)
ansible_module = IPAAnsibleModule(
@@ -1128,7 +1161,7 @@ def main():
choices=["member", "user"]),
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]),
"unlocked", "undeleted", "renamed"]),
# Add user specific parameters for simple use case
**user_spec
@@ -1209,6 +1242,8 @@ def main():
preserve = ansible_module.params_get("preserve")
# mod
update_password = ansible_module.params_get("update_password")
# rename
rename = ansible_module.params_get("rename")
# general
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")
@@ -1219,27 +1254,30 @@ def main():
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")
if state == "present":
if state in ["present", "renamed"]:
if names is not None and len(names) != 1:
act = "renamed" if state == "renamed" else "added"
ansible_module.fail_json(
msg="Only one user can be added at a time using name.")
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos, shell,
email,
principal, principalexpiration, passwordexpiration, password, random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
certmapdata = convert_certmapdata(certmapdata)
msg="Only one user can be %s at a time using name." % (act))
# Use users if names is None
if users is not None:
names = users
else:
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos,
shell, email,
principal, principalexpiration, passwordexpiration, password,
random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certmapdata = convert_certmapdata(certmapdata)
# Init
@@ -1330,6 +1368,7 @@ def main():
smb_home_drive = user.get("smb_home_drive")
idp = user.get("idp")
idp_user_id = user.get("idp_user_id")
rename = user.get("rename")
certificate = user.get("certificate")
certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate")
@@ -1346,7 +1385,8 @@ def main():
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certmapdata = convert_certmapdata(certmapdata)
# Check API specific parameters
@@ -1737,6 +1777,12 @@ def main():
else:
raise ValueError("No user '%s'" % name)
elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No user '%s'" % name)
else:
if rename != name:
commands.append([name, 'user_mod', {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)

View File

@@ -9,7 +9,7 @@
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: manager1,manager2,manager3,pinky,pinky2,igagarin
name: manager1,manager2,manager3,pinky,pinky2,igagarin,reddy
state: absent
- name: User manager1 present
@@ -352,6 +352,46 @@
register: result
failed_when: result.changed or result.failed
- name: Rename user pinky to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename user pinky to reddy, again
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.failed or "No user 'pinky'" not in result.msg
- name: Rename user reddy to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: reddy
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename user reddy back to pinky
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: pinky
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: User pinky absent and preserved for future exclusion.
ipauser:
ipaadmin_password: SomeADMINpassword

View File

@@ -5,6 +5,12 @@
gather_facts: false
tasks:
- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
name: manager1,manager2,manager3,pinky,pinky2,mod1,mod2
state: absent
- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
@@ -48,7 +54,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Users user1..10 present
- name: Users user1..10 present, again
ipauser:
ipaadmin_password: SomeADMINpassword
users:
@@ -85,6 +91,42 @@
register: result
failed_when: result.changed or result.failed
- name: Rename users user1 and user2 to mod1 and mod1
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: user1
rename: mod1
- name: user2
rename: mod2
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename users mod1 and mod2 to the same name
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: mod1
- name: mod2
rename: mod2
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename users mod1 and mod2 back to user1 and user2
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: user1
- name: mod2
rename: user2
state: renamed
register: result
failed_when: not result.changed or result.failed
# failed_when: not result.failed has been added as this test needs to
# fail because two users with the same name should be added in the same
# task.