Skip to content

Commit

Permalink
[DRAFT]: Add support for state:fetched to user module
Browse files Browse the repository at this point in the history
  • Loading branch information
t-woerner committed Feb 25, 2022
1 parent 9981e5f commit 85e9363
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 9 deletions.
103 changes: 103 additions & 0 deletions plugins/module_utils/ansible_freeipa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,109 @@ def exception_handler(module, ex, exit_args, one_name):

return changed

def execute_fetched(self, exit_args, names, prefix, name_ipa_param,
fetch_params, ipa_param_mapping,
ipa_param_converter,
show_function, find_function):
"""
Execute fetched state.
Parameters
----------
exit_args: Exit args dict
This is the dict that will be resturned with
ansible_module.exit_json
names: The main items to return
It names is not None and not an empty list then all items
found with "item_find" are returned, else the items in names.
prefix: The prefix for use with several main items
The prefix is "users" for the "user" module. It is used
if only the list of main items (example: users) is returned.
name_ipa_param: The IPA param name of the name parameter
This is for example "uid" that is used for the user name in
the user module.
fetch_params: The parameters to return
The parameters that should be returned. If fetch_params is
["all"], all parameters in ipa_pram_names will be returned.
ipa_param_mapping: IPA param mapping
This is the mapping of the default module paramter name to
IPA option name.
Example: "uid" for user name of the user commands.
ipa_param_converter: Parameter converter
This is an extra parameter converter for parameters that
need to be converted into integers for example.
show_function: The function to show one entry
This is "user-show" for the user command.
find_function: The function to find several entries
This is "user-find" for the user command.
Example (ipauser module):
if state == "fetched":
changed = ansible_module.execute_fetched(
exit_args, "users", "uid", fetch_params, ipa_param_mapping,
names, user_show, user_find)
ansible_module.exit_json(changed=False, user=exit_args)
"""

def store_params(exit_args, name, prefix, name_ipa_param, result,
params, ipa_param_converter):
if params is None:
exit_args.setdefault(prefix, []).append(
result[name_ipa_param])
return
for field in params:
ipa_field = ipa_param_mapping[field]

if ipa_field in result:
value = result[ipa_field]
if ipa_param_converter and \
field in ipa_param_converter:
if isinstance(value, list):
value = [ipa_param_converter[field](val)
for val in value]
else:
value = ipa_param_converter[field](value)
else:
if isinstance(value, list):
value = [to_text(val) for val in value]
else:
value = to_text(value)
if name is None:
exit_args[field] = value
else:
exit_args.setdefault(name, {})[field] = value

if fetch_params == ["all"]:
fetch_params = ipa_param_mapping.keys()

# if names and isinstance(names, list):
# with_name = len(names) > 1
# for name in names:
# result = show_function(self, name)
# if result:
# store_params(exit_args, name if with_name else None,
# prefix, name_ipa_param, result,
# fetch_params, ipa_param_converter)
# else:
# results = find_function(self)
# if results is not None:
# for result in results:
# name = result[name_ipa_param]
# store_params(exit_args, name, prefix, name_ipa_param,
# result, fetch_params, ipa_param_converter)

results = find_function(self)
if results is not None:
for result in results:
name = result[name_ipa_param]
if (names and name in names) or not names:
store_params(exit_args, name, prefix, name_ipa_param,
result, fetch_params, ipa_param_converter)

return False

class FreeIPABaseModule(IPAAnsibleModule):
"""
Base class for FreeIPA Ansible modules.
Expand Down
126 changes: 117 additions & 9 deletions plugins/modules/ipauser.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@
default: "always"
choices: ["always", "on_create"]
required: false
fetch_param:
description: The fields to fetch with state=fetched
required: false
action:
description: Work on user or member level
default: "user"
Expand All @@ -393,7 +396,8 @@
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"fetched"]
author:
- Thomas Woerner
"""
Expand Down Expand Up @@ -481,7 +485,7 @@
unicode = str


def find_user(module, name):
def user_show(module, name):
_args = {
"all": True,
}
Expand All @@ -501,6 +505,41 @@ def find_user(module, name):
return _result


def user_find(module):
_args = {
"all": True,
}

try:
_result = module.ipa_command_no_name("user_find", _args).get("result")
except ipalib_errors.NotFound:
return None

for res in _result:
# Transform each principal to a string
if "krbprincipalname" in res:
res["krbprincipalname"] = [
to_text(x) for x in (res.get("krbprincipalname") or [])
]
# Transform each certificate to a string
if "usercertificate" in res:
res["usercertificate"] = [
encode_certificate(x) for x in
(res.get("usercertificate") or [])
]
# All single value parameters should not be lists
for param in res:
if isinstance(res[param], list) and len(res[param]) == 1 and \
param not in ["manager", "krbprincipalname", "usercertificate",
"ipacertmapdata"]:
res[param] = res[param][0]
if param in []:
res[param] = int(res[param])

# module.warn("_result: %s" % repr(_result))
return _result


def gen_args(first, last, fullname, displayname, initials, homedir, shell,
email, principalexpiration, passwordexpiration, password,
random, uid, gid, city, userstate, postalcode, phone, mobile,
Expand Down Expand Up @@ -618,6 +657,15 @@ def check_parameters( # pylint: disable=unused-argument
"certificate", "certmapdata",
])

if state == "fetched":
invalid.append("users")

if action == "member":
module.fail_json(
msg="Fetched is not possible with action=member")
else:
invalid.append("fetch_param")

if state != "absent" and preserve is not None:
module.fail_json(
msg="Preserve is only possible for state=absent")
Expand Down Expand Up @@ -809,6 +857,51 @@ def main():
nomembers=dict(type='bool', default=None),
)

ipa_param_mapping = {
"first": "givenname",
"last": "sn",
"fullname": "cn",
"displayname": "displayname",
"initials": "initials",
"homedir": "homedirectory",
"shell": "loginshell",
"email": "mail",
"principalexpiration": "krbprincipalexpiration",
"passwordexpiration": "krbpasswordexpiration",
# "password": "userpassword", Never return passwords
# "randompassword": "randompassword", Never return passwords
"uid": "uidnumber",
"gid": "gidnumber",
"city": "l",
"userstate": "st",
"postalcode": "postalcode",
"phone": "telephonenumber",
"mobile": "mobile",
"pager": "pager",
"fax": "facsimiletelephonenumber",
"orgunit": "ou",
"title": "title",
"carlicense": "carlicense",
"sshpubkey": "ipasshpubkey",
"userauthtype": "ipauserauthtype",
"userclass": "userclass",
"radius": "ipatokenradiusconfiglink",
"radiususer": "ipatokenradiususername",
"departmentnumber": "departmentnumber",
"employeenumber": "employeenumber",
"employeetype": "employeetype",
"preferredlanguage": "preferredlanguage",
"manager": "manager",
"principal": "krbprincipalname",
"certificate": "usercertificate",
"certmapdata": "ipacertmapdata",
}

ipa_param_converter = {
"uid": int,
"gid": int,
}

ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
Expand All @@ -833,18 +926,24 @@ def main():
update_password=dict(type='str', default=None, no_log=False,
choices=['always', 'on_create']),

# fetched
fetch_param=dict(type="list", default=None,
choices=["all"].extend(ipa_param_mapping.keys()),
required=False),

# general
action=dict(type="str", default="user",
choices=["member", "user"]),
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]),
"unlocked", "undeleted", "fetched"]),

# Add user specific parameters for simple use case
**user_spec
),
mutually_exclusive=[["name", "users"]],
required_one_of=[["name", "users"]],
# Required one of [["name", "users"]] has been removed as there is
# an extra test below and it is not working with state=fetched
supports_check_mode=True,
)

Expand Down Expand Up @@ -909,15 +1008,18 @@ def main():
preserve = ansible_module.params_get("preserve")
# mod
update_password = ansible_module.params_get("update_password")
# fetched
fetch_param = ansible_module.params_get("fetch_param")
# general
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")

# Check parameters

if (names is None or len(names) < 1) and \
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")
if state != "fetched":
if (names is None or len(names) < 1) and \
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")

if state == "present":
if names is not None and len(names) != 1:
Expand Down Expand Up @@ -965,6 +1067,12 @@ def main():
commands = []
user_set = set()

if state == "fetched":
changed = ansible_module.execute_fetched(
exit_args, names, "users", "uid", fetch_param,
ipa_param_mapping, ipa_param_converter, user_show, user_find)
ansible_module.exit_json(changed=False, user=exit_args)

for user in names:
if isinstance(user, dict):
name = user.get("name")
Expand Down Expand Up @@ -1074,7 +1182,7 @@ def main():
"your IPA version")

# Make sure user exists
res_find = find_user(ansible_module, name)
res_find = user_show(ansible_module, name)

# Create command
if state == "present":
Expand Down Expand Up @@ -1141,7 +1249,7 @@ def main():
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("krbprincipalname"))
# Principals are not returned as utf8 for IPA using
# python2 using user_find, therefore we need to
# python2 using user_show, therefore we need to
# convert the principals that we should remove.
principal_del = [to_text(x) for x in principal_del]

Expand Down
Loading

0 comments on commit 85e9363

Please sign in to comment.