From e7b1f29518301f931476db06f46c8a22587a901f Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Thu, 24 Aug 2023 13:41:04 +0200 Subject: [PATCH] New idview management module. There is a new idview management module placed in the plugins folder: plugins/modules/ipaidview.py The idview module allows to ensure presence and absence of idviews and idview host members. Here is the documentation for the module: README-idview.md New example playbooks have been added: playbooks/idview/idview-absent.yml playbooks/idview/idview-host-applied.yml playbooks/idview/idview-host-unapplied.yml playbooks/idview/idview-present.yml New tests for the module can be found at: tests/idview/test_idview.yml tests/idview/test_idview_client_context.yml --- README-idview.md | 153 +++++++++ README.md | 2 + playbooks/idview/idview-absent.yml | 11 + playbooks/idview/idview-host-applied.yml | 12 + playbooks/idview/idview-host-unapplied.yml | 13 + playbooks/idview/idview-present.yml | 10 + plugins/modules/ipaidview.py | 352 ++++++++++++++++++++ tests/idview/test_idview.yml | 316 ++++++++++++++++++ tests/idview/test_idview_client_context.yml | 40 +++ 9 files changed, 909 insertions(+) create mode 100644 README-idview.md create mode 100644 playbooks/idview/idview-absent.yml create mode 100644 playbooks/idview/idview-host-applied.yml create mode 100644 playbooks/idview/idview-host-unapplied.yml create mode 100644 playbooks/idview/idview-present.yml create mode 100644 plugins/modules/ipaidview.py create mode 100644 tests/idview/test_idview.yml create mode 100644 tests/idview/test_idview_client_context.yml diff --git a/README-idview.md b/README-idview.md new file mode 100644 index 000000000..b9154683d --- /dev/null +++ b/README-idview.md @@ -0,0 +1,153 @@ +Idview module +============ + +Description +----------- + +The idview module allows to ensure presence and absence of idviews and idview host members. + +Use Cases +--------- + +With ID views it is possible to override user or group attributes for users stored in the LDAP server. For example the login name, home directory, certificate for authentication or SSH keys. An ID view is client-side and specifies new values for user or group attributes and also the client host or hosts on which the values apply. + +The ID view and the applied hosts are managed with idview, the user attributes are managed with idoverrideuser and the group attributes with idoverridegroup. + +Features +-------- + +* Idview management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipaidview 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 idview "test_idview" is present: + +```yaml +--- +- name: Playbook to manage IPA idview. + hosts: ipaserver + become: false + + tasks: + - ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview +``` + + +Example playbook to make sure idview "test_idview" member host "testhost.example.com" is present: + +```yaml +--- +- name: Playbook to manage IPA idview host member. + hosts: ipaserver + become: false + + tasks: + - ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member +``` + + +Example playbook to make sure idview "test_idview" member host "testhost.example.com" is absent: + +```yaml +--- +- name: Playbook to manage IPA idview host member. + hosts: ipaserver + become: false + + tasks: + - ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member + state: absent +``` + + +Example playbook to make sure idview "test_idview" is present with domain_resolution_order for "ad.example.com:ipa.example.com": + +```yaml +--- +- name: Playbook to manage IPA idview host member. + hosts: ipaserver + become: false + + tasks: + - ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + domain_resolution_order: "ad.example.com:ipa.example.com" +``` + + +Example playbook to make sure idview "test_idview" is absent: + +```yaml +--- +- name: Playbook to manage IPA idview. + hosts: ipaserver + become: false + + tasks: + - ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + 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 true. (bool) | no +`name` \| `cn` | The list of idview name strings. | yes +`description` \| `desc` | The description string of the idview. | no +`domain_resolution_order` \| `ipadomainresolutionorder` | Colon-separated list of domains used for short name qualification. | no +`host` \| `hosts` | List of hosts to apply the ID View to. If a host is applied to an idview and is then applied to another idview, it will be removed from the previously applied idview and only be applied to the last idview. | no +`rename` \| `new_name` | Rename the ID view object to the new name string. Only usable with `state: renamed`. | no +`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no +`action` | Work on idview or member level. It can be on of `member` or `idview` and defaults to `idview`. | no +`state` | The state to ensure. It can be one of `present`, `absent` and `renamed`, default: `present`. | no + + +Authors +======= + +Thomas Woerner diff --git a/README.md b/README.md index 6bc009ebe..8af48aa5b 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Features * Modules for host management * Modules for hostgroup management * Modules for idrange management +* Modules for idview management * Modules for location management * Modules for netgroup management * Modules for permission management @@ -451,6 +452,7 @@ Modules in plugin/modules * [ipahost](README-host.md) * [ipahostgroup](README-hostgroup.md) * [idrange](README-idrange.md) +* [idview](README-idview.md) * [ipalocation](README-location.md) * [ipanetgroup](README-netgroup.md) * [ipapermission](README-permission.md) diff --git a/playbooks/idview/idview-absent.yml b/playbooks/idview/idview-absent.yml new file mode 100644 index 000000000..f1b428157 --- /dev/null +++ b/playbooks/idview/idview-absent.yml @@ -0,0 +1,11 @@ +--- +- name: Idview absent example + hosts: ipaserver + become: no + + tasks: + - name: Ensure idview test_idview is absent + ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + state: absent diff --git a/playbooks/idview/idview-host-applied.yml b/playbooks/idview/idview-host-applied.yml new file mode 100644 index 000000000..0fe3d4b50 --- /dev/null +++ b/playbooks/idview/idview-host-applied.yml @@ -0,0 +1,12 @@ +--- +- name: Idview host member applied example + hosts: ipaserver + become: no + + tasks: + - name: Ensure host testhost.example.com is applied to idview test_idview + ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member diff --git a/playbooks/idview/idview-host-unapplied.yml b/playbooks/idview/idview-host-unapplied.yml new file mode 100644 index 000000000..90f051d6a --- /dev/null +++ b/playbooks/idview/idview-host-unapplied.yml @@ -0,0 +1,13 @@ +--- +- name: Idview host member unapplied example + hosts: ipaserver + become: no + + tasks: + - name: Ensure host testhost.example.com is not applied to idview test_idview + ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member + state: absent diff --git a/playbooks/idview/idview-present.yml b/playbooks/idview/idview-present.yml new file mode 100644 index 000000000..82d4ddbfb --- /dev/null +++ b/playbooks/idview/idview-present.yml @@ -0,0 +1,10 @@ +--- +- name: Idview present example + hosts: ipaserver + become: no + + tasks: + - name: Ensure idview test_idview is present + ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview diff --git a/plugins/modules/ipaidview.py b/plugins/modules/ipaidview.py new file mode 100644 index 000000000..b6b7a44dc --- /dev/null +++ b/plugins/modules/ipaidview.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner +# +# Copyright (C) 2023 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 . + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipaidview +short_description: Manage FreeIPA idview +description: Manage FreeIPA idview and idview host members +extends_documentation_fragment: + - ipamodule_base_docs +options: + name: + description: The list of idview name strings. + required: true + type: list + elements: str + aliases: ["cn"] + description: + description: Description + required: False + type: str + aliases: ["desc"] + domain_resolution_order: + description: | + Colon-separated list of domains used for short name qualification + required: False + type: str + aliases: ["ipadomainresolutionorder"] + host: + description: Hosts to apply the ID View to + required: False + type: list + elements: str + aliases: ["hosts"] + rename: + description: Rename the ID view object + required: False + type: str + aliases: ["new_name"] + delete_continue: + description: | + Continuous mode. Don't stop on errors. + Valid only if `state` is `absent`. + required: false + type: bool + aliases: ["continue"] + action: + description: Work on idview or member level. + choices: ["idview", "member"] + default: idview + type: str + state: + description: The state to ensure. + choices: ["present", "absent", "renamed"] + default: present + type: str +author: + - Thomas Woerner (@t-woerner) +""" + +EXAMPLES = """ +# Ensure idview test_idview is present +- ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + +# name: Ensure host testhost.example.com is applied to idview test_idview +- ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member + +# Ensure host testhost.example.com is not applied to idview test_idview +- ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + host: testhost.example.com + action: member + state: absent + +# Ensure idview "test_idview" is present with domain_resolution_order for +# "ad.example.com:ipa.example.com" +- ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + domain_resolution_order: "ad.example.com:ipa.example.com" + +# Ensure idview test_idview is absent +- ipaidview: + ipaadmin_password: SomeADMINpassword + name: test_idview + 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 +from ansible.module_utils import six + +if six.PY3: + unicode = str + + +def find_idview(module, name): + """Find if a idview with the given name already exist.""" + try: + _result = module.ipa_command("idview_show", name, {"all": True}) + except Exception: # pylint: disable=broad-except + # An exception is raised if idview name is not found. + return None + else: + return _result["result"] + + +def gen_args(description, domain_resolution_order): + _args = {} + if description is not None: + _args["description"] = description + if domain_resolution_order is not None: + _args["ipadomainresolutionorder"] = domain_resolution_order + return _args + + +def gen_member_args(host): + _args = {} + if host is not None: + _args["host"] = host + return _args + + +def main(): + ansible_module = IPAAnsibleModule( + argument_spec=dict( + # general + name=dict(type="list", elements="str", required=True, + aliases=["cn"]), + # present + description=dict(type="str", required=False, aliases=["desc"]), + domain_resolution_order=dict(type="str", required=False, + aliases=["ipadomainresolutionorder"]), + host=dict(type="list", elements="str", required=False, + aliases=["hosts"], default=None), + rename=dict(type="str", required=False, aliases=["new_name"]), + delete_continue=dict(type="bool", required=False, + aliases=['continue'], default=None), + # action + action=dict(type="str", default="idview", + choices=["member", "idview"]), + # state + state=dict(type="str", default="present", + choices=["present", "absent", "renamed"]), + ), + supports_check_mode=True, + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + names = ansible_module.params_get("name") + + # present + description = ansible_module.params_get("description") + domain_resolution_order = ansible_module.params_get( + "domain_resolution_order") + host = ansible_module.params_get("host") + rename = ansible_module.params_get("rename") + + action = ansible_module.params_get("action") + + # absent + delete_continue = ansible_module.params_get("delete_continue") + + # state + state = ansible_module.params_get("state") + + # Check parameters + + invalid = [] + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one idview can be added at a time.") + invalid = ["delete_continue", "rename"] + if action == "member": + invalid += ["description", "domain_resolution_order"] + + if state == "renamed": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one idoverridegroup can be renamed at a time.") + if not rename: + ansible_module.fail_json( + msg="Rename is required for state: renamed.") + if action == "member": + ansible_module.fail_json( + msg="Action member can not be used with state: renamed.") + invalid = ["description", "domain_resolution_order", "host", + "delete_continue"] + + if state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["description", "domain_resolution_order", "rename"] + if action == "idview": + invalid += ["host"] + + ansible_module.params_fail_used_invalid(invalid, state, action) + + # Init + + changed = False + exit_args = {} + + # Connect to IPA API + with ansible_module.ipa_connect(): + + commands = [] + for name in names: + # Make sure idview exists + res_find = find_idview(ansible_module, name) + + # add/del lists + host_add, host_del = [], [] + + # Create command + if state == "present": + + # Generate args + args = gen_args(description, domain_resolution_order) + + if action == "idview": + # Found the idview + 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, "idview_mod", args]) + else: + commands.append([name, "idview_add", args]) + res_find = {} + + member_args = gen_member_args(host) + if not compare_args_ipa(ansible_module, member_args, + res_find): + + # Generate addition and removal lists + host_add, host_del = gen_add_del_lists( + host, res_find.get("appliedtohosts")) + + elif action == "member": + if res_find is None: + ansible_module.fail_json( + msg="No idview '%s'" % name) + + # Reduce add lists for host + # to new entries only that are not in res_find. + if host is not None: + host_add = gen_add_list( + host, res_find.get("appliedtohosts")) + + elif state == "absent": + if action == "idview": + if res_find is not None: + commands.append( + [name, "idview_del", + {"continue": delete_continue or False}] + ) + + elif action == "member": + if res_find is None: + ansible_module.fail_json( + msg="No idview '%s'" % name) + + # Reduce del lists of member_host + # to the entries only that are in res_find. + if host is not None: + host_del = gen_intersection_list( + host, res_find.get("appliedtohosts")) + + elif state == "renamed": + if res_find is None: + ansible_module.fail_json(msg="No idview '%s'" % name) + else: + commands.append([name, 'idview_mod', {"rename": rename}]) + + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Member management + + # Add members + if host_add: + commands.append([name, "idview_apply", {"host": host_add}]) + + # Remove members + if host_del: + # idview_unapply does not have the idview name (cn) as an arg. + # It is removing the host from any idview it is applied to. + # But as we create the intersection with the list of hosts of + # the idview, we emulate the correct behaviour. But this means + # that there is no general idview_unapply like in the cli. + commands.append([None, "idview_unapply", {"host": host_del}]) + + # Execute commands + + changed = ansible_module.execute_ipa_commands(commands) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/idview/test_idview.yml b/tests/idview/test_idview.yml new file mode 100644 index 000000000..8e70dc6a3 --- /dev/null +++ b/tests/idview/test_idview.yml @@ -0,0 +1,316 @@ +--- +- name: Test idview + hosts: "{{ ipa_test_host | default('ipaserver') }}" + # It is normally not needed to set "become" to "true" for a module test. + # Only set it to true if it is needed to execute commands as root. + become: false + # Enable "gather_facts" only if "ansible_facts" variable needs to be used. + gather_facts: false + module_defaults: + ipahost: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + ipaidview: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + + tasks: + + - name: Get Domain from server name + ansible.builtin.set_fact: + ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join('.') }}" + when: ipaserver_domain is not defined + + - name: Set host1_fqdn .. host2_fqdn + ansible.builtin.set_fact: + host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" + host2_fqdn: "{{ 'host2.' + ipaserver_domain }}" + + # CLEANUP TEST ITEMS + + - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" are absent + ipahost: + hosts: + - name: "{{ host1_fqdn }}" + - name: "{{ host2_fqdn }}" + state: absent + + - name: Ensure idview test1_idview, test2_idview and renamed_idview are absent + ipaidview: + name: + - test1_idview + - test2_idview + - renamed_idview + state: absent + + # CREATE TEST ITEMS + + - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" are present + ipahost: + hosts: + - name: "{{ host1_fqdn }}" + force: true + - name: "{{ host2_fqdn }}" + force: true + register: result + failed_when: not result.changed or result.failed + + # TESTS + + - name: Ensure idview test1_idview is present + ipaidview: + name: test1_idview + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present again + ipaidview: + name: test1_idview + # Add needed parameters here + register: result + failed_when: result.changed or result.failed + + - name: Ensure idview test2_idview is present + ipaidview: + name: test2_idview + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test2_idview is present again + ipaidview: + name: test2_idview + # Add needed parameters here + register: result + failed_when: result.changed or result.failed + + - name: Rename test1_idview to renamed_idview + ipaidview: + name: test1_idview + rename: renamed_idview + state: renamed + register: result + failed_when: not result.changed or result.failed + + # This task will fail as there is no idview to be renamed + - name: Rename test1_idview to renamed_idview, again + ipaidview: + name: test1_idview + rename: renamed_idview + state: renamed + register: result + failed_when: result.changed or (not result.failed and "No idview 'test1_idview'" not in result.msg) + + - name: Rename renamed_idview back to to test1_idview + ipaidview: + name: renamed_idview + rename: test1_idview + state: renamed + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present with description + ipaidview: + name: test1_idview + description: "Test IDView" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present with description, again + ipaidview: + name: test1_idview + description: "Test IDView" + register: result + failed_when: result.changed or result.failed + + - name: Ensure idview test1_idview is present with empty description + ipaidview: + name: test1_idview + description: "" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present with empty description, again + ipaidview: + name: test1_idview + description: "" + register: result + failed_when: result.changed or result.failed + + - name: Ensure idview test1_idview is present with domain reolution order "{{ ipaserver_domain }}" + ipaidview: + name: test1_idview + domain_resolution_order: "{{ ipaserver_domain }}" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present with domain reolution order "{{ ipaserver_domain }}", again + ipaidview: + name: test1_idview + domain_resolution_order: "{{ ipaserver_domain }}" + register: result + failed_when: result.changed or result.failed + + - name: Ensure idview test1_idview is present with empty domain reolution order + ipaidview: + name: test1_idview + domain_resolution_order: "" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure idview test1_idview is present with empty domain reolution order, again + ipaidview: + name: test1_idview + domain_resolution_order: "" + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test1_idview + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test1_idview, again + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host2_fqdn }}" is applied to idview test1_idview + ipaidview: + name: test1_idview + host: + - "{{ host2_fqdn }}" + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure host "{{ host2_fqdn }}" is applied to idview test1_idview, again + ipaidview: + name: test1_idview + host: + - "{{ host2_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure hosts "{{ host1_fqdn }}" and "{{ host1_fqdn }}" are applied to idview test1_idview + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure hosts "{{ host1_fqdn }}" and "{{ host1_fqdn }}" are not applied to idview test1_idview + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure hosts "{{ host1_fqdn }}" and "{{ host1_fqdn }}" are not applied to idview test1_idview, again + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + action: member + state: absent + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test1_idview + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test1_idview, again + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test2_idview + ipaidview: + name: test2_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: not result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is applied to idview test2_idview, again + ipaidview: + name: test2_idview + host: + - "{{ host1_fqdn }}" + action: member + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is not applied to idview test1_idview anymore + ipaidview: + name: test1_idview + host: + - "{{ host1_fqdn }}" + action: member + state: absent + register: result + failed_when: result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is not applied to idview test2_idview + ipaidview: + name: test2_idview + host: + - "{{ host1_fqdn }}" + action: member + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure host "{{ host1_fqdn }}" is not applied to idview test2_idview, again + ipaidview: + name: test2_idview + host: + - "{{ host1_fqdn }}" + action: member + state: absent + register: result + failed_when: result.changed or result.failed + + # CLEANUP TEST ITEMS + + - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" absent + ipahost: + hosts: + - name: "{{ host1_fqdn }}" + - name: "{{ host2_fqdn }}" + state: absent + + - name: Ensure idview test1_idview, test2_idview and renamed_idview are absent + ipaidview: + name: + - test1_idview + - test2_idview + - renamed_idview + state: absent diff --git a/tests/idview/test_idview_client_context.yml b/tests/idview/test_idview_client_context.yml new file mode 100644 index 000000000..3a576f1b6 --- /dev/null +++ b/tests/idview/test_idview_client_context.yml @@ -0,0 +1,40 @@ +--- +- name: Test idview + hosts: ipaclients, ipaserver + # It is normally not needed to set "become" to "true" for a module test. + # Only set it to true if it is needed to execute commands as root. + become: false + # Enable "gather_facts" only if "ansible_facts" variable needs to be used. + gather_facts: false + + tasks: + - name: Include FreeIPA facts. + ansible.builtin.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. + ipaidview: + 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 idview using client context, in client host. + import_playbook: test_idview.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test idview using client context, in server host. + import_playbook: test_idview.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients']