From 102736d1a449eac8130912dcf528339a1e9ac4d1 Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Thu, 25 Aug 2022 21:10:34 +0200 Subject: [PATCH] ipagroup: Implement state:quers using IPAAnsibleModule.execute_query The query_param parameter has been added, together with the dict query_param_settings. The existing find_group function has been transformed into user_show to get the result for a single user and new convert_result and user_find function have been added. --- plugins/modules/ipagroup.py | 150 +++++++++++++++++++++++---- tests/group/test_group_query.yml | 171 +++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 tests/group/test_group_query.yml diff --git a/plugins/modules/ipagroup.py b/plugins/modules/ipagroup.py index 09d8e1c936..452b314477 100644 --- a/plugins/modules/ipagroup.py +++ b/plugins/modules/ipagroup.py @@ -38,7 +38,7 @@ - ipamodule_base_docs options: name: - description: The group name + description: The group namename required: false aliases: ["cn"] description: @@ -102,6 +102,9 @@ - User ID overrides to add required: false type: list + query_param: + description: The fields to query with state=query + required: false action: description: Work on group or member level default: group @@ -109,7 +112,8 @@ state: description: State to ensure default: present - choices: ["present", "absent"] + choices: ["present", "absent", + "query"] author: - Thomas Woerner """ @@ -189,31 +193,73 @@ from ansible.module_utils._text import to_text from ansible.module_utils.ansible_freeipa_module import \ IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \ - gen_add_list, gen_intersection_list, api_check_param + gen_add_list, gen_intersection_list, api_check_param, ipalib_errors -def find_group(module, name): +def group_show(module, name): _args = { "all": True, - "cn": name, } - _result = module.ipa_command("group_find", name, _args) - - if len(_result["result"]) > 1: - module.fail_json( - msg="There is more than one group '%s'" % (name)) - elif len(_result["result"]) == 1: - _res = _result["result"][0] - # The returned services are of type ipapython.kerberos.Principal, - # also services are not case sensitive. Therefore services are - # converted to lowercase strings to be able to do the comparison. - if "member_service" in _res: + try: + _result = module.ipa_command("group_show", name, _args).get("result") + except ipalib_errors.NotFound: + return None + + # The returned services are of type ipapython.kerberos.Principal, + # also services are not case sensitive. Therefore services are + # converted to lowercase strings to be able to do the comparison. + if "member_service" in _result: + _result["member_service"] = \ + [to_text(svc).lower() for svc in _result["member_service"]] + return _result + + +def convert_result(res): + _res = {} + for key in res: + if key == "member_service": _res["member_service"] = \ - [to_text(svc).lower() for svc in _res["member_service"]] - return _res + [to_text(svc).lower() for svc in res["member_service"]] + elif isinstance(res[key], list): + # All single value parameters should not be lists + # This does not apply to manager, krbprincipalname, + # usercertificate and ipacertmapdata + if len(res[key]) == 1: + _res[key] = to_text(res[key][0]) + else: + _res[key] = [to_text(item) for item in res[key]] + elif key in ["gidNumber"]: + _res[key] = int(res[key]) + else: + _res[key] = to_text(res[key]) + return _res + + +def group_find(module, name, sizelimit=None, timelimit=None): + _args = {"all": True} + + if sizelimit is not None: + _args["sizelimit"] = sizelimit + if timelimit is not None: + _args["timelimit"] = timelimit + + try: + if name: + _args["cn"] = name + module.warn("args: %s " % repr(_args)) + _result = module.ipa_command_no_name("group_find", _args).get("result") + module.warn("_result: %s " % repr(_result)) + if _result: + if name: + _result = convert_result(_result[0]) + else: + _result = [convert_result(res) for res in _result] - return None + except ipalib_errors.NotFound: + return None + else: + return _result def gen_args(description, gid, nomembers): @@ -271,12 +317,37 @@ def check_objectclass_args(module, res_find, posix, external): "`non-posix`.") +query_param_settings = { + "ALL": [ + "dn", "objectclass", "ipauniqueid", "ipantsecurityidentifier", + "name", + "description", + "gid", + "nomembers", + "user", "group", "service", "external", + "idoverrideuser" + ], + "BASE": [ + "name", "description", "gid" + ], + "mapping": { + "name": "cn", + "gid": "gidnumber", + "user": "member_user", + "group": "member_group", + "service": "member_service", + "externalmember": "member_external", + "idoverrideuser": "member_idoverrideuser", + } +} + + def main(): ansible_module = IPAAnsibleModule( argument_spec=dict( # general name=dict(type="list", aliases=["cn"], default=None, - required=True), + required=False), # present description=dict(type="str", default=None), gid=dict(type="int", aliases=["gidnumber"], default=None), @@ -296,11 +367,17 @@ def main(): "ipaexternalmember", "external_member" ]), + # query + query_param=dict(type="list", default=None, + choices=["ALL", "BASE"].extend( + query_param_settings["ALL"]), + required=False), + # action action=dict(type="str", default="group", choices=["member", "group"]), # state state=dict(type="str", default="present", - choices=["present", "absent"]), + choices=["present", "absent", "query"]), ), # It does not make sense to set posix, nonposix or external at the # same time @@ -330,6 +407,9 @@ def main(): membermanager_user = ansible_module.params_get("membermanager_user") membermanager_group = ansible_module.params_get("membermanager_group") externalmember = ansible_module.params_get("externalmember") + # query + query_param = ansible_module.params_get("query_param") + # action action = ansible_module.params_get("action") # state state = ansible_module.params_get("state") @@ -354,6 +434,18 @@ def main(): if action == "group": invalid.extend(["user", "group", "service", "externalmember"]) + # if state == "query": + # invalid.append("groups") + # + # if action == "member": + # module.fail_json( + # msg="Query is not possible with action=member") + + if state != "query": + if names is None or len(names) < 1: + ansible_module.fail_json(msg="name is required") + invalid.append("query_param") + ansible_module.params_fail_used_invalid(invalid, state, action) if external is False: @@ -372,6 +464,20 @@ def main(): # Connect to IPA API with ansible_module.ipa_connect(): + if state == "query": + exit_args = ansible_module.execute_query( + names, "groups", "cn", query_param, group_find, + query_param_settings) + + ansible_module.exit_json(changed=False, group=exit_args) + + if state == "query": + exit_args = ansible_module.execute_query( + names, "groups", "cn", query_param, group_find, + query_param_settings) + + ansible_module.exit_json(changed=False, group=exit_args) + has_add_member_service = ansible_module.ipa_command_param_exists( "group_add_member", "service") if service is not None and not has_add_member_service: @@ -399,7 +505,7 @@ def main(): for name in names: # Make sure group exists - res_find = find_group(ansible_module, name) + res_find = group_show(ansible_module, name) user_add, user_del = [], [] group_add, group_del = [], [] diff --git a/tests/group/test_group_query.yml b/tests/group/test_group_query.yml new file mode 100644 index 0000000000..14e323e221 --- /dev/null +++ b/tests/group/test_group_query.yml @@ -0,0 +1,171 @@ +--- +- name: Test group query + hosts: ipaserver + become: true + + tasks: + + # CLEANUP + + - name: Ensure groups "testgroup1" and "testgroup2" are absent + ipagroup: + ipaadmin_password: SomeADMINpassword + name: + - testgroup1 + - testgroup2 + - non-existing-group + state: absent + + # CREATE TEST ITEMS + + - name: Ensure groups "testgroup1" is present + ipagroup: + ipaadmin_password: SomeADMINpassword + name: testgroup1 + description: test group 1 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure groups "testgroup2" is present + ipagroup: + ipaadmin_password: SomeADMINpassword + name: testgroup2 + description: test group 2 + group: testgroup1 + register: result + failed_when: not result.changed or result.failed + + - name: Query group "non-existing-group" + ipagroup: + ipaadmin_password: SomeADMINpassword + name: + - non-existing-group + query_param: ALL + state: query + register: result + failed_when: result.changed or result.failed + + - name: Print query information + debug: + var: result + + - name: Fail on non empty query result + fail: + msg: "{{ result['group'] }} is not empty" + when: result['group'] | length > 0 + + - name: Query all groups + ipagroup: + ipaadmin_password: SomeADMINpassword + state: query + register: result + failed_when: result.changed or result.failed + + - name: Print query information + debug: + var: result + + - name: Fail on missing "testgroup1" in query result + fail: + msg: "'testgroup1' not in query result {{ result['group']['groups'] }}" + when: ("testgroup1" not in result["group"]["groups"]) + + - name: Fail on missing "testgroup2" in query result + fail: + msg: "'testgroup2' not in query result {{ result['group']['groups'] }}" + when: ("testgroup2" not in result["group"]["groups"]) + + - name: Fail on "non-existing-group" in query result + fail: + msg: "'non-existing-group' in query result {{ result['group']['groups'] }}" + when: ("non-existing-group" in result["group"]["groups"]) + + - name: Query groups "testgroup1", "testgroup2" and "non-existing-group" + ipagroup: + ipaadmin_password: SomeADMINpassword + name: + - testgroup1 + - testgroup2 + - non-existing-group + state: query + register: result + failed_when: result.changed or result.failed + + - name: Fail on missing "testgroup1" in query result + fail: + msg: "'testgroup1' not in query result {{ result['group']['groups'] }}" + when: ("testgroup1" not in result["group"]["groups"]) + + - name: Fail on missing "testgroup2" in query result + fail: + msg: "'testgroup2' not in query result {{ result['group']['groups'] }}" + when: ("testgroup2" not in result["group"]["groups"]) + + - name: Fail on "non-existing-group" in query result + fail: + msg: "'non-existing-group' in query result {{ result['group']['groups'] }}" + when: ("non-existing-group" in result["group"]["groups"]) + + + - name: Query all group parameters for "testgroup1" + ipagroup: + ipaadmin_password: SomeADMINpassword + name: + - testgroup2 + query_param: ALL + state: query + register: result + failed_when: result.changed or result.failed + + - name: Print query information + debug: + var: result + + - name: Fail on missing information in query result + fail: + msg: "Query result {{ result['group'] }} is incomplete" + when: (result["group"]["description"] != "test group 2" or + result["group"]["group"] != "testgroup1") + + - name: Query "gid", "user", "group" for all groups + ipagroup: + ipaadmin_password: SomeADMINpassword + query_param: + - gid + - user + - group + state: query + register: result + failed_when: result.changed or result.failed + + - name: Print query information + debug: + var: result + + - name: Fail on less than 3 groups in result + fail: + msg: "{{ result['group'] }} is not empty" + when: result['group'] | length < 3 + + - name: Fail on missing "testgroup1" information in query result + fail: + msg: "'testgroup1' not in query result {{ result['group'] }}" + when: ("testgroup1" not in result["group"] or + "gid" not in result["group"]["testgroup1"]) + + - name: Fail on missing "testgroup2" information in query result + fail: + msg: "'testgroup2' not in query result {{ result['group'] }}" + when: ("testgroup2" not in result["group"] or + "gid" not in result["group"]["testgroup2"] or + result["group"]["testgroup2"]["group"] != "testgroup1") + + # CLEANUP + + - name: Ensure groups "testgroup1" and "testgroup2" are absent + ipagroup: + ipaadmin_password: SomeADMINpassword + name: + - testgroup1 + - testgroup2 + state: absent