From 6f5bb9eebf94bc04e2e2175415574ee94977edb4 Mon Sep 17 00:00:00 2001 From: Thomas Woerner <twoerner@redhat.com> Date: Wed, 13 Sep 2023 17:53:00 +0200 Subject: [PATCH] New idoverridegroup management module. There is a new idoverridegroup management module placed in the plugins folder: plugins/modules/ipaidoverridegroup.py The idoverridegroup module allows to ensure presence and absence of idoverrides for groups. Here is the documentation for the module: README-idoverridegroup.md New example playbooks have been added: playbooks/idoverridegroup/idoverridegroup-absent.yml playbooks/idoverridegroup/idoverridegroup-present.yml New tests for the module can be found at: tests/idoverridegroup/test_idoverridegroup.yml tests/idoverridegroup/test_idoverridegroup_client_context.yml --- README-idoverridegroup.md | 233 ++++++++++++ README.md | 2 + .../idoverridegroup-absent.yml | 13 + .../idoverridegroup-present.yml | 11 + plugins/modules/ipaidoverridegroup.py | 354 ++++++++++++++++++ .../idoverridegroup/test_idoverridegroup.yml | 205 ++++++++++ .../test_idoverridegroup_client_context.yml | 40 ++ 7 files changed, 858 insertions(+) create mode 100644 README-idoverridegroup.md create mode 100644 playbooks/idoverridegroup/idoverridegroup-absent.yml create mode 100644 playbooks/idoverridegroup/idoverridegroup-present.yml create mode 100644 plugins/modules/ipaidoverridegroup.py create mode 100644 tests/idoverridegroup/test_idoverridegroup.yml create mode 100644 tests/idoverridegroup/test_idoverridegroup_client_context.yml diff --git a/README-idoverridegroup.md b/README-idoverridegroup.md new file mode 100644 index 00000000..86b7bdee --- /dev/null +++ b/README-idoverridegroup.md @@ -0,0 +1,233 @@ +Idoverridegroup module +============ + +Description +----------- + +The idoverridegroup module allows to ensure presence and absence of idoverridegroups and idoverridegroup members. + + +Use Cases +--------- + +With idoverridegroup it is possible to manage group attributes within ID views. These attributes are for example the group name or gid. + + +Features +-------- + +* Idoverridegroup management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipaidoverridegroup module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.13 + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + + +Example playbook to make sure test group test_group is present in idview test_idview + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview. + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group +``` + + +Example playbook to make sure test group test_group is present in idview test_idview with description + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview with description + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + description: "test_group description" +``` + + +Example playbook to make sure test group test_group is present in idview test_idview without description + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview without description + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + description: "" +``` + + +Example playbook to make sure test group test_group is present in idview test_idview with internal name test_123_group + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview with internal name test_123_group + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + name: test_123_group +``` + + +Example playbook to make sure test group test_group is present in idview test_idview without internal name + +```yaml +--- +- name: Playbook to manage idoverridegroup +- name: Ensure test group test_group is present in idview test_idview without internal name + hosts: ipaserver + become: false + + tasks: + - ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + name: "" +``` + + +Example playbook to make sure test group test_group is present in idview test_idview with gid 20001 + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview with gid 20001 + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + gid: 20001 +``` + + +Example playbook to make sure test group test_group is present in idview test_idview without gid + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview without gid + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + gid: "" +``` + + +Example playbook to make sure test group test_group is present in idview test_idview with enabling falling back to AD DC LDAP when resolving AD trusted objects. (For two-way trusts only.) + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is present in idview test_idview with fallback_to_ldap enabled + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + fallback_to_ldap: true +``` + + +Example playbook to make sure test group test_group is absent in idview test_idview + +```yaml +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: false + + tasks: + - name: Ensure test group test_group is absent in idview test_idview + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + continue: true + 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 +`idview` \| `idviewcn` | The doverridegroup idview string. | yes +`anchor` \| `ipaanchoruuid` | The list of anchors to override. | yes +`description` \| `desc` | Description | no +`name` \| `group_name` \| `cn` | The group. | no +`gid` \| `gidnumber` | Group ID Number (int or "") | no +`fallback_to_ldap` | Allow falling back to AD DC LDAP when resolving AD trusted objects. For two-way trusts only. | no +`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no +`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no + + +Authors +======= + +Thomas Woerner diff --git a/README.md b/README.md index fd87aa05..a166b0c4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Features * Modules for hbacsvcgroup management * Modules for host management * Modules for hostgroup management +* Modules for idoverridegroup management * Modules for idoverrideuser management * Modules for idrange management * Modules for idview management @@ -442,6 +443,7 @@ Modules in plugin/modules * [ipahbacsvcgroup](README-hbacsvcgroup.md) * [ipahost](README-host.md) * [ipahostgroup](README-hostgroup.md) +* [idoverridegroup](README-idoverridegroup.md) * [idoverrideuser](README-idoverrideuser.md) * [idrange](README-idrange.md) * [idview](README-idview.md) diff --git a/playbooks/idoverridegroup/idoverridegroup-absent.yml b/playbooks/idoverridegroup/idoverridegroup-absent.yml new file mode 100644 index 00000000..559d055b --- /dev/null +++ b/playbooks/idoverridegroup/idoverridegroup-absent.yml @@ -0,0 +1,13 @@ +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: no + + tasks: + - name: Ensure idoverridegroup test_group is absent in idview test_idview. + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + continue: true + state: absent diff --git a/playbooks/idoverridegroup/idoverridegroup-present.yml b/playbooks/idoverridegroup/idoverridegroup-present.yml new file mode 100644 index 00000000..52fed6f0 --- /dev/null +++ b/playbooks/idoverridegroup/idoverridegroup-present.yml @@ -0,0 +1,11 @@ +--- +- name: Playbook to manage idoverridegroup + hosts: ipaserver + become: no + + tasks: + - name: Ensure idoverridegroup test_group is present in idview test_idview. + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group diff --git a/plugins/modules/ipaidoverridegroup.py b/plugins/modules/ipaidoverridegroup.py new file mode 100644 index 00000000..8ddfe324 --- /dev/null +++ b/plugins/modules/ipaidoverridegroup.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# 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, exither 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"], +} + +# No rename support: 'ID overrides cannot be renamed' +# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback + +DOCUMENTATION = """ +--- +module: ipaidoverridegroup +short_description: Manage FreeIPA idoverridegroup +description: Manage FreeIPA idoverridegroups +extends_documentation_fragment: + - ipamodule_base_docs +options: + idview: + description: The idoverridegroup idview string. + type: str + required: true + aliases: ["idviewcn"] + anchor: + description: The list of anchors to override + type: list + elements: str + required: true + aliases: ["ipaanchoruuid"] + description: + description: Description + type: str + required: False + aliases: ["desc"] + name: + description: Group name + type: str + required: False + aliases: ["group_name", "cn"] + gid: + description: Group ID Number (int or "") + type: str + required: False + aliases: ["gidnumber"] + fallback_to_ldap: + description: | + Allow falling back to AD DC LDAP when resolving AD trusted objects. + For two-way trusts only. + required: False + type: bool + delete_continue: + description: | + Continuous mode. Don't stop on errors. + Valid only if `state` is `absent`. + required: false + type: bool + aliases: ["continue"] + state: + description: The state to ensure. + choices: ["present", "absent"] + default: present + type: str +author: + - Thomas Woerner (@t-woerner) +""" + +EXAMPLES = """ +# Ensure test group test_group is present in idview test_idview +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + +# Ensure test group test_group is present in idview test_idview with +# description +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + description: "test_group description" + +# Ensure test group test_group is present in idview test_idview without +# description +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + description: "" + +# Ensure test group test_group is present in idview test_idview with internal +# name test_123_group +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + name: test_123_group + +# Ensure test group test_group is present in idview test_idview without +# internal name +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + name: "" + +# Ensure test group test_group is present in idview test_idview with gid 20001 +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + gid: 20001 + +# Ensure test group test_group is present in idview test_idview without gid +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + gid: "" + +# Ensure test group test_group is absent in idview test_idview +- ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + idview: test_idview + anchor: test_group + continue: true + state: absent +""" + +RETURN = """ +""" + + +from ansible.module_utils.ansible_freeipa_module import \ + IPAAnsibleModule, compare_args_ipa +from ansible.module_utils import six + +if six.PY3: + unicode = str + + +def find_idoverridegroup(module, idview, anchor): + """Find if a idoverridegroup with the given name already exist.""" + try: + _result = module.ipa_command("idoverridegroup_show", idview, + {"ipaanchoruuid": anchor, + "all": True}) + except Exception: # pylint: disable=broad-except + # An exception is raised if idoverridegroup anchor is not found. + return None + return _result["result"] + + +def gen_args(anchor, description, name, gid): + # fallback_to_ldap is only a runtime tuning parameter + _args = {} + if anchor is not None: + _args["ipaanchoruuid"] = anchor + if description is not None: + _args["description"] = description + if name is not None: + _args["cn"] = name + if gid is not None: + _args["gidnumber"] = gid + return _args + + +def gen_args_runtime(fallback_to_ldap): + _args = {} + if fallback_to_ldap is not None: + _args["fallback_to_ldap"] = fallback_to_ldap + return _args + + +def merge_dicts(dict1, dict2): + ret = dict1.copy() + ret.update(dict2) + return ret + + +def main(): + ansible_module = IPAAnsibleModule( + argument_spec=dict( + # general + idview=dict(type="str", required=True, aliases=["idviewcn"]), + anchor=dict(type="list", elements="str", required=True, + aliases=["ipaanchoruuid"]), + + # present + description=dict(type="str", required=False, aliases=["desc"]), + name=dict(type="str", required=False, + aliases=["group_name", "cn"]), + gid=dict(type="str", required=False, aliases=["gidnumber"]), + + # runtime flags + fallback_to_ldap=dict(type="bool", required=False), + + # absent + delete_continue=dict(type="bool", required=False, + aliases=['continue'], default=None), + + # No rename support: 'ID overrides cannot be renamed' + # ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback + + # state + state=dict(type="str", default="present", + choices=["present", "absent"]), + ), + supports_check_mode=True, + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + idview = ansible_module.params_get("idview") + anchors = ansible_module.params_get("anchor") + + # present + description = ansible_module.params_get("description") + name = ansible_module.params_get("name") + gid = ansible_module.params_get("gid") + + # runtime flags + fallback_to_ldap = ansible_module.params_get("fallback_to_ldap") + + # absent + delete_continue = ansible_module.params_get("delete_continue") + + # state + state = ansible_module.params_get("state") + + # Check parameters + + invalid = [] + + if state == "present": + if len(anchors) != 1: + ansible_module.fail_json( + msg="Only one idoverridegroup can be added at a time.") + invalid = ["delete_continue"] + + if state == "absent": + if len(anchors) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["description", "name", "gid"] + + ansible_module.params_fail_used_invalid(invalid, state) + + # Ensure parameter values are valid and have proper type. + def int_or_empty_param(value, param): + if value is not None and value != "": + try: + value = int(value) + except ValueError: + ansible_module.fail_json( + msg="Invalid value '%s' for argument '%s'" % (value, param) + ) + return value + + gid = int_or_empty_param(gid, "gid") + + # Init + + changed = False + exit_args = {} + + # Connect to IPA API + with ansible_module.ipa_connect(): + + runtime_args = gen_args_runtime(fallback_to_ldap) + commands = [] + for anchor in anchors: + # Make sure idoverridegroup exists + res_find = find_idoverridegroup(ansible_module, idview, anchor) + + # Create command + if state == "present": + + # Generate args + args = gen_args(anchor, description, name, gid) + # fallback_to_ldap is only a runtime tuning parameter + all_args = merge_dicts(args, runtime_args) + + # Found the idoverridegroup + if res_find is not None: + # For idempotency: Remove empty sshpubkey list if + # there are no sshpubkey in the found entry. + if "ipasshpubkey" in args and \ + len(args["ipasshpubkey"]) < 1 and \ + "ipasshpubkey" not in res_find: + del args["ipasshpubkey"] + # 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([idview, "idoverridegroup_mod", + all_args]) + else: + commands.append([idview, "idoverridegroup_add", + all_args]) + + elif state == "absent": + if res_find is not None: + commands.append( + [idview, "idoverridegroup_del", + merge_dicts( + { + "ipaanchoruuid": anchor, + "continue": delete_continue + }, + runtime_args + )] + ) + + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # 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/idoverridegroup/test_idoverridegroup.yml b/tests/idoverridegroup/test_idoverridegroup.yml new file mode 100644 index 00000000..d19520e6 --- /dev/null +++ b/tests/idoverridegroup/test_idoverridegroup.yml @@ -0,0 +1,205 @@ +--- +- name: Test idoverridegroup + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: false + gather_facts: false + module_defaults: + ipaidoverridegroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + ipaidview: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + ipagroup: + ipaadmin_password: SomeADMINpassword + ipaapi_context: "{{ ipa_context | default(omit) }}" + + tasks: + + # CLEANUP TEST ITEMS + + - name: Ensure test group test_group does not exist + ipagroup: + name: test_group + state: absent + + - name: Ensure test group test_group is absent in idview test_idview + ipaidoverridegroup: + idview: test_idview + anchor: test_group + continue: true + state: absent + + - name: Ensure test idview test_idview does not exist + ipaidview: + name: test_idview + state: absent + + # CREATE TEST ITEMS + + - name: Ensure test group test_group exists + ipagroup: + name: test_group + + - name: Ensure test idview test_idview exists + ipaidview: + name: test_idview + + # TESTS + + - name: Ensure test group test_group is present in idview test_idview + ipaidoverridegroup: + idview: test_idview + anchor: test_group + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + register: result + failed_when: result.changed or result.failed + + # description + + - name: Ensure test group test_group is present in idview test_idview with description + ipaidoverridegroup: + idview: test_idview + anchor: test_group + description: "test_group description" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview with description, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + description: "test_group description" + register: result + failed_when: result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without description + ipaidoverridegroup: + idview: test_idview + anchor: test_group + description: "" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without description, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + description: "" + register: result + failed_when: result.changed or result.failed + + # name + + - name: Ensure test group test_group is present in idview test_idview with internal name test_123_group + ipaidoverridegroup: + idview: test_idview + anchor: test_group + name: test_123_group + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview with internal name test_123_group, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + name: test_123_group + register: result + failed_when: result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without internal name + ipaidoverridegroup: + idview: test_idview + anchor: test_group + name: "" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without internal name, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + name: "" + register: result + failed_when: result.changed or result.failed + + # gid + + - name: Ensure test group test_group is present in idview test_idview with gid 20001 + ipaidoverridegroup: + idview: test_idview + anchor: test_group + gid: 20001 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview with gid 20001, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + gid: 20001 + register: result + failed_when: result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without gid + ipaidoverridegroup: + idview: test_idview + anchor: test_group + gid: "" + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is present in idview test_idview without gid, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + gid: "" + register: result + failed_when: result.changed or result.failed + + # no fallback_to_ldap tests + + # absent + + - name: Ensure test group test_group is absent in idview test_idview + ipaidoverridegroup: + idview: test_idview + anchor: test_group + continue: true + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure test group test_group is absent in idview test_idview, again + ipaidoverridegroup: + idview: test_idview + anchor: test_group + continue: true + state: absent + register: result + failed_when: result.changed or result.failed + + # CLEANUP TEST ITEMS + + - name: Ensure test group test_group does not exist + ipagroup: + name: test_group + state: absent + + - name: Ensure test group test_group is absent in idview test_idview + ipaidoverridegroup: + idview: test_idview + anchor: test_group + continue: true + state: absent + + - name: Ensure test idview test_idview does not exist + ipaidview: + name: test_idview + state: absent diff --git a/tests/idoverridegroup/test_idoverridegroup_client_context.yml b/tests/idoverridegroup/test_idoverridegroup_client_context.yml new file mode 100644 index 00000000..92a62fad --- /dev/null +++ b/tests/idoverridegroup/test_idoverridegroup_client_context.yml @@ -0,0 +1,40 @@ +--- +- name: Test idoverridegroup + 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. + ipaidoverridegroup: + 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 idoverridegroup using client context, in client host. + import_playbook: test_idoverridegroup.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test idoverridegroup using client context, in server host. + import_playbook: test_idoverridegroup.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients'] -- GitLab