From 495677df38e5c913d17100eaed9f38ebd35f7d5c Mon Sep 17 00:00:00 2001 From: Denis Karpelevich <dkarpele@redhat.com> Date: Wed, 10 Aug 2022 23:18:47 +0200 Subject: [PATCH] New netgroup management module There is a new netgroup management module placed in the plugins folder: plugins/modules/ipanetgroup.py The netgroup module allows to ensure presence or absence of netgroup and manage netgroup members. Here is the documentation for the module: README-netgroup.md New example playbooks have been added: playbooks/netgroup/netgroup-absent.yml playbooks/netgroup/netgroup-member-absent.yml playbooks/netgroup/netgroup-member-present.yml playbooks/netgroup/netgroup-present.yml New tests for the module: tests/netgroup/test_netgroup.yml tests/netgroup/test_netgroup_client_context.yml tests/netgroup/test_netgroup_member.yml tests/netgroup/test_netgroup_member_absent.yml tests/netgroup/test_netgroup_member_case_insensitive.yml Signed-off-by: Denis Karpelevich <dkarpele@redhat.com> --- README-netgroup.md | 179 ++++++++ README.md | 2 + playbooks/netgroup/netgroup-absent.yml | 12 + playbooks/netgroup/netgroup-member-absent.yml | 14 + .../netgroup/netgroup-member-present.yml | 13 + playbooks/netgroup/netgroup-present.yml | 12 + plugins/modules/ipanetgroup.py | 423 ++++++++++++++++++ tests/netgroup/test_netgroup.yml | 149 ++++++ .../netgroup/test_netgroup_client_context.yml | 51 +++ tests/netgroup/test_netgroup_member.yml | 159 +++++++ .../netgroup/test_netgroup_member_absent.yml | 206 +++++++++ .../test_netgroup_member_case_insensitive.yml | 251 +++++++++++ 12 files changed, 1471 insertions(+) create mode 100644 README-netgroup.md create mode 100644 playbooks/netgroup/netgroup-absent.yml create mode 100644 playbooks/netgroup/netgroup-member-absent.yml create mode 100644 playbooks/netgroup/netgroup-member-present.yml create mode 100644 playbooks/netgroup/netgroup-present.yml create mode 100644 plugins/modules/ipanetgroup.py create mode 100644 tests/netgroup/test_netgroup.yml create mode 100644 tests/netgroup/test_netgroup_client_context.yml create mode 100644 tests/netgroup/test_netgroup_member.yml create mode 100644 tests/netgroup/test_netgroup_member_absent.yml create mode 100644 tests/netgroup/test_netgroup_member_case_insensitive.yml diff --git a/README-netgroup.md b/README-netgroup.md new file mode 100644 index 00000000..468e1057 --- /dev/null +++ b/README-netgroup.md @@ -0,0 +1,179 @@ +Netgroup module +============ + +Description +----------- + +The netgroup module allows to ensure presence and absence of netgroups. + +Features +-------- + +* Netgroup management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipanetgroup module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + + +Example playbook to make sure netgroup "my_netgroup1" is present: + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroup my_netgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + description: My netgroup 1 +``` + + +Example playbook to make sure netgroup "my_netgroup1" is absent: + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroup my_netgroup1 is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + state: absent +``` + + +Example playbook to make sure netgroup is present with user "user1" + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroup is present with user "user1" + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: user1 + action: member +``` + + +Example playbook to make sure netgroup user, "user1", is absent + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroup user, "user1", is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: "user1" + action: member + state: absent +``` + + +Example playbook to make sure netgroup is present with members + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroup members are present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: user1,user2 + group: group1 + host: host1 + hostgroup: ipaservers + netgroup: admins + action: member +``` + + +Example playbook to make sure 2 netgroups TestNetgroup1, admins are absent + +```yaml +--- +- name: Playbook to manage IPA netgroup. + hosts: ipaserver + become: no + + tasks: + - name: Ensure netgroups are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: + - TestNetgroup1 + - admins + state: absent +``` + + +Variables +--------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no +`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to yes. (bool) | no +`name` \| `cn` | The list of netgroup name strings. | yes +`description` | Netgroup description | no +`nisdomain` | NIS domain name | no +`nomembers` | Suppress processing of membership attributes. (bool) | no +`user` | List of user name strings assigned to this netgroup. | no +`group` | List of group name strings assigned to this netgroup. | no +`host` | List of host name strings assigned to this netgroup. | no +`hostgroup` | List of hostgroup name strings assigned to this netgroup. | no +`netgroup` | List of netgroup name strings assigned to this netgroup. | no +`action` | Work on group or member level. It can be on of `member` or `netgroup` and defaults to `netgroup`. | no +`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no + + +Authors +======= + +Denis Karpelevich diff --git a/README.md b/README.md index a3ddc3aa..f3ff4aaa 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Features * Modules for hostgroup management * Modules for idrange management * Modules for location management +* Modules for netgroup management * Modules for permission management * Modules for privilege management * Modules for pwpolicy management @@ -450,6 +451,7 @@ Modules in plugin/modules * [ipahostgroup](README-hostgroup.md) * [idrange](README-idrange.md) * [ipalocation](README-location.md) +* [ipanetgroup](README-netgroup.md) * [ipapermission](README-permission.md) * [ipaprivilege](README-privilege.md) * [ipapwpolicy](README-pwpolicy.md) diff --git a/playbooks/netgroup/netgroup-absent.yml b/playbooks/netgroup/netgroup-absent.yml new file mode 100644 index 00000000..c3d9d593 --- /dev/null +++ b/playbooks/netgroup/netgroup-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Netgroup absent example + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure netgroup my_netgroup1 is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + state: absent diff --git a/playbooks/netgroup/netgroup-member-absent.yml b/playbooks/netgroup/netgroup-member-absent.yml new file mode 100644 index 00000000..54eadd4b --- /dev/null +++ b/playbooks/netgroup/netgroup-member-absent.yml @@ -0,0 +1,14 @@ +--- +- name: Netgroup absent example + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure netgroup user, "user1", is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: "user1" + action: member + state: absent diff --git a/playbooks/netgroup/netgroup-member-present.yml b/playbooks/netgroup/netgroup-member-present.yml new file mode 100644 index 00000000..b14d2386 --- /dev/null +++ b/playbooks/netgroup/netgroup-member-present.yml @@ -0,0 +1,13 @@ +--- +- name: Netgroup member present example + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure netgroup is present with user "user1" + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: user1 + action: member diff --git a/playbooks/netgroup/netgroup-present.yml b/playbooks/netgroup/netgroup-present.yml new file mode 100644 index 00000000..5fdf7c03 --- /dev/null +++ b/playbooks/netgroup/netgroup-present.yml @@ -0,0 +1,12 @@ +--- +- name: Netgroup present example + hosts: ipaserver + become: no + gather_facts: no + + tasks: + - name: Ensure netgroup my_netgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + description: My netgroup 1 diff --git a/plugins/modules/ipanetgroup.py b/plugins/modules/ipanetgroup.py new file mode 100644 index 00000000..bb5b4c13 --- /dev/null +++ b/plugins/modules/ipanetgroup.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- + +# Authors: +# Denis Karpelevich <dkarpele@redhat.com> +# +# Copyright (C) 2022 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipanetgroup +short_description: NIS entities can be stored in netgroups. +description: | + A netgroup is a group used for permission checking. + It can contain both user and host values. +extends_documentation_fragment: + - ipamodule_base_docs + - ipamodule_base_docs.delete_continue +options: + name: + description: The list of netgroup name strings. + required: true + type: list + elements: str + aliases: ["cn"] + description: + description: Netgroup description + required: false + type: str + aliases: ["desc"] + nisdomain: + description: NIS domain name + required: false + type: str + aliases: ["nisdomainname"] + nomembers: + description: Suppress processing of membership attributes + required: false + type: bool + user: + description: List of user names assigned to this netgroup. + required: false + type: list + elements: str + aliases: ["users"] + group: + description: List of group names assigned to this netgroup. + required: false + type: list + elements: str + aliases: ["groups"] + host: + description: List of host names assigned to this netgroup. + required: false + type: list + elements: str + aliases: ["hosts"] + hostgroup: + description: List of host group names assigned to this netgroup. + required: false + type: list + elements: str + aliases: ["hostgroups"] + netgroup: + description: List of netgroup names assigned to this netgroup. + required: false + type: list + elements: str + aliases: ["netgroups"] + action: + description: Work on netgroup or member level + required: false + default: netgroup + choices: ["member", "netgroup"] + state: + description: The state to ensure. + choices: ["present", "absent"] + default: present +author: + - Denis Karpelevich (@dkarpele) +""" + +EXAMPLES = """ +- name: Ensure netgroup my_netgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + description: My netgroup 1 + +- name: Ensure netgroup my_netgroup1 is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: my_netgroup1 + state: absent + +- name: Ensure netgroup is present with user "user1" + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: user1 + action: member + +- name: Ensure netgroup user, "user1", is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: "user1" + action: member + state: absent + +- name: Ensure netgroup is present with members + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: TestNetgroup1 + user: user1,user2 + group: group1 + host: host1 + hostgroup: ipaservers + netgroup: admins + action: member + +- name: Ensure 2 netgroups TestNetgroup1, admins are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: + - TestNetgroup1 + - admins + state: absent +""" + +RETURN = """ +""" + + +from ansible.module_utils.ansible_freeipa_module import \ + IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \ + gen_add_list, gen_intersection_list, ipalib_errors, ensure_fqdn + + +def find_netgroup(module, name): + """Find if a netgroup with the given name already exist.""" + try: + _result = module.ipa_command("netgroup_show", name, {"all": True}) + except ipalib_errors.NotFound: + # An exception is raised if netgroup name is not found. + return None + else: + return _result["result"] + + +def gen_args(description, nisdomain, nomembers): + _args = {} + if description is not None: + _args["description"] = description + if nisdomain is not None: + _args["nisdomainname"] = nisdomain + if nomembers is not None: + _args["nomembers"] = nomembers + + return _args + + +def gen_member_args(user, group, host, hostgroup, netgroup): + _args = {} + if user is not None: + _args["memberuser_user"] = user + if group is not None: + _args["memberuser_group"] = group + if host is not None: + _args["memberhost_host"] = host + if hostgroup is not None: + _args["memberhost_hostgroup"] = hostgroup + if netgroup is not None: + _args["member_netgroup"] = netgroup + + return _args + + +def main(): + ansible_module = IPAAnsibleModule( + argument_spec=dict( + # general + name=dict(type="list", elements="str", aliases=["cn"], + required=True), + # present + description=dict(required=False, type='str', + aliases=["desc"], default=None), + nisdomain=dict(required=False, type='str', + aliases=["nisdomainname"], default=None), + nomembers=dict(required=False, type='bool', default=None), + user=dict(required=False, type='list', elements="str", + aliases=["users"], default=None), + group=dict(required=False, type='list', elements="str", + aliases=["groups"], default=None), + host=dict(required=False, type='list', elements="str", + aliases=["hosts"], default=None), + hostgroup=dict(required=False, type='list', elements="str", + aliases=["hostgroups"], default=None), + netgroup=dict(required=False, type='list', elements="str", + aliases=["netgroups"], default=None), + action=dict(required=False, type="str", default="netgroup", + choices=["member", "netgroup"]), + # state + state=dict(type="str", default="present", + choices=["present", "absent"]), + ), + supports_check_mode=True, + ipa_module_options=["delete_continue"], + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + names = ansible_module.params_get("name") + + # present + description = ansible_module.params_get("description") + nisdomain = ansible_module.params_get("nisdomain") + nomembers = ansible_module.params_get("nomembers") + user = ansible_module.params_get_lowercase("user") + group = ansible_module.params_get_lowercase("group") + host = ansible_module.params_get_lowercase("host") + hostgroup = ansible_module.params_get_lowercase("hostgroup") + netgroup = ansible_module.params_get_lowercase("netgroup") + action = ansible_module.params_get("action") + + # state + state = ansible_module.params_get("state") + + # Check parameters + + invalid = [] + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one netgroup can be added at a time.") + if action == "member": + invalid = ["description", "nisdomain", "nomembers"] + + if state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + if len(names) != 1 and action == "member": + ansible_module.fail_json(msg="Members can be removed only from one" + " netgroup at a time.") + invalid = ["description", "nisdomain", "nomembers"] + if action == "netgroup": + invalid.extend(["user", "group", "host", "hostgroup", "netgroup"]) + + ansible_module.params_fail_used_invalid(invalid, state) + + # Init + + exit_args = {} + + # Connect to IPA API + with ansible_module.ipa_connect(): + # Ensure fqdn host names, use default domain for simple names + if host is not None: + default_domain = ansible_module.ipa_get_domain() + host = [ensure_fqdn(_host, default_domain).lower() + for _host in host] + + commands = [] + for name in names: + # Make sure netgroup exists + res_find = find_netgroup(ansible_module, name) + + user_add, user_del = [], [] + group_add, group_del = [], [] + host_add, host_del = [], [] + hostgroup_add, hostgroup_del = [], [] + netgroup_add, netgroup_del = [], [] + + # Create command + if state == "present": + # Generate args + args = gen_args(description, nisdomain, nomembers) + + if action == "netgroup": + # Found the netgroup + if res_find is not None: + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify + if not compare_args_ipa(ansible_module, args, + res_find): + commands.append([name, "netgroup_mod", args]) + else: + commands.append([name, "netgroup_add", args]) + res_find = {} + + member_args = gen_member_args( + user, group, host, hostgroup, netgroup + ) + if not compare_args_ipa(ansible_module, member_args, + res_find): + # Generate addition and removal lists + user_add, user_del = gen_add_del_lists( + user, res_find.get("memberuser_user")) + + group_add, group_del = gen_add_del_lists( + group, res_find.get("memberuser_group")) + + host_add, host_del = gen_add_del_lists( + host, res_find.get("memberhost_host")) + + hostgroup_add, hostgroup_del = gen_add_del_lists( + hostgroup, res_find.get("memberhost_hostgroup")) + + netgroup_add, netgroup_del = gen_add_del_lists( + netgroup, res_find.get("member_netgroup")) + + elif action == "member": + if res_find is None: + ansible_module.fail_json(msg="No netgroup '%s'" % name) + + # Reduce add lists for memberuser_user, memberuser_group, + # member_service and member_external to new entries + # only that are not in res_find. + user_add = gen_add_list( + user, res_find.get("memberuser_user")) + group_add = gen_add_list( + group, res_find.get("memberuser_group")) + host_add = gen_add_list( + host, res_find.get("memberhost_host")) + hostgroup_add = gen_add_list( + hostgroup, res_find.get("memberhost_hostgroup")) + netgroup_add = gen_add_list( + netgroup, res_find.get("member_netgroup")) + + elif state == "absent": + if action == "netgroup": + if res_find is not None: + commands.append([name, "netgroup_del", {}]) + + elif action == "member": + if res_find is None: + ansible_module.fail_json(msg="No netgroup '%s'" % name) + user_del = gen_intersection_list( + user, res_find.get("memberuser_user")) + group_del = gen_intersection_list( + group, res_find.get("memberuser_group")) + host_del = gen_intersection_list( + host, res_find.get("memberhost_host")) + hostgroup_del = gen_intersection_list( + hostgroup, res_find.get("memberhost_hostgroup")) + netgroup_del = gen_intersection_list( + netgroup, res_find.get("member_netgroup")) + + else: + ansible_module.fail_json(msg="Unknown state '%s'" % state) + + # manage members + # setup member args for add/remove members. + add_member_args = { + "user": user_add, + "group": group_add, + "host": host_add, + "hostgroup": hostgroup_add, + "netgroup": netgroup_add + } + + del_member_args = { + "user": user_del, + "group": group_del, + "host": host_del, + "hostgroup": hostgroup_del, + "netgroup": netgroup_del + } + + # Add members + add_members = any([user_add, group_add, host_add, + hostgroup_add, netgroup_add]) + if add_members: + commands.append( + [name, "netgroup_add_member", add_member_args] + ) + # Remove members + remove_members = any([user_del, group_del, host_del, + hostgroup_del, netgroup_del]) + if remove_members: + commands.append( + [name, "netgroup_remove_member", del_member_args] + ) + # Execute commands + + changed = ansible_module.execute_ipa_commands( + commands, fail_on_member_errors=True) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/netgroup/test_netgroup.yml b/tests/netgroup/test_netgroup.yml new file mode 100644 index 00000000..d4ac69e9 --- /dev/null +++ b/tests/netgroup/test_netgroup.yml @@ -0,0 +1,149 @@ +--- +- name: Test netgroup + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: no + gather_facts: no + + tasks: + - block: + # CLEANUP TEST ITEMS + - name: Ensure netgroups are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - my_netgroup1 + - my_netgroup2 + - my_netgroup3 + state: absent + + # CREATE TEST ITEMS + - name: Get Domain from server name + set_fact: + ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join ('.') }}" + when: ipaserver_domain is not defined + + - name: Ensure netgroup my_netgroup2 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup2 + + - name: Ensure netgroup my_netgroup3 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup3 + + # TESTS + + - name: Ensure netgroup my_netgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup my_netgroup1 is present again + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + register: result + failed_when: result.changed or result.failed + + - name: Ensure netgroup my_netgroup1 is present with description and + nisdomain + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + description: My netgroup 1 + nisdomain: domain.test + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup my_netgroup1 is present with new description + and new nisdomain + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + description: New description + nisdomain: new-domain.test + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup my_netgroup1 is present with description and + nisdomain again + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + description: New description + nisdomain: new-domain.test + register: result + failed_when: result.changed or result.failed + + - name: Ensure 2 netgroups aren't present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - my_netgroup1 + - my_netgroup2 + register: result + failed_when: result.changed or not result.failed or + "Only one netgroup can be added at a time." not in result.msg + + - name: Ensure netgroup my_netgroup1 is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup my_netgroup1 is absent again + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup1 + state: absent + register: result + failed_when: result.changed or result.failed + + # netgroup and hostgroup with the same name are deprecated + - name: Ensure hostgroup my_netgroup2 isn't present + ipahostgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: my_netgroup2 + register: result + failed_when: result.changed or not result.failed or + "Hostgroups and netgroups share a common namespace" not in result.msg + + - name: Ensure netgroups my_netgroup2, my_netgroup3 are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - my_netgroup2 + - my_netgroup3 + state: absent + register: result + failed_when: not result.changed + + always: + # cleanup + - name: Ensure netgroups are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - my_netgroup1 + - my_netgroup2 + - my_netgroup3 + state: absent diff --git a/tests/netgroup/test_netgroup_client_context.yml b/tests/netgroup/test_netgroup_client_context.yml new file mode 100644 index 00000000..f5a4dd3a --- /dev/null +++ b/tests/netgroup/test_netgroup_client_context.yml @@ -0,0 +1,51 @@ +--- +- name: Test netgroup + hosts: ipaclients, ipaserver + become: no + gather_facts: no + + tasks: + - name: Include FreeIPA facts. + include_tasks: ../env_freeipa_facts.yml + + # Test will only be executed if host is not a server. + - name: Execute with server context in the client. + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: server + name: ThisShouldNotWork + register: result + failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*")) + when: ipa_host_is_client + +# Import basic module tests, and execute with ipa_context set to 'client'. +# If ipaclients is set, it will be executed using the client, if not, +# ipaserver will be used. +# +# With this setup, tests can be executed against an IPA client, against +# an IPA server using "client" context, and ensure that tests are executed +# in upstream CI. + +- name: Test netgroup using client context, in client host. + import_playbook: test_netgroup.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test netgroup using client context, in server host. + import_playbook: test_netgroup.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients'] + vars: + ipa_context: client + +- name: Test netgroup with member using client context, in client host. + import_playbook: test_netgroup_member.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test netgroup with member using client context, in server host. + import_playbook: test_netgroup_member.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients'] + vars: + ipa_context: client diff --git a/tests/netgroup/test_netgroup_member.yml b/tests/netgroup/test_netgroup_member.yml new file mode 100644 index 00000000..3fc00246 --- /dev/null +++ b/tests/netgroup/test_netgroup_member.yml @@ -0,0 +1,159 @@ +--- +- name: Netgroup member test + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: no + gather_facts: no + + tasks: + - block: + - name: Get Domain from server name + set_fact: + ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join ('.') }}" + when: ipaserver_domain is not defined + + - name: Set host1_fqdn .. host2_fqdn + set_fact: + host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" + host2_fqdn: "{{ 'host2.' + ipaserver_domain }}" + + # CLEANUP TEST ITEMS + - name: Ensure users user1, user2 are absent + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: user1,user2 + state: absent + + - name: Ensure group group1 is absent + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + state: absent + + - name: Ensure hosts are absent + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + state: absent + + - name: Ensure netgroups TestNetgroup1, admins are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - TestNetgroup1,admins + state: absent + + # CREATE TEST ITEMS + - name: Ensure users user1, user2 are present + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + users: + - name: user1 + first: first1 + last: last1 + - name: user2 + first: first2 + last: last2 + + - name: Ensure groups group1 are present + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + + - name: Ensure hosts "{{ 'host[1..2].' + ipaserver_domain }}" are present + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + hosts: + - name: "{{ host1_fqdn }}" + force: yes + - name: "{{ host2_fqdn }}" + force: yes + + - name: Ensure netgroup admins is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: admins + + # TEST + - name: Ensure netgroup TestNetgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + action: netgroup + description: Description for TestNetgroup1 + nisdomain: "{{ ipaserver_domain }}" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup is present with members + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user1,user2 + group: group1 + host: "{{ host1_fqdn }}" + hostgroup: ipaservers + netgroup: admins + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure netgroup is present with members again (idempotence check) + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user1,user2 + group: group1 + host: + - "{{ host1_fqdn }}" + - host1 + hostgroup: ipaservers + netgroup: admins + action: member + register: result + failed_when: result.changed or result.failed + + always: + # CLEANUP TEST ITEMS + - name: Ensure users user1, user2 are absent + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: user1,user2 + state: absent + + - name: Ensure group group1 is absent + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + state: absent + + - name: Ensure hosts are absent + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + state: absent + + - name: Ensure netgroups TestNetgroup1, admins are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - TestNetgroup1,admins + state: absent diff --git a/tests/netgroup/test_netgroup_member_absent.yml b/tests/netgroup/test_netgroup_member_absent.yml new file mode 100644 index 00000000..3ccd36d5 --- /dev/null +++ b/tests/netgroup/test_netgroup_member_absent.yml @@ -0,0 +1,206 @@ +--- +- name: Netgroup member absent test + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: no + gather_facts: no + + tasks: + - block: + - name: Get Domain from server name + set_fact: + ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join ('.') }}" + when: ipaserver_domain is not defined + + - name: Set host1_fqdn .. host2_fqdn + set_fact: + host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" + host2_fqdn: "{{ 'host2.' + ipaserver_domain }}" + + # CLEANUP TEST ITEMS + - name: Ensure users user1, user2 are absent + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: user1,user2 + state: absent + + - name: Ensure group group1 is absent + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + state: absent + + - name: Ensure hosts are absent + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + state: absent + + - name: Ensure netgroups TestNetgroup1, admins are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - TestNetgroup1,admins + state: absent + + # CREATE TEST ITEMS + - name: Ensure users user1, user2 are present + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + users: + - name: user1 + first: first1 + last: last1 + - name: user2 + first: first2 + last: last2 + + - name: Ensure group group1 is present + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + + - name: Ensure hosts "{{ 'host[1..2].' + ipaserver_domain }}" are present + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + hosts: + - name: "{{ host1_fqdn }}" + force: yes + - name: "{{ host2_fqdn }}" + force: yes + + - name: Ensure netgroup admins is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: admins + + - name: Ensure netgroup TestNetgroup1 is present + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + description: Description for TestNetgroup1 + nisdomain: "{{ ipaserver_domain }}" + + - name: Ensure netgroup is present with members + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user1,user2 + group: group1 + host: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + hostgroup: ipaservers + netgroup: admins + action: member + + # TEST + - name: Ensure members are absent in netgroup + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user1 + group: group1 + host: + - "{{ host1_fqdn }}" + - host1 + hostgroup: ipaservers + netgroup: admins + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure some members are still present in netgroup + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user2 + host: + - "{{ host2_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure host was removed by hostname from netgroup + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + host: + - host2 + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure member user2 presents in netgroup + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: TestNetgroup1 + user: user2 + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure members from netgroups my_netgroup1,my_netgroup2 aren't + absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - my_netgroup1 + - my_netgroup2 + state: absent + action: member + register: result + failed_when: result.changed or not result.failed or + "Members can be removed only from one netgroup at a time." not in + result.msg + + always: + # CLEANUP TEST ITEMS + - name: Ensure users user1, user2 are absent + ipauser: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: user1,user2 + state: absent + + - name: Ensure group group1 is absent + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: group1 + state: absent + + - name: Ensure hosts are absent + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + state: absent + + - name: Ensure netgroups TestNetgroup1, admins are absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + name: + - TestNetgroup1,admins + state: absent diff --git a/tests/netgroup/test_netgroup_member_case_insensitive.yml b/tests/netgroup/test_netgroup_member_case_insensitive.yml new file mode 100644 index 00000000..abd12593 --- /dev/null +++ b/tests/netgroup/test_netgroup_member_case_insensitive.yml @@ -0,0 +1,251 @@ +--- +- name: Test netgroup members should be case insensitive. + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: no + gather_facts: no + + vars: + groups_present: + - eleMENT1 + - Element2 + - eLeMenT3 + - ElemENT4 + + + tasks: + - block: + # SETUP + - name: Get Domain from server name + set_fact: + ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join ('.') }}" + when: ipaserver_domain is not defined + + - name: Ensure test groups exist. + ipagroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + loop: "{{ groups_present }}" + + - name: Ensure test hostgroups exist. + ipahostgroup: + ipaadmin_password: SomeADMINpassword + name: "hostgroup{{ item }}" + loop: "{{ groups_present }}" + + - name: Ensure test netgroups exist. + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "netgroup{{ item }}" + loop: "{{ groups_present }}" + + - name: Ensure test hosts exist. + ipahost: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}.{{ ipaserver_domain }}" + force: yes + loop: "{{ groups_present }}" + + - name: Ensure test users exist. + ipauser: + ipaadmin_password: SomeADMINpassword + name: "user{{ item }}" + first: "{{ item }}" + last: "{{ item }}" + loop: "{{ groups_present }}" + + - name: Ensure netgroups don't exist + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + state: absent + loop: "{{ groups_present }}" + + # TESTS + - name: Start tests. + debug: + msg: "Tests are starting." + + - name: Ensure netgroups exist + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or not result.changed + + - name: Ensure netgroups exist with members + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item }}" + host: "{{ item }}.{{ ipaserver_domain }}" + group: "{{ item }}" + user: "user{{ item }}" + netgroup: "netgroup{{ item }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or not result.changed + + - name: Ensure netgroups exist with members, lowercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | lower }}" + host: "{{ item | lower }}.{{ ipaserver_domain }}" + group: "{{ item | lower }}" + user: "user{{ item | lower }}" + netgroup: "netgroup{{ item | lower }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: Ensure netgroups exist with members, uppercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | upper }}" + host: "{{ item | upper }}.{{ ipaserver_domain }}" + group: "{{ item | upper }}" + user: "user{{ item | upper }}" + netgroup: "netgroup{{ item | upper }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: Ensure netgroup member is absent + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item }}" + host: "{{ item }}.{{ ipaserver_domain }}" + group: "{{ item }}" + user: "user{{ item }}" + netgroup: "netgroup{{ item }}" + action: member + state: absent + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or not result.changed + + - name: Ensure netgroup member is absent, lowercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | lower }}" + host: "{{ item | lower }}.{{ ipaserver_domain }}" + group: "{{ item | lower }}" + user: "user{{ item | lower }}" + netgroup: "netgroup{{ item | lower }}" + action: member + state: absent + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: Ensure netgroup member is absent, uppercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | upper }}" + host: "{{ item | upper }}.{{ ipaserver_domain }}" + group: "{{ item | upper }}" + user: "user{{ item | upper }}" + netgroup: "netgroup{{ item | upper }}" + action: member + state: absent + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: Ensure netgroup member is present, uppercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | upper }}" + host: "{{ item | upper }}.{{ ipaserver_domain }}" + group: "{{ item | upper }}" + user: "user{{ item | upper }}" + netgroup: "netgroup{{ item | upper }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or not result.changed + + - name: Ensure netgroup member is present, lowercase + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item | lower }}" + host: "{{ item | lower }}.{{ ipaserver_domain }}" + group: "{{ item | lower }}" + user: "user{{ item | lower }}" + netgroup: "netgroup{{ item | lower }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: Ensure netgroup member is present, mixed case + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + hostgroup: "hostgroup{{ item }}" + host: "{{ item }}.{{ ipaserver_domain }}" + group: "{{ item }}" + user: "user{{ item }}" + netgroup: "netgroup{{ item }}" + action: member + loop: "{{ groups_present }}" + register: result + failed_when: result.failed or result.changed + + - name: End tests. + debug: + msg: "All tests executed." + + always: + # cleanup + - name: Ensure netgroups do not exist + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + state: absent + loop: "{{ groups_present }}" + + - name: Ensure test groups do not exist. + ipagroup: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}" + state: absent + loop: "{{ groups_present }}" + + - name: Ensure test hostgroups do not exist. + ipahostgroup: + ipaadmin_password: SomeADMINpassword + name: "hostgroup{{ item }}" + state: absent + loop: "{{ groups_present }}" + + - name: Ensure test netgroups do not exist. + ipanetgroup: + ipaadmin_password: SomeADMINpassword + name: "netgroup{{ item }}" + state: absent + loop: "{{ groups_present }}" + + - name: Ensure test hosts do not exist. + ipahost: + ipaadmin_password: SomeADMINpassword + name: "{{ item }}.{{ ipaserver_domain }}" + state: absent + loop: "{{ groups_present }}" + + - name: Ensure test users do not exist. + ipauser: + ipaadmin_password: SomeADMINpassword + name: "user{{ item }}" + state: absent + loop: "{{ groups_present }}" -- GitLab