diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 02e02630e2d2106ea4b9cdcbfd07745fa7379ac4..32098b351a05c16fb0456481730d19a2cba93f3d 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -22,780 +22,776 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-import sys
-import operator
-import os
-import uuid
-import tempfile
-import shutil
-import netaddr
-import gssapi
-from datetime import datetime
-from pprint import pformat
-
-try:
-    from packaging import version
-except ImportError:
-    # If `packaging` not found, split version string for creating version
-    # object. Although it is not PEP 440 compliant, it will work for stable
-    # FreeIPA releases.
-    import re
-
-    class version:
-        @staticmethod
-        def parse(version_str):
-            """
-            Split a version string A.B.C, into a tuple.
-
-            This will not work for `rc`, `dev` or similar version string.
-            """
-            return tuple(re.split("[-_\.]", version_str))  # noqa: W605
-
-from ipalib import api
-from ipalib import errors as ipalib_errors  # noqa
-from ipalib.config import Env
-from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
-
-try:
-    from ipalib.install.kinit import kinit_password, kinit_keytab
-except ImportError:
-    from ipapython.ipautil import kinit_password, kinit_keytab
-from ipapython.ipautil import run
-from ipapython.dn import DN
-from ipapython.version import VERSION
-from ipaplatform.paths import paths
-from ipalib.krb_utils import get_credentials_if_valid
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_text
-from ansible.module_utils.common.text.converters import jsonify
-
-try:
-    from ipalib.x509 import Encoding
-except ImportError:
-    from cryptography.hazmat.primitives.serialization import Encoding
-
-try:
-    from ipalib.x509 import load_pem_x509_certificate
-except ImportError:
-    from ipalib.x509 import load_certificate
-    load_pem_x509_certificate = None
-
-import socket
-import base64
-import six
-
-try:
-    from collections.abc import Mapping  # noqa
-except ImportError:
-    from collections import Mapping  # noqa
-
-
-if six.PY3:
-    unicode = str
-
-# ansible-freeipa requires locale to be C, IPA requires utf-8.
-os.environ["LANGUAGE"] = "C"
-
-
-def valid_creds(module, principal):  # noqa
-    """Get valid credentials matching the princial, try GSSAPI first."""
-    if "KRB5CCNAME" in os.environ:
-        ccache = os.environ["KRB5CCNAME"]
-        module.debug('KRB5CCNAME set to %s' % ccache)
-
-        try:
-            cred = gssapi.Credentials(usage='initiate',
-                                      store={'ccache': ccache})
-        except gssapi.raw.misc.GSSError as e:
-            module.fail_json(msg='Failed to find default ccache: %s' % e)
-        else:
-            module.debug("Using principal %s" % str(cred.name))
-            return True
-
-    elif "KRB5_CLIENT_KTNAME" in os.environ:
-        keytab = os.environ.get('KRB5_CLIENT_KTNAME', None)
-        module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab)
-
-        ccache_name = "MEMORY:%s" % str(uuid.uuid4())
-        os.environ["KRB5CCNAME"] = ccache_name
+__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
+           "DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
+           "kinit_password", "kinit_keytab", "run", "DN", "VERSION",
+           "paths", "get_credentials_if_valid", "Encoding",
+           "load_pem_x509_certificate"]
 
-        try:
-            cred = kinit_keytab(principal, keytab, ccache_name)
-        except gssapi.raw.misc.GSSError as e:
-            module.fail_json(msg='Kerberos authentication failed : %s' % e)
-        else:
-            module.debug("Using principal %s" % str(cred.name))
-            return True
+import sys
 
-    creds = get_credentials_if_valid()
-    if creds and \
-       creds.lifetime > 0 and \
-       "%s@" % principal in creds.name.display_as(creds.name.name_type):
-        return True
-    return False
+# HACK: workaround for Ansible 2.9
+# https://github.com/ansible/ansible/issues/68361
+if 'ansible.executor' in sys.modules:
+    for attr in __all__:
+        setattr(sys.modules[__name__], attr, None)
+else:
+    import operator
+    import os
+    import uuid
+    import tempfile
+    import shutil
+    import netaddr
+    import gssapi
+    from datetime import datetime
+    from pprint import pformat
+
+    # ansible-freeipa requires locale to be C, IPA requires utf-8.
+    os.environ["LANGUAGE"] = "C"
 
+    try:
+        from packaging import version
+    except ImportError:
+        # If `packaging` not found, split version string for creating version
+        # object. Although it is not PEP 440 compliant, it will work for stable
+        # FreeIPA releases.
+        import re
+
+        class version:
+            @staticmethod
+            def parse(version_str):
+                """
+                Split a version string A.B.C, into a tuple.
+
+                This will not work for `rc`, `dev` or similar version string.
+                """
+                return tuple(re.split("[-_\.]", version_str))  # noqa: W605
+
+    from ipalib import api
+    from ipalib import errors as ipalib_errors  # noqa
+    from ipalib.config import Env
+    from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
 
-def temp_kinit(principal, password):
-    """Kinit with password using a temporary ccache."""
-    if not password:
-        raise RuntimeError("The password is not set")
-    if not principal:
-        principal = "admin"
+    try:
+        from ipalib.install.kinit import kinit_password, kinit_keytab
+    except ImportError:
+        from ipapython.ipautil import kinit_password, kinit_keytab
+    from ipapython.ipautil import run
+    from ipapython.dn import DN
+    from ipapython.version import VERSION
+    from ipaplatform.paths import paths
+    from ipalib.krb_utils import get_credentials_if_valid
+    from ansible.module_utils.basic import AnsibleModule
+    from ansible.module_utils._text import to_text
+    from ansible.module_utils.common.text.converters import jsonify
 
-    ccache_dir = tempfile.mkdtemp(prefix='krbcc')
-    ccache_name = os.path.join(ccache_dir, 'ccache')
+    try:
+        from ipalib.x509 import Encoding
+    except ImportError:
+        from cryptography.hazmat.primitives.serialization import Encoding
 
     try:
-        kinit_password(principal, password, ccache_name)
-    except RuntimeError as e:
-        raise RuntimeError("Kerberos authentication failed: {}".format(e))
+        from ipalib.x509 import load_pem_x509_certificate
+    except ImportError:
+        from ipalib.x509 import load_certificate
+        load_pem_x509_certificate = None
 
-    os.environ["KRB5CCNAME"] = ccache_name
-    return ccache_dir, ccache_name
+    import socket
+    import base64
+    import six
 
+    try:
+        from collections.abc import Mapping  # noqa
+    except ImportError:
+        from collections import Mapping  # noqa
 
-def temp_kdestroy(ccache_dir, ccache_name):
-    """Destroy temporary ticket and remove temporary ccache."""
-    if ccache_name is not None:
-        run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
-        del os.environ['KRB5CCNAME']
-    if ccache_dir is not None:
-        shutil.rmtree(ccache_dir, ignore_errors=True)
+    if six.PY3:
+        unicode = str
 
+    def valid_creds(module, principal):  # noqa
+        """Get valid credentials matching the princial, try GSSAPI first."""
+        if "KRB5CCNAME" in os.environ:
+            ccache = os.environ["KRB5CCNAME"]
+            module.debug('KRB5CCNAME set to %s' % ccache)
 
-def api_connect(context=None):
-    """
-    Initialize IPA API with the provided context.
+            try:
+                cred = gssapi.Credentials(usage='initiate',
+                                          store={'ccache': ccache})
+            except gssapi.raw.misc.GSSError as e:
+                module.fail_json(msg='Failed to find default ccache: %s' % e)
+            else:
+                module.debug("Using principal %s" % str(cred.name))
+                return True
 
-    `context` can be any of:
-        * `server` (default)
-        * `ansible-freeipa`
-        * `cli_installer`
-    """
-    env = Env()
-    env._bootstrap()
-    env._finalize_core(**dict(DEFAULT_CONFIG))
+        elif "KRB5_CLIENT_KTNAME" in os.environ:
+            keytab = os.environ.get('KRB5_CLIENT_KTNAME', None)
+            module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab)
 
-    # available contexts are 'server', 'ansible-freeipa' and 'cli_installer'
-    if context is None:
-        context = 'server'
+            ccache_name = "MEMORY:%s" % str(uuid.uuid4())
+            os.environ["KRB5CCNAME"] = ccache_name
 
-    api.bootstrap(context=context, debug=env.debug, log=None)
-    api.finalize()
+            try:
+                cred = kinit_keytab(principal, keytab, ccache_name)
+            except gssapi.raw.misc.GSSError as e:
+                module.fail_json(msg='Kerberos authentication failed : %s' % e)
+            else:
+                module.debug("Using principal %s" % str(cred.name))
+                return True
 
-    if api.env.in_server:
-        backend = api.Backend.ldap2
-    else:
-        backend = api.Backend.rpcclient
+        creds = get_credentials_if_valid()
+        if creds and \
+           creds.lifetime > 0 and \
+           "%s@" % principal in creds.name.display_as(creds.name.name_type):
+            return True
+        return False
 
-    if not backend.isconnected():
-        backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
+    def temp_kinit(principal, password):
+        """Kinit with password using a temporary ccache."""
+        if not password:
+            raise RuntimeError("The password is not set")
+        if not principal:
+            principal = "admin"
 
+        ccache_dir = tempfile.mkdtemp(prefix='krbcc')
+        ccache_name = os.path.join(ccache_dir, 'ccache')
 
-def api_command(module, command, name, args):
-    """Call ipa.Command."""
-    return api.Command[command](name, **args)
+        try:
+            kinit_password(principal, password, ccache_name)
+        except RuntimeError as e:
+            raise RuntimeError("Kerberos authentication failed: {}".format(e))
 
+        os.environ["KRB5CCNAME"] = ccache_name
+        return ccache_dir, ccache_name
 
-def api_command_no_name(module, command, args):
-    """Call ipa.Command without a name."""
-    return api.Command[command](**args)
+    def temp_kdestroy(ccache_dir, ccache_name):
+        """Destroy temporary ticket and remove temporary ccache."""
+        if ccache_name is not None:
+            run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
+            del os.environ['KRB5CCNAME']
+        if ccache_dir is not None:
+            shutil.rmtree(ccache_dir, ignore_errors=True)
 
+    def api_connect(context=None):
+        """
+        Initialize IPA API with the provided context.
 
-def api_check_command(command):
-    """Return if command exists in command list."""
-    return command in api.Command
+        `context` can be any of:
+            * `server` (default)
+            * `ansible-freeipa`
+            * `cli_installer`
+        """
+        env = Env()
+        env._bootstrap()
+        env._finalize_core(**dict(DEFAULT_CONFIG))
 
+        # available contexts are 'server', 'ansible-freeipa' and
+        # 'cli_installer'
 
-def api_check_param(command, name):
-    """Check if param exists in command param list."""
-    return name in api.Command[command].params
+        if context is None:
+            context = 'server'
 
+        api.bootstrap(context=context, debug=env.debug, log=None)
+        api.finalize()
 
-def api_check_ipa_version(oper, requested_version):
-    """
-    Compare the installed IPA version against a requested version.
+        if api.env.in_server:
+            backend = api.Backend.ldap2
+        else:
+            backend = api.Backend.rpcclient
 
-    The valid operators are: <, <=, >, >=, ==, !=
-    """
-    oper_map = {
-        "<": operator.lt,
-        "<=": operator.le,
-        ">": operator.gt,
-        ">=": operator.ge,
-        "==": operator.eq,
-        "!=": operator.ne,
-    }
-    operation = oper_map.get(oper)
-    if not(operation):
-        raise NotImplementedError("Invalid operator: %s" % oper)
-    return operation(version.parse(VERSION), version.parse(requested_version))
+        if not backend.isconnected():
+            backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
 
+    def api_command(module, command, name, args):
+        """Call ipa.Command."""
+        return api.Command[command](name, **args)
 
-def execute_api_command(module, principal, password, command, name, args):
-    """
-    Execute an API command.
+    def api_command_no_name(module, command, args):
+        """Call ipa.Command without a name."""
+        return api.Command[command](**args)
 
-    Get KRB ticket if not already there, initialize api, connect,
-    execute command and destroy ticket again if it has been created also.
-    """
-    ccache_dir = None
-    ccache_name = None
-    try:
-        if not valid_creds(module, principal):
-            ccache_dir, ccache_name = temp_kinit(principal, password)
-        api_connect()
+    def api_check_command(command):
+        """Return if command exists in command list."""
+        return command in api.Command
 
-        return api_command(module, command, name, args)
-    except Exception as e:
-        module.fail_json(msg=str(e))
+    def api_check_param(command, name):
+        """Check if param exists in command param list."""
+        return name in api.Command[command].params
 
-    finally:
-        temp_kdestroy(ccache_dir, ccache_name)
+    def api_check_ipa_version(oper, requested_version):
+        """
+        Compare the installed IPA version against a requested version.
 
+        The valid operators are: <, <=, >, >=, ==, !=
+        """
+        oper_map = {
+            "<": operator.lt,
+            "<=": operator.le,
+            ">": operator.gt,
+            ">=": operator.ge,
+            "==": operator.eq,
+            "!=": operator.ne,
+        }
+        operation = oper_map.get(oper)
+        if not(operation):
+            raise NotImplementedError("Invalid operator: %s" % oper)
+        return operation(version.parse(VERSION),
+                         version.parse(requested_version))
 
-def date_format(value):
-    accepted_date_formats = [
-        LDAP_GENERALIZED_TIME_FORMAT,  # generalized time
-        '%Y-%m-%dT%H:%M:%SZ',  # ISO 8601, second precision
-        '%Y-%m-%dT%H:%MZ',     # ISO 8601, minute precision
-        '%Y-%m-%dZ',           # ISO 8601, date only
-        '%Y-%m-%d %H:%M:%SZ',  # non-ISO 8601, second precision
-        '%Y-%m-%d %H:%MZ',     # non-ISO 8601, minute precision
-    ]
+    def execute_api_command(module, principal, password, command, name, args):
+        """
+        Execute an API command.
 
-    for date_format in accepted_date_formats:
+        Get KRB ticket if not already there, initialize api, connect,
+        execute command and destroy ticket again if it has been created also.
+        """
+        ccache_dir = None
+        ccache_name = None
         try:
-            return datetime.strptime(value, date_format)
-        except ValueError:
-            pass
-    raise ValueError("Invalid date '%s'" % value)
+            if not valid_creds(module, principal):
+                ccache_dir, ccache_name = temp_kinit(principal, password)
+            api_connect()
 
+            return api_command(module, command, name, args)
+        except Exception as e:
+            module.fail_json(msg=str(e))
 
-def compare_args_ipa(module, args, ipa):  # noqa
-    """Compare IPA obj attrs with the command args.
+        finally:
+            temp_kdestroy(ccache_dir, ccache_name)
 
-    This function compares IPA objects attributes with the args the
-    module is intending to use to call a command. This is useful to know
-    if call to IPA server will be needed or not.
-    In other to compare we have to prepare the perform slight changes in
-    data formats.
+    def date_format(value):
+        accepted_date_formats = [
+            LDAP_GENERALIZED_TIME_FORMAT,  # generalized time
+            '%Y-%m-%dT%H:%M:%SZ',  # ISO 8601, second precision
+            '%Y-%m-%dT%H:%MZ',     # ISO 8601, minute precision
+            '%Y-%m-%dZ',           # ISO 8601, date only
+            '%Y-%m-%d %H:%M:%SZ',  # non-ISO 8601, second precision
+            '%Y-%m-%d %H:%MZ',     # non-ISO 8601, minute precision
+        ]
 
-    Returns True if they are the same and False otherwise.
-    """
-    base_debug_msg = "Ansible arguments and IPA commands differed. "
+        for date_format in accepted_date_formats:
+            try:
+                return datetime.strptime(value, date_format)
+            except ValueError:
+                pass
+        raise ValueError("Invalid date '%s'" % value)
 
-    # If both args and ipa are None, return there's no difference.
-    # If only one is None, return there is a difference.
-    # This tests avoid unecessary invalid access to attributes.
-    if args is None and ipa is None:
-        return True
-    if args is None or ipa is None:
-        module.debug(
-            base_debug_msg + "args is%s None an ipa is%s None" % (
-                "" if args is None else " not", "" if ipa is None else " not",
-            )
-        )
-        return False
+    def compare_args_ipa(module, args, ipa):  # noqa
+        """Compare IPA obj attrs with the command args.
+
+        This function compares IPA objects attributes with the args the
+        module is intending to use to call a command. This is useful to know
+        if call to IPA server will be needed or not.
+        In other to compare we have to prepare the perform slight changes in
+        data formats.
 
-    # Fail if args or ipa are not dicts.
-    if not (isinstance(args, dict) and isinstance(ipa, dict)):
-        raise TypeError("Expected 'dicts' to compare.")
+        Returns True if they are the same and False otherwise.
+        """
+        base_debug_msg = "Ansible arguments and IPA commands differed. "
 
-    for key in args.keys():
-        if key not in ipa:
+        # If both args and ipa are None, return there's no difference.
+        # If only one is None, return there is a difference.
+        # This tests avoid unecessary invalid access to attributes.
+        if args is None and ipa is None:
+            return True
+        if args is None or ipa is None:
             module.debug(
-                base_debug_msg + "Command key not present in IPA: %s" % key
+                base_debug_msg + "args is%s None an ipa is%s None" % (
+                    "" if args is None else " not",
+                    "" if ipa is None else " not",
+                )
             )
             return False
-        else:
-            arg = args[key]
-            ipa_arg = ipa[key]
-            # If ipa_arg is a list and arg is not, replace arg
-            # with list containing arg. Most args in a find result
-            # are lists, but not all.
-            if isinstance(ipa_arg, tuple):
-                ipa_arg = list(ipa_arg)
-            if isinstance(ipa_arg, list):
-                if not isinstance(arg, list):
-                    arg = [arg]
-                if len(ipa_arg) != len(arg):
-                    module.debug(
-                        base_debug_msg
-                        + "List length doesn't match for key %s: %d %d"
-                        % (key, len(arg), len(ipa_arg),)
-                    )
-                    return False
-                if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
-                    arg = [to_text(_arg) for _arg in arg]
-                if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
-                    arg = [to_text(_arg) for _arg in arg]
-            try:
-                arg_set = set(arg)
-                ipa_arg_set = set(ipa_arg)
-            except TypeError:
-                if arg != ipa_arg:
-                    module.debug(
-                        base_debug_msg
-                        + "Different values: %s %s" % (arg, ipa_arg)
-                    )
-                    return False
-            else:
-                if arg_set != ipa_arg_set:
-                    module.debug(
-                        base_debug_msg
-                        + "Different set content: %s %s"
-                        % (arg_set, ipa_arg_set,)
-                    )
-                    return False
-    return True
-
-
-def _afm_convert(value):
-    if value is not None:
-        if isinstance(value, list):
-            return [_afm_convert(x) for x in value]
-        elif isinstance(value, dict):
-            return {_afm_convert(k): _afm_convert(v) for k, v in value.items()}
-        elif isinstance(value, str):
-            return to_text(value)
-        else:
-            return value
-    else:
-        return value
-
-
-def module_params_get(module, name):
-    return _afm_convert(module.params.get(name))
-
-
-def api_get_realm():
-    return api.env.realm
 
+        # Fail if args or ipa are not dicts.
+        if not (isinstance(args, dict) and isinstance(ipa, dict)):
+            raise TypeError("Expected 'dicts' to compare.")
 
-def gen_add_del_lists(user_list, res_list):
-    """Generate the lists for the addition and removal of members."""
-    # The user list is None, therefore the parameter should not be touched
-    if user_list is None:
-        return [], []
+        for key in args.keys():
+            if key not in ipa:
+                module.debug(
+                    base_debug_msg + "Command key not present in IPA: %s" % key
+                )
+                return False
+            else:
+                arg = args[key]
+                ipa_arg = ipa[key]
+                # If ipa_arg is a list and arg is not, replace arg
+                # with list containing arg. Most args in a find result
+                # are lists, but not all.
+                if isinstance(ipa_arg, tuple):
+                    ipa_arg = list(ipa_arg)
+                if isinstance(ipa_arg, list):
+                    if not isinstance(arg, list):
+                        arg = [arg]
+                    if len(ipa_arg) != len(arg):
+                        module.debug(
+                            base_debug_msg
+                            + "List length doesn't match for key %s: %d %d"
+                            % (key, len(arg), len(ipa_arg),)
+                        )
+                        return False
+                    if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
+                        arg = [to_text(_arg) for _arg in arg]
+                    if isinstance(ipa_arg[0], unicode) \
+                       and isinstance(arg[0], int):
+                        arg = [to_text(_arg) for _arg in arg]
+                try:
+                    arg_set = set(arg)
+                    ipa_arg_set = set(ipa_arg)
+                except TypeError:
+                    if arg != ipa_arg:
+                        module.debug(
+                            base_debug_msg
+                            + "Different values: %s %s" % (arg, ipa_arg)
+                        )
+                        return False
+                else:
+                    if arg_set != ipa_arg_set:
+                        module.debug(
+                            base_debug_msg
+                            + "Different set content: %s %s"
+                            % (arg_set, ipa_arg_set,)
+                        )
+                        return False
+        return True
 
-    add_list = list(set(user_list or []) - set(res_list or []))
-    del_list = list(set(res_list or []) - set(user_list or []))
+    def _afm_convert(value):
+        if value is not None:
+            if isinstance(value, list):
+                return [_afm_convert(x) for x in value]
+            elif isinstance(value, dict):
+                return {_afm_convert(k): _afm_convert(v)
+                        for k, v in value.items()}
+            elif isinstance(value, str):
+                return to_text(value)
+            else:
+                return value
+        else:
+            return value
 
-    return add_list, del_list
+    def module_params_get(module, name):
+        return _afm_convert(module.params.get(name))
 
+    def api_get_realm():
+        return api.env.realm
 
-def encode_certificate(cert):
-    """
-    Encode a certificate using base64.
+    def gen_add_del_lists(user_list, res_list):
+        """Generate the lists for the addition and removal of members."""
+        # The user list is None, therefore the parameter should not be touched
+        if user_list is None:
+            return [], []
 
-    It also takes FreeIPA and Python versions into account.
-    """
-    if isinstance(cert, (str, unicode, bytes)):
-        encoded = base64.b64encode(cert)
-    else:
-        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
-    if not six.PY2:
-        encoded = encoded.decode('ascii')
-    return encoded
+        add_list = list(set(user_list or []) - set(res_list or []))
+        del_list = list(set(res_list or []) - set(user_list or []))
 
+        return add_list, del_list
 
-def load_cert_from_str(cert):
-    cert = cert.strip()
-    if not cert.startswith("-----BEGIN CERTIFICATE-----"):
-        cert = "-----BEGIN CERTIFICATE-----\n" + cert
-    if not cert.endswith("-----END CERTIFICATE-----"):
-        cert += "\n-----END CERTIFICATE-----"
+    def encode_certificate(cert):
+        """
+        Encode a certificate using base64.
 
-    if load_pem_x509_certificate is not None:
-        cert = load_pem_x509_certificate(cert.encode('utf-8'))
-    else:
-        cert = load_certificate(cert.encode('utf-8'))
-    return cert
+        It also takes FreeIPA and Python versions into account.
+        """
+        if isinstance(cert, (str, unicode, bytes)):
+            encoded = base64.b64encode(cert)
+        else:
+            encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
+        if not six.PY2:
+            encoded = encoded.decode('ascii')
+        return encoded
+
+    def load_cert_from_str(cert):
+        cert = cert.strip()
+        if not cert.startswith("-----BEGIN CERTIFICATE-----"):
+            cert = "-----BEGIN CERTIFICATE-----\n" + cert
+        if not cert.endswith("-----END CERTIFICATE-----"):
+            cert += "\n-----END CERTIFICATE-----"
+
+        if load_pem_x509_certificate is not None:
+            cert = load_pem_x509_certificate(cert.encode('utf-8'))
+        else:
+            cert = load_certificate(cert.encode('utf-8'))
+        return cert
 
+    def DN_x500_text(text):
+        if hasattr(DN, "x500_text"):
+            return DN(text).x500_text()
+        else:
+            # Emulate x500_text
+            dn = DN(text)
+            dn.rdns = reversed(dn.rdns)
+            return str(dn)
 
-def DN_x500_text(text):
-    if hasattr(DN, "x500_text"):
-        return DN(text).x500_text()
-    else:
-        # Emulate x500_text
-        dn = DN(text)
-        dn.rdns = reversed(dn.rdns)
-        return str(dn)
+    def is_valid_port(port):
+        if not isinstance(port, int):
+            return False
 
+        if 1 <= port <= 65535:
+            return True
 
-def is_valid_port(port):
-    if not isinstance(port, int):
         return False
 
-    if 1 <= port <= 65535:
+    def is_ip_address(ipaddr):
+        """Test if given IP address is a valid IPv4 or IPv6 address."""
+        try:
+            netaddr.IPAddress(str(ipaddr))
+        except (netaddr.AddrFormatError, ValueError):
+            return False
         return True
 
-    return False
-
-
-def is_ip_address(ipaddr):
-    """Test if given IP address is a valid IPv4 or IPv6 address."""
-    try:
-        netaddr.IPAddress(str(ipaddr))
-    except (netaddr.AddrFormatError, ValueError):
-        return False
-    return True
-
-
-def is_ip_network_address(ipaddr):
-    """Test if given IP address is a valid IPv4 or IPv6 address."""
-    try:
-        netaddr.IPNetwork(str(ipaddr))
-    except (netaddr.AddrFormatError, ValueError):
-        return False
-    return True
-
-
-def is_ipv4_addr(ipaddr):
-    """Test if given IP address is a valid IPv4 address."""
-    try:
-        socket.inet_pton(socket.AF_INET, ipaddr)
-    except socket.error:
-        return False
-    return True
-
-
-def is_ipv6_addr(ipaddr):
-    """Test if given IP address is a valid IPv6 address."""
-    try:
-        socket.inet_pton(socket.AF_INET6, ipaddr)
-    except socket.error:
-        return False
-    return True
-
-
-def exit_raw_json(module, **kwargs):
-    """
-    Print the raw parameters in JSON format, without masking.
-
-    Due to Ansible filtering out values in the output that match values
-    in variables which has `no_log` set, if a module need to return user
-    defined dato to the controller, it cannot rely on
-    AnsibleModule.exit_json, as there is a chance that a partial match may
-    occur, masking the data returned.
-
-    This method is a replacement for AnsibleModule.exit_json. It has
-    nearly the same implementation as exit_json, but does not filter
-    data. Beware that this data will be logged by Ansible, and if it
-    contains sensible data, it will be appear in the logs.
-    """
-    module.do_cleanup_files()
-    print(jsonify(kwargs))
-    sys.exit(0)
+    def is_ip_network_address(ipaddr):
+        """Test if given IP address is a valid IPv4 or IPv6 address."""
+        try:
+            netaddr.IPNetwork(str(ipaddr))
+        except (netaddr.AddrFormatError, ValueError):
+            return False
+        return True
 
+    def is_ipv4_addr(ipaddr):
+        """Test if given IP address is a valid IPv4 address."""
+        try:
+            socket.inet_pton(socket.AF_INET, ipaddr)
+        except socket.error:
+            return False
+        return True
 
-class AnsibleFreeIPAParams(Mapping):
-    def __init__(self, ansible_module):
-        self.mapping = ansible_module.params
-        self.ansible_module = ansible_module
+    def is_ipv6_addr(ipaddr):
+        """Test if given IP address is a valid IPv6 address."""
+        try:
+            socket.inet_pton(socket.AF_INET6, ipaddr)
+        except socket.error:
+            return False
+        return True
 
-    def __getitem__(self, key):
-        param = self.mapping[key]
-        if param is not None:
-            return _afm_convert(param)
+    def exit_raw_json(module, **kwargs):
+        """
+        Print the raw parameters in JSON format, without masking.
+
+        Due to Ansible filtering out values in the output that match values
+        in variables which has `no_log` set, if a module need to return user
+        defined dato to the controller, it cannot rely on
+        AnsibleModule.exit_json, as there is a chance that a partial match may
+        occur, masking the data returned.
+
+        This method is a replacement for AnsibleModule.exit_json. It has
+        nearly the same implementation as exit_json, but does not filter
+        data. Beware that this data will be logged by Ansible, and if it
+        contains sensible data, it will be appear in the logs.
+        """
+        module.do_cleanup_files()
+        print(jsonify(kwargs))
+        sys.exit(0)
 
-    def __iter__(self):
-        return iter(self.mapping)
+    class AnsibleFreeIPAParams(Mapping):
+        def __init__(self, ansible_module):
+            self.mapping = ansible_module.params
+            self.ansible_module = ansible_module
 
-    def __len__(self):
-        return len(self.mapping)
+        def __getitem__(self, key):
+            param = self.mapping[key]
+            if param is not None:
+                return _afm_convert(param)
 
-    @property
-    def names(self):
-        return self.name
+        def __iter__(self):
+            return iter(self.mapping)
 
-    def __getattr__(self, name):
-        return self.get(name)
+        def __len__(self):
+            return len(self.mapping)
 
+        @property
+        def names(self):
+            return self.name
 
-class FreeIPABaseModule(AnsibleModule):
-    """
-    Base class for FreeIPA Ansible modules.
+        def __getattr__(self, name):
+            return self.get(name)
 
-    Provides methods useful methods to be used by our modules.
+    class FreeIPABaseModule(AnsibleModule):
+        """
+        Base class for FreeIPA Ansible modules.
 
-    This class should be overriten and instantiated for the module.
-    A basic implementation of an Ansible FreeIPA module expects its
-    class to:
+        Provides methods useful methods to be used by our modules.
 
-    1. Define a class attribute ``ipa_param_mapping``
-    2. Implement the method ``define_ipa_commands()``
-    3. Implement the method ``check_ipa_params()`` (optional)
+        This class should be overriten and instantiated for the module.
+        A basic implementation of an Ansible FreeIPA module expects its
+        class to:
 
-    After instantiating the class the method ``ipa_run()`` should be called.
+        1. Define a class attribute ``ipa_param_mapping``
+        2. Implement the method ``define_ipa_commands()``
+        3. Implement the method ``check_ipa_params()`` (optional)
 
-    Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
+        After instantiating the class the method ``ipa_run()`` should be
+        called.
 
-    class SomeIPAModule(FreeIPABaseModule):
-        ipa_param_mapping = {
-            "arg_to_be_passed_to_ipa_command": "module_param",
-            "another_arg": "get_another_module_param",
-        }
+        Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
 
-        def get_another_module_param(self):
-            another_module_param = self.ipa_params.another_module_param
-            # Validate or modify another_module_param
-            # ...
-            return another_module_param
+        class SomeIPAModule(FreeIPABaseModule):
+            ipa_param_mapping = {
+                "arg_to_be_passed_to_ipa_command": "module_param",
+                "another_arg": "get_another_module_param",
+            }
 
-        def check_ipa_params(self):
-            # Validate your params here
-            # Example:
-            if not self.ipa_params.module_param in VALID_OPTIONS:
-                self.fail_json(msg="Invalid value for argument module_param")
+            def get_another_module_param(self):
+                another_module_param = self.ipa_params.another_module_param
+                # Validate or modify another_module_param
+                # ...
+                return another_module_param
 
-        def define_ipa_commands(self):
-            args = self.get_ipa_command_args()
+            def check_ipa_params(self):
+                # Validate your params here
+                # Example:
+                if not self.ipa_params.module_param in VALID_OPTIONS:
+                    self.fail_json(
+                        msg="Invalid value for argument module_param")
 
-            self.add_ipa_command(
-                "some_ipa_command",
-                name="obj-name",
-                args=args,
-            )
+            def define_ipa_commands(self):
+                args = self.get_ipa_command_args()
 
-    def main():
-        ipa_module = SomeIPAModule(argument_spec=dict(
-            module_param=dict(
-                type="str",
-                default=None,
-                required=False,
-            ),
-            another_module_param=dict(
-                type="str",
-                default=None,
-                required=False,
-            ),
-        ))
-        ipa_module.ipa_run()
+                self.add_ipa_command(
+                    "some_ipa_command",
+                    name="obj-name",
+                    args=args,
+                )
 
-    if __name__ == "__main__":
-        main()
+        def main():
+            ipa_module = SomeIPAModule(argument_spec=dict(
+                module_param=dict(
+                    type="str",
+                    default=None,
+                    required=False,
+                ),
+                another_module_param=dict(
+                    type="str",
+                    default=None,
+                    required=False,
+                ),
+            ))
+            ipa_module.ipa_run()
+
+        if __name__ == "__main__":
+            main()
 
-    """
+        """
 
-    ipa_param_mapping = None
+        ipa_param_mapping = None
 
-    def __init__(self, *args, **kwargs):
-        super(FreeIPABaseModule, self).__init__(*args, **kwargs)
+        def __init__(self, *args, **kwargs):
+            super(FreeIPABaseModule, self).__init__(*args, **kwargs)
 
-        # Attributes to store kerberos credentials (if needed)
-        self.ccache_dir = None
-        self.ccache_name = None
+            # Attributes to store kerberos credentials (if needed)
+            self.ccache_dir = None
+            self.ccache_name = None
 
-        # Status of an execution. Will be changed to True
-        #   if something is actually peformed.
-        self.changed = False
+            # Status of an execution. Will be changed to True
+            #   if something is actually peformed.
+            self.changed = False
 
-        # Status of the connection with the IPA server.
-        # We need to know if the connection was actually stablished
-        #   before we start sending commands.
-        self.ipa_connected = False
+            # Status of the connection with the IPA server.
+            # We need to know if the connection was actually stablished
+            #   before we start sending commands.
+            self.ipa_connected = False
 
-        # Commands to be executed
-        self.ipa_commands = []
+            # Commands to be executed
+            self.ipa_commands = []
 
-        # Module exit arguments.
-        self.exit_args = {}
+            # Module exit arguments.
+            self.exit_args = {}
 
-        # Wrapper around the AnsibleModule.params.
-        # Return the actual params but performing transformations
-        #   when needed.
-        self.ipa_params = AnsibleFreeIPAParams(self)
+            # Wrapper around the AnsibleModule.params.
+            # Return the actual params but performing transformations
+            #   when needed.
+            self.ipa_params = AnsibleFreeIPAParams(self)
 
-    def get_ipa_command_args(self, **kwargs):
-        """
-        Return a dict to be passed to an IPA command.
+        def get_ipa_command_args(self, **kwargs):
+            """
+            Return a dict to be passed to an IPA command.
 
-        The keys of ``ipa_param_mapping`` are also the keys of the return dict.
+            The keys of ``ipa_param_mapping`` are also the keys of the return
+            dict.
 
-        The values of ``ipa_param_mapping`` needs to be either:
-            * A str with the name of a defined method; or
-            * A key of ``AnsibleModule.param``.
+            The values of ``ipa_param_mapping`` needs to be either:
+                * A str with the name of a defined method; or
+                * A key of ``AnsibleModule.param``.
 
-        In case of a method the return of the method will be set as value
-        for the return dict.
+            In case of a method the return of the method will be set as value
+            for the return dict.
 
-        In case of a AnsibleModule.param the value of the param will be
-        set in the return dict. In addition to that boolean values will be
-        automaticaly converted to uppercase strings (as required by FreeIPA
-        server).
+            In case of a AnsibleModule.param the value of the param will be
+            set in the return dict. In addition to that boolean values will be
+            automaticaly converted to uppercase strings (as required by FreeIPA
+            server).
 
-        """
-        args = {}
-        for ipa_param_name, param_name in self.ipa_param_mapping.items():
-
-            # Check if param_name is actually a param
-            if param_name in self.ipa_params:
-                value = self.ipa_params.get(param_name)
-                if isinstance(value, bool):
-                    value = "TRUE" if value else "FALSE"
-
-            # Since param wasn't a param check if it's a method name
-            elif hasattr(self, param_name):
-                method = getattr(self, param_name)
-                if callable(method):
-                    value = method(**kwargs)
-
-            # We don't have a way to guess the value so fail.
-            else:
-                self.fail_json(
-                    msg=(
-                        "Couldn't get a value for '%s'. Option '%s' is not "
-                        "a module argument neither a defined method."
+            """
+            args = {}
+            for ipa_param_name, param_name in self.ipa_param_mapping.items():
+
+                # Check if param_name is actually a param
+                if param_name in self.ipa_params:
+                    value = self.ipa_params.get(param_name)
+                    if isinstance(value, bool):
+                        value = "TRUE" if value else "FALSE"
+
+                # Since param wasn't a param check if it's a method name
+                elif hasattr(self, param_name):
+                    method = getattr(self, param_name)
+                    if callable(method):
+                        value = method(**kwargs)
+
+                # We don't have a way to guess the value so fail.
+                else:
+                    self.fail_json(
+                        msg=(
+                            "Couldn't get a value for '%s'. Option '%s' is "
+                            "not a module argument neither a defined method."
+                        )
+                        % (ipa_param_name, param_name)
                     )
-                    % (ipa_param_name, param_name)
-                )
-
-            if value is not None:
-                args[ipa_param_name] = value
-
-        return args
-
-    def check_ipa_params(self):
-        """Validate ipa_params before command is called."""
-        pass
 
-    def define_ipa_commands(self):
-        """Define commands that will be run in IPA server."""
-        raise NotImplementedError
+                if value is not None:
+                    args[ipa_param_name] = value
 
-    def api_command(self, command, name=None, args=None):
-        """Execute a single command in IPA server."""
-        if args is None:
-            args = {}
+            return args
 
-        if name is None:
-            return api_command_no_name(self, command, args)
+        def check_ipa_params(self):
+            """Validate ipa_params before command is called."""
+            pass
 
-        return api_command(self, command, name, args)
+        def define_ipa_commands(self):
+            """Define commands that will be run in IPA server."""
+            raise NotImplementedError
 
-    def __enter__(self):
-        """
-        Connect to IPA server.
+        def api_command(self, command, name=None, args=None):
+            """Execute a single command in IPA server."""
+            if args is None:
+                args = {}
 
-        Check the there are working Kerberos credentials to connect to
-        IPA server. If there are not we perform a temporary kinit
-        that will be terminated when exiting the context.
+            if name is None:
+                return api_command_no_name(self, command, args)
 
-        If the connection fails ``ipa_connected`` attribute will be set
-        to False.
-        """
-        principal = self.ipa_params.ipaadmin_principal
-        password = self.ipa_params.ipaadmin_password
+            return api_command(self, command, name, args)
 
-        try:
-            if not valid_creds(self, principal):
-                self.ccache_dir, self.ccache_name = temp_kinit(
-                    principal, password,
-                )
+        def __enter__(self):
+            """
+            Connect to IPA server.
 
-            api_connect()
+            Check the there are working Kerberos credentials to connect to
+            IPA server. If there are not we perform a temporary kinit
+            that will be terminated when exiting the context.
 
-        except Exception as excpt:
-            self.fail_json(msg=str(excpt))
-        else:
-            self.ipa_connected = True
+            If the connection fails ``ipa_connected`` attribute will be set
+            to False.
+            """
+            principal = self.ipa_params.ipaadmin_principal
+            password = self.ipa_params.ipaadmin_password
 
-        return self
+            try:
+                if not valid_creds(self, principal):
+                    self.ccache_dir, self.ccache_name = temp_kinit(
+                        principal, password,
+                    )
 
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        """
-        Terminate a connection with the IPA server.
+                api_connect()
 
-        Deal with exceptions, destroy temporary kinit credentials and
-        exit the module with proper arguments.
+            except Exception as excpt:
+                self.fail_json(msg=str(excpt))
+            else:
+                self.ipa_connected = True
 
-        """
-        # TODO: shouldn't we also disconnect from api backend?
-        temp_kdestroy(self.ccache_dir, self.ccache_name)
-
-        if exc_type == SystemExit:
-            raise
-
-        if exc_val:
-            self.fail_json(msg=str(exc_val))
-
-        self.exit_json(changed=self.changed, **self.exit_args)
-
-    def get_command_errors(self, command, result):
-        """Look for erros into command results."""
-        # Get all errors
-        # All "already a member" and "not a member" failures in the
-        # result are ignored. All others are reported.
-        errors = []
-        for item in result.get("failed", tuple()):
-            failed_item = result["failed"][item]
-            for member_type in failed_item:
-                for member, failure in failed_item[member_type]:
-                    if (
-                        "already a member" in failure
-                        or "not a member" in failure
-                    ):
-                        continue
-                    errors.append(
-                        "%s: %s %s: %s"
-                        % (command, member_type, member, failure)
-                    )
+            return self
 
-        if len(errors) > 0:
-            self.fail_json(", ".join("errors"))
+        def __exit__(self, exc_type, exc_val, exc_tb):
+            """
+            Terminate a connection with the IPA server.
 
-    def add_ipa_command(self, command, name=None, args=None):
-        """Add a command to the list of commands to be executed."""
-        self.ipa_commands.append((name, command, args or {}))
+            Deal with exceptions, destroy temporary kinit credentials and
+            exit the module with proper arguments.
 
-    def _run_ipa_commands(self):
-        """Execute commands in self.ipa_commands."""
-        result = None
+            """
+            # TODO: shouldn't we also disconnect from api backend?
+            temp_kdestroy(self.ccache_dir, self.ccache_name)
+
+            if exc_type == SystemExit:
+                raise
+
+            if exc_val:
+                self.fail_json(msg=str(exc_val))
+
+            self.exit_json(changed=self.changed, **self.exit_args)
+
+        def get_command_errors(self, command, result):
+            """Look for erros into command results."""
+            # Get all errors
+            # All "already a member" and "not a member" failures in the
+            # result are ignored. All others are reported.
+            errors = []
+            for item in result.get("failed", tuple()):
+                failed_item = result["failed"][item]
+                for member_type in failed_item:
+                    for member, failure in failed_item[member_type]:
+                        if (
+                            "already a member" in failure
+                            or "not a member" in failure
+                        ):
+                            continue
+                        errors.append(
+                            "%s: %s %s: %s"
+                            % (command, member_type, member, failure)
+                        )
+
+            if len(errors) > 0:
+                self.fail_json(", ".join("errors"))
+
+        def add_ipa_command(self, command, name=None, args=None):
+            """Add a command to the list of commands to be executed."""
+            self.ipa_commands.append((name, command, args or {}))
+
+        def _run_ipa_commands(self):
+            """Execute commands in self.ipa_commands."""
+            result = None
+
+            for name, command, args in self.ipa_commands:
+                try:
+                    result = self.api_command(command, name, args)
+                except Exception as excpt:
+                    self.fail_json(msg="%s: %s: %s" % (command, name,
+                                                       str(excpt)))
+                else:
+                    self.process_command_result(name, command, args, result)
+                self.get_command_errors(command, result)
+
+        def process_command_result(self, name, command, args, result):
+            """
+            Process an API command result.
 
-        for name, command, args in self.ipa_commands:
-            try:
-                result = self.api_command(command, name, args)
-            except Exception as excpt:
-                self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
+            This method can be overriden in subclasses, and
+            change self.exit_values
+            to return data in the result for the controller.
+            """
+            if "completed" in result:
+                if result["completed"] > 0:
+                    self.changed = True
             else:
-                self.process_command_result(name, command, args, result)
-            self.get_command_errors(command, result)
-
-    def process_command_result(self, name, command, args, result):
-        """
-        Process an API command result.
-
-        This method can be overriden in subclasses, and change self.exit_values
-        to return data in the result for the controller.
-        """
-        if "completed" in result:
-            if result["completed"] > 0:
                 self.changed = True
-        else:
-            self.changed = True
 
-    def require_ipa_attrs_change(self, command_args, ipa_attrs):
-        """
-        Compare given args with current object attributes.
+        def require_ipa_attrs_change(self, command_args, ipa_attrs):
+            """
+            Compare given args with current object attributes.
 
-        Returns True in case current IPA object attributes differ from
-        args passed to the module.
-        """
-        equal = compare_args_ipa(self, command_args, ipa_attrs)
-        return not equal
-
-    def pdebug(self, value):
-        """Debug with pretty formatting."""
-        self.debug(pformat(value))
-
-    def ipa_run(self):
-        """Execute module actions."""
-        with self:
-            if not self.ipa_connected:
-                return
-
-            self.check_ipa_params()
-            self.define_ipa_commands()
-            self._run_ipa_commands()
+            Returns True in case current IPA object attributes differ from
+            args passed to the module.
+            """
+            equal = compare_args_ipa(self, command_args, ipa_attrs)
+            return not equal
+
+        def pdebug(self, value):
+            """Debug with pretty formatting."""
+            self.debug(pformat(value))
+
+        def ipa_run(self):
+            """Execute module actions."""
+            with self:
+                if not self.ipa_connected:
+                    return
+
+                self.check_ipa_params()
+                self.define_ipa_commands()
+                self._run_ipa_commands()
diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py
index fac8123481e33b24e8bff97d64170e26dd85c4bd..33531c51a32be8233e3488424e97a7ecd07183e0 100644
--- a/roles/ipaclient/module_utils/ansible_ipa_client.py
+++ b/roles/ipaclient/module_utils/ansible_ipa_client.py
@@ -45,229 +45,244 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
            "configure_firefox", "sync_time", "check_ldap_conf",
            "sssd_enable_ifp"]
 
-from ipapython.version import NUM_VERSION, VERSION
+import sys
+
+# HACK: workaround for Ansible 2.9
+# https://github.com/ansible/ansible/issues/68361
+if 'ansible.executor' in sys.modules:
+    for attr in __all__:
+        setattr(sys.modules[__name__], attr, None)
 
-if NUM_VERSION < 30201:
-    # See ipapython/version.py
-    IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)]
-    IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
 else:
-    IPA_PYTHON_VERSION = NUM_VERSION
-
-
-class installer_obj(object):
-    def __init__(self):
-        pass
-
-    def set_logger(self, logger):
-        self.logger = logger
-
-    # def __getattribute__(self, attr):
-    #    value = super(installer_obj, self).__getattribute__(attr)
-    #    if not attr.startswith("--") and not attr.endswith("--"):
-    #        logger.debug(
-    #            "  <-- Accessing installer.%s (%s)" % (attr, repr(value)))
-    #    return value
-
-    # def __getattr__(self, attr):
-    #    # logger.info("  --> ADDING missing installer.%s" % attr)
-    #    self.logger.warn("  --> ADDING missing installer.%s" % attr)
-    #    setattr(self, attr, None)
-    #    return getattr(self, attr)
-
-    # def __setattr__(self, attr, value):
-    #    logger.debug("  --> Setting installer.%s to %s" % (attr, repr(value)))
-    #    return super(installer_obj, self).__setattr__(attr, value)
-
-    def knobs(self):
-        for name in self.__dict__:
-            yield self, name
-
-
-# Initialize installer settings
-installer = installer_obj()
-# Create options
-options = installer
-options.interactive = False
-options.unattended = not options.interactive
-
-if NUM_VERSION >= 40400:
-    # IPA version >= 4.4
-
-    import sys
-    import inspect
-    import gssapi
-    import logging
-
-    from ipapython import version
-    try:
-        from ipaclient.install import ipadiscovery
-    except ImportError:
-        from ipaclient import ipadiscovery
-    from ipalib import api, errors, x509
-    from ipalib import constants
-    try:
-        from ipalib import sysrestore
-    except ImportError:
+    from ipapython.version import NUM_VERSION, VERSION
+
+    if NUM_VERSION < 30201:
+        # See ipapython/version.py
+        IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in
+                                             VERSION.split(".", 2)]
+        IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
+    else:
+        IPA_PYTHON_VERSION = NUM_VERSION
+
+    class installer_obj(object):
+        def __init__(self):
+            pass
+
+        def set_logger(self, logger):
+            self.logger = logger
+
+        # def __getattribute__(self, attr):
+        #    value = super(installer_obj, self).__getattribute__(attr)
+        #    if not attr.startswith("--") and not attr.endswith("--"):
+        #        logger.debug(
+        #            "  <-- Accessing installer.%s (%s)" % (attr, repr(value)))
+        #    return value
+
+        # def __getattr__(self, attr):
+        #    # logger.info("  --> ADDING missing installer.%s" % attr)
+        #    self.logger.warn("  --> ADDING missing installer.%s" % attr)
+        #    setattr(self, attr, None)
+        #    return getattr(self, attr)
+
+        # def __setattr__(self, attr, value):
+        #    logger.debug("  --> Setting installer.%s to %s" %
+        #                 (attr, repr(value)))
+        #    return super(installer_obj, self).__setattr__(attr, value)
+
+        def knobs(self):
+            for name in self.__dict__:
+                yield self, name
+
+    # Initialize installer settings
+    installer = installer_obj()
+    # Create options
+    options = installer
+    options.interactive = False
+    options.unattended = not options.interactive
+
+    if NUM_VERSION >= 40400:
+        # IPA version >= 4.4
+
+        import sys
+        import inspect
+        import gssapi
+        import logging
+
+        from ipapython import version
+        try:
+            from ipaclient.install import ipadiscovery
+        except ImportError:
+            from ipaclient import ipadiscovery
+        from ipalib import api, errors, x509
+        from ipalib import constants
+        try:
+            from ipalib import sysrestore
+        except ImportError:
+            try:
+                from ipalib.install import sysrestore
+            except ImportError:
+                from ipapython import sysrestore
+        try:
+            from ipalib.install import certmonger
+        except ImportError:
+            from ipapython import certmonger
+        try:
+            from ipalib.install import certstore
+        except ImportError:
+            from ipalib import certstore
+        from ipalib.rpc import delete_persistent_client_session_data
+        from ipapython import certdb, ipautil
+        from ipapython.admintool import ScriptError
+        from ipapython.ipautil import CheckedIPAddress
+        from ipalib.util import validate_domain_name, normalize_hostname, \
+            validate_hostname
+        from ipaplatform import services
+        from ipaplatform.paths import paths
+        from ipaplatform.tasks import tasks
+        try:
+            from cryptography.hazmat.primitives import serialization
+        except ImportError:
+            serialization = None
+        from ipapython.ipautil import CalledProcessError, write_tmp_file, \
+            ipa_generate_password
+        from ipapython.dn import DN
         try:
-            from ipalib.install import sysrestore
+            from ipalib.install.kinit import kinit_keytab, kinit_password
         except ImportError:
-            from ipapython import sysrestore
-    try:
-        from ipalib.install import certmonger
-    except ImportError:
-        from ipapython import certmonger
-    try:
-        from ipalib.install import certstore
-    except ImportError:
-        from ipalib import certstore
-    from ipalib.rpc import delete_persistent_client_session_data
-    from ipapython import certdb, ipautil
-    from ipapython.admintool import ScriptError
-    from ipapython.ipautil import CheckedIPAddress
-    from ipalib.util import validate_domain_name, normalize_hostname, \
-        validate_hostname
-    from ipaplatform import services
-    from ipaplatform.paths import paths
-    from ipaplatform.tasks import tasks
-    try:
-        from cryptography.hazmat.primitives import serialization
-    except ImportError:
-        serialization = None
-    from ipapython.ipautil import CalledProcessError, write_tmp_file, \
-        ipa_generate_password
-    from ipapython.dn import DN
-    try:
-        from ipalib.install.kinit import kinit_keytab, kinit_password
-    except ImportError:
-        from ipapython.ipautil import kinit_keytab, kinit_password
-    from ipapython.ipa_log_manager import standard_logging_setup
-    from gssapi.exceptions import GSSError
-    try:
-        from ipaclient.install.client import configure_krb5_conf, \
-            get_ca_certs, SECURE_PATH, get_server_connection_interface, \
-            disable_ra, client_dns, \
-            configure_certmonger, update_ssh_keys, configure_openldap_conf, \
-            hardcode_ldap_server, get_certs_from_ldap, save_state, \
-            create_ipa_nssdb, configure_ssh_config, configure_sshd_config, \
-            configure_automount, configure_firefox, configure_nisdomain, \
-            CLIENT_INSTALL_ERROR, is_ipa_client_installed, \
-            CLIENT_ALREADY_CONFIGURED, nssldap_exists, remove_file, \
-            check_ip_addresses, print_port_conf_info, configure_ipa_conf, \
-            purge_host_keytab, configure_sssd_conf, configure_ldap_conf, \
-            configure_nslcd_conf, nosssd_files
-        get_ca_cert = None
-    except ImportError:
-        # Create temporary copy of ipa-client-install script (as
-        # ipa_client_install.py) to be able to import the script easily
-        # and also to remove the global finally clause in which the
-        # generated ccache file gets removed. The ccache file will be
-        # needed in the next step.
-        # This is done in a temporary directory that gets removed right
-        # after ipa_client_install has been imported.
-        import shutil
-        import tempfile
-        temp_dir = tempfile.mkdtemp(dir="/tmp")
-        sys.path.append(temp_dir)
-        temp_file = "%s/ipa_client_install.py" % temp_dir
-
-        with open("/usr/sbin/ipa-client-install", "r") as f_in:
-            with open(temp_file, "w") as f_out:
-                for line in f_in:
-                    if line.startswith("finally:"):
-                        break
-                    f_out.write(line)
-        import ipa_client_install
-
-        shutil.rmtree(temp_dir, ignore_errors=True)
-        sys.path.remove(temp_dir)
-
-        argspec = inspect.getargspec(ipa_client_install.configure_krb5_conf)
-        if argspec.keywords is None:
-            def configure_krb5_conf(
-                    cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
-                    filename, client_domain, client_hostname, force=False,
-                    configure_sssd=True):
-                global options
-                options.force = force
-                options.sssd = configure_sssd
-                return ipa_client_install.configure_krb5_conf(
-                    cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options,
-                    filename, client_domain, client_hostname)
-        else:
-            configure_krb5_conf = ipa_client_install.configure_krb5_conf
-        if NUM_VERSION < 40100:
-            get_ca_cert = ipa_client_install.get_ca_cert
-            get_ca_certs = None
-        else:
+            from ipapython.ipautil import kinit_keytab, kinit_password
+        from ipapython.ipa_log_manager import standard_logging_setup
+        from gssapi.exceptions import GSSError
+        try:
+            from ipaclient.install.client import configure_krb5_conf, \
+                get_ca_certs, SECURE_PATH, get_server_connection_interface, \
+                disable_ra, client_dns, \
+                configure_certmonger, update_ssh_keys, \
+                configure_openldap_conf, \
+                hardcode_ldap_server, get_certs_from_ldap, save_state, \
+                create_ipa_nssdb, configure_ssh_config, \
+                configure_sshd_config, \
+                configure_automount, configure_firefox, configure_nisdomain, \
+                CLIENT_INSTALL_ERROR, is_ipa_client_installed, \
+                CLIENT_ALREADY_CONFIGURED, nssldap_exists, remove_file, \
+                check_ip_addresses, print_port_conf_info, configure_ipa_conf, \
+                purge_host_keytab, configure_sssd_conf, configure_ldap_conf, \
+                configure_nslcd_conf, nosssd_files
             get_ca_cert = None
-            get_ca_certs = ipa_client_install.get_ca_certs
-        SECURE_PATH = ("/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:"
-                       "/usr/bin:/usr/sbin")
-
-        get_server_connection_interface = \
-            ipa_client_install.get_server_connection_interface
-        disable_ra = ipa_client_install.disable_ra
-        client_dns = ipa_client_install.client_dns
-        configure_certmonger = ipa_client_install.configure_certmonger
-        update_ssh_keys = ipa_client_install.update_ssh_keys
-        configure_openldap_conf = ipa_client_install.configure_openldap_conf
-        hardcode_ldap_server = ipa_client_install.hardcode_ldap_server
-        get_certs_from_ldap = ipa_client_install.get_certs_from_ldap
-        save_state = ipa_client_install.save_state
-
-        create_ipa_nssdb = certdb.create_ipa_nssdb
-
-        argspec = inspect.getargspec(ipa_client_install.configure_nisdomain)
-        if len(argspec.args) == 3:
-            configure_nisdomain = ipa_client_install.configure_nisdomain
-        else:
-            def configure_nisdomain(options, domain, statestore=None):
-                return ipa_client_install.configure_nisdomain(options, domain)
-
-        configure_ldap_conf = ipa_client_install.configure_ldap_conf
-        configure_nslcd_conf = ipa_client_install.configure_nslcd_conf
-        nosssd_files = ipa_client_install.nosssd_files
-
-        configure_ssh_config = ipa_client_install.configure_ssh_config
-        configure_sshd_config = ipa_client_install.configure_sshd_config
-        configure_automount = ipa_client_install.configure_automount
-        configure_firefox = ipa_client_install.configure_firefox
-
-    from ipapython.ipautil import realm_to_suffix, run
-
-    try:
-        from ipaclient.install import timeconf
-        time_service = "chronyd"
-    except ImportError:
+        except ImportError:
+            # Create temporary copy of ipa-client-install script (as
+            # ipa_client_install.py) to be able to import the script easily
+            # and also to remove the global finally clause in which the
+            # generated ccache file gets removed. The ccache file will be
+            # needed in the next step.
+            # This is done in a temporary directory that gets removed right
+            # after ipa_client_install has been imported.
+            import shutil
+            import tempfile
+            temp_dir = tempfile.mkdtemp(dir="/tmp")
+            sys.path.append(temp_dir)
+            temp_file = "%s/ipa_client_install.py" % temp_dir
+
+            with open("/usr/sbin/ipa-client-install", "r") as f_in:
+                with open(temp_file, "w") as f_out:
+                    for line in f_in:
+                        if line.startswith("finally:"):
+                            break
+                        f_out.write(line)
+            import ipa_client_install
+
+            shutil.rmtree(temp_dir, ignore_errors=True)
+            sys.path.remove(temp_dir)
+
+            argspec = inspect.getargspec(
+                ipa_client_install.configure_krb5_conf)
+            if argspec.keywords is None:
+                def configure_krb5_conf(
+                        cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
+                        filename, client_domain, client_hostname, force=False,
+                        configure_sssd=True):
+                    global options
+                    options.force = force
+                    options.sssd = configure_sssd
+                    return ipa_client_install.configure_krb5_conf(
+                        cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
+                        options, filename, client_domain, client_hostname)
+            else:
+                configure_krb5_conf = ipa_client_install.configure_krb5_conf
+            if NUM_VERSION < 40100:
+                get_ca_cert = ipa_client_install.get_ca_cert
+                get_ca_certs = None
+            else:
+                get_ca_cert = None
+                get_ca_certs = ipa_client_install.get_ca_certs
+            SECURE_PATH = ("/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:"
+                           "/usr/bin:/usr/sbin")
+
+            get_server_connection_interface = \
+                ipa_client_install.get_server_connection_interface
+            disable_ra = ipa_client_install.disable_ra
+            client_dns = ipa_client_install.client_dns
+            configure_certmonger = ipa_client_install.configure_certmonger
+            update_ssh_keys = ipa_client_install.update_ssh_keys
+            configure_openldap_conf = \
+                ipa_client_install.configure_openldap_conf
+            hardcode_ldap_server = ipa_client_install.hardcode_ldap_server
+            get_certs_from_ldap = ipa_client_install.get_certs_from_ldap
+            save_state = ipa_client_install.save_state
+
+            create_ipa_nssdb = certdb.create_ipa_nssdb
+
+            argspec = \
+                inspect.getargspec(ipa_client_install.configure_nisdomain)
+            if len(argspec.args) == 3:
+                configure_nisdomain = ipa_client_install.configure_nisdomain
+            else:
+                def configure_nisdomain(options, domain, statestore=None):
+                    return ipa_client_install.configure_nisdomain(options,
+                                                                  domain)
+
+            configure_ldap_conf = ipa_client_install.configure_ldap_conf
+            configure_nslcd_conf = ipa_client_install.configure_nslcd_conf
+            nosssd_files = ipa_client_install.nosssd_files
+
+            configure_ssh_config = ipa_client_install.configure_ssh_config
+            configure_sshd_config = ipa_client_install.configure_sshd_config
+            configure_automount = ipa_client_install.configure_automount
+            configure_firefox = ipa_client_install.configure_firefox
+
+        from ipapython.ipautil import realm_to_suffix, run
+
         try:
-            from ipaclient.install import ntpconf as timeconf
+            from ipaclient.install import timeconf
+            time_service = "chronyd"
         except ImportError:
-            from ipaclient import ntpconf as timeconf
-        time_service = "ntpd"
+            try:
+                from ipaclient.install import ntpconf as timeconf
+            except ImportError:
+                from ipaclient import ntpconf as timeconf
+            time_service = "ntpd"
 
-    try:
-        from ipaclient.install.client import sync_time
-    except ImportError:
-        sync_time = None
+        try:
+            from ipaclient.install.client import sync_time
+        except ImportError:
+            sync_time = None
 
-    try:
-        from ipaclient.install.client import check_ldap_conf
-    except ImportError:
-        check_ldap_conf = None
+        try:
+            from ipaclient.install.client import check_ldap_conf
+        except ImportError:
+            check_ldap_conf = None
 
-    try:
-        from ipaclient.install.client import sssd_enable_ifp
-    except ImportError:
-        sssd_enable_ifp = None
+        try:
+            from ipaclient.install.client import sssd_enable_ifp
+        except ImportError:
+            sssd_enable_ifp = None
 
-    logger = logging.getLogger("ipa-client-install")
-    root_logger = logger
+        logger = logging.getLogger("ipa-client-install")
+        root_logger = logger
 
-else:
-    # IPA version < 4.4
+    else:
+        # IPA version < 4.4
 
-    raise Exception("freeipa version '%s' is too old" % VERSION)
+        raise Exception("freeipa version '%s' is too old" % VERSION)
 
 
 def setup_logging():
diff --git a/roles/ipareplica/module_utils/ansible_ipa_replica.py b/roles/ipareplica/module_utils/ansible_ipa_replica.py
index b0f0d03456a66afaf11b3ba87da2ec6c686df391..1de75e1a46576fa882476ffe573240533d36edf0 100644
--- a/roles/ipareplica/module_utils/ansible_ipa_replica.py
+++ b/roles/ipareplica/module_utils/ansible_ipa_replica.py
@@ -46,379 +46,383 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
            "dnsname", "kernel_keyring", "krbinstance"]
 
 import sys
-import logging
-from contextlib import contextmanager as contextlib_contextmanager
 
-
-from ipapython.version import NUM_VERSION, VERSION
-
-if NUM_VERSION < 30201:
-    # See ipapython/version.py
-    IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)]
-    IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
+# HACK: workaround for Ansible 2.9
+# https://github.com/ansible/ansible/issues/68361
+if 'ansible.executor' in sys.modules:
+    for attr in __all__:
+        setattr(sys.modules[__name__], attr, None)
 else:
-    IPA_PYTHON_VERSION = NUM_VERSION
-
-
-if NUM_VERSION >= 40600:
-    # IPA version >= 4.6
-
-    import contextlib
-
-    import dns.exception as dnsexception
-    import dns.name as dnsname
-    import dns.resolver as dnsresolver
-    import dns.reversename as dnsreversename
-
-    from pkg_resources import parse_version
-
-    from ipaclient.install.ipachangeconf import IPAChangeConf
-    from ipalib.install import certstore, sysrestore
-    from ipapython.ipautil import ipa_generate_password
-    from ipalib.install.kinit import kinit_keytab
-    from ipapython import ipaldap, ipautil, kernel_keyring
-    from ipapython.certdb import IPA_CA_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS
-    from ipapython.dn import DN
-    from ipapython.admintool import ScriptError
-    from ipapython.ipa_log_manager import standard_logging_setup
-    from ipaplatform import services
-    from ipaplatform.tasks import tasks
-    from ipaplatform.paths import paths
-    from ipalib import api, constants, create_api, errors, rpc, x509
-    from ipalib.config import Env
-    from ipalib.util import (
-        validate_domain_name,
-        no_matching_interface_for_ip_address_warning)
-    from ipaclient.install.client import configure_krb5_conf, purge_host_keytab
-    from ipaserver.install import (
-        adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance,
-        installutils, kra, krbinstance,
-        otpdinstance, custodiainstance, service, upgradeinstance)
-    try:
-        from ipaserver.masters import (
-            find_providing_servers, find_providing_server)
-    except ImportError:
-        from ipaserver.install.service import (
-            find_providing_servers, find_providing_server)
-    from ipaserver.install.installutils import (
-        ReplicaConfig, load_pkcs12)
-    try:
-        from ipalib.facts import is_ipa_configured
-    except ImportError:
-        from ipaserver.install.installutils import is_ipa_configured
-    from ipaserver.install.replication import (
-        ReplicationManager, replica_conn_check)
-    from ipaserver.install.server.replicainstall import (
-        make_pkcs12_info, install_replica_ds, install_krb, install_ca_cert,
-        install_http, install_dns_records, create_ipa_conf, check_dirsrv,
-        check_dns_resolution, configure_certmonger, remove_replica_info_dir,
-        # common_cleanup,
-        preserve_enrollment_state, uninstall_client,
-        promote_sssd, promote_openldap_conf, rpc_client,
-        check_remote_fips_mode, check_remote_version, common_check,
-        current_domain_level, check_domain_level_is_supported,
-        # enroll_dl0_replica,
-        # ensure_enrolled,
-        promotion_check_ipa_domain
-    )
-    import SSSDConfig
-    from subprocess import CalledProcessError
-
-    try:
-        from ipaclient.install import timeconf
-        time_service = "chronyd"
-        ntpinstance = None
-    except ImportError:
+    import logging
+    from contextlib import contextmanager as contextlib_contextmanager
+
+    from ipapython.version import NUM_VERSION, VERSION
+
+    if NUM_VERSION < 30201:
+        # See ipapython/version.py
+        IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in
+                                             VERSION.split(".", 2)]
+        IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
+    else:
+        IPA_PYTHON_VERSION = NUM_VERSION
+
+    if NUM_VERSION >= 40600:
+        # IPA version >= 4.6
+
+        import contextlib
+
+        import dns.exception as dnsexception
+        import dns.name as dnsname
+        import dns.resolver as dnsresolver
+        import dns.reversename as dnsreversename
+
+        from pkg_resources import parse_version
+
+        from ipaclient.install.ipachangeconf import IPAChangeConf
+        from ipalib.install import certstore, sysrestore
+        from ipapython.ipautil import ipa_generate_password
+        from ipalib.install.kinit import kinit_keytab
+        from ipapython import ipaldap, ipautil, kernel_keyring
+        from ipapython.certdb import IPA_CA_TRUST_FLAGS, \
+            EXTERNAL_CA_TRUST_FLAGS
+        from ipapython.dn import DN
+        from ipapython.admintool import ScriptError
+        from ipapython.ipa_log_manager import standard_logging_setup
+        from ipaplatform import services
+        from ipaplatform.tasks import tasks
+        from ipaplatform.paths import paths
+        from ipalib import api, constants, create_api, errors, rpc, x509
+        from ipalib.config import Env
+        from ipalib.util import (
+            validate_domain_name,
+            no_matching_interface_for_ip_address_warning)
+        from ipaclient.install.client import configure_krb5_conf, \
+            purge_host_keytab
+        from ipaserver.install import (
+            adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance,
+            installutils, kra, krbinstance,
+            otpdinstance, custodiainstance, service, upgradeinstance)
+        try:
+            from ipaserver.masters import (
+                find_providing_servers, find_providing_server)
+        except ImportError:
+            from ipaserver.install.service import (
+                find_providing_servers, find_providing_server)
+        from ipaserver.install.installutils import (
+            ReplicaConfig, load_pkcs12)
+        try:
+            from ipalib.facts import is_ipa_configured
+        except ImportError:
+            from ipaserver.install.installutils import is_ipa_configured
+        from ipaserver.install.replication import (
+            ReplicationManager, replica_conn_check)
+        from ipaserver.install.server.replicainstall import (
+            make_pkcs12_info, install_replica_ds, install_krb, install_ca_cert,
+            install_http, install_dns_records, create_ipa_conf, check_dirsrv,
+            check_dns_resolution, configure_certmonger,
+            remove_replica_info_dir,
+            # common_cleanup,
+            preserve_enrollment_state, uninstall_client,
+            promote_sssd, promote_openldap_conf, rpc_client,
+            check_remote_fips_mode, check_remote_version, common_check,
+            current_domain_level, check_domain_level_is_supported,
+            # enroll_dl0_replica,
+            # ensure_enrolled,
+            promotion_check_ipa_domain
+        )
+        import SSSDConfig
+        from subprocess import CalledProcessError
+
         try:
-            from ipaclient.install import ntpconf as timeconf
+            from ipaclient.install import timeconf
+            time_service = "chronyd"
+            ntpinstance = None
         except ImportError:
-            from ipaclient import ntpconf as timeconf
-        from ipaserver.install import ntpinstance
-        time_service = "ntpd"
+            try:
+                from ipaclient.install import ntpconf as timeconf
+            except ImportError:
+                from ipaclient import ntpconf as timeconf
+            from ipaserver.install import ntpinstance
+            time_service = "ntpd"
 
+    else:
+        # IPA version < 4.6
 
-else:
-    # IPA version < 4.6
-
-    raise Exception("freeipa version '%s' is too old" % VERSION)
-
-
-logger = logging.getLogger("ipa-server-install")
-
-
-def setup_logging():
-    # logger.setLevel(logging.DEBUG)
-    standard_logging_setup(
-        paths.IPAREPLICA_INSTALL_LOG, verbose=False, debug=False,
-        filemode='a', console_format='%(message)s')
-
-
-@contextlib_contextmanager
-def redirect_stdout(f):
-    sys.stdout = f
-    try:
-        yield f
-    finally:
-        sys.stdout = sys.__stdout__
-
-
-class AnsibleModuleLog():
-    def __init__(self, module):
-        self.module = module
-        _ansible_module_log = self
-
-        class AnsibleLoggingHandler(logging.Handler):
-            def emit(self, record):
-                _ansible_module_log.write(self.format(record))
-
-        self.logging_handler = AnsibleLoggingHandler()
-        logger.setLevel(logging.DEBUG)
-        logger.root.addHandler(self.logging_handler)
-
-    def close(self):
-        self.flush()
-
-    def flush(self):
-        pass
-
-    def log(self, msg):
-        # self.write(msg+"\n")
-        self.write(msg)
-
-    def debug(self, msg):
-        self.module.debug(msg)
-
-    def info(self, msg):
-        self.module.debug(msg)
-
-    def write(self, msg):
-        self.module.debug(msg)
-        # self.module.warn(msg)
-
-
-class installer_obj(object):
-    def __init__(self):
-        # CompatServerReplicaInstall
-        self.ca_cert_files = None
-        self.all_ip_addresses = False
-        self.no_wait_for_dns = True
-        self.nisdomain = None
-        self.no_nisdomain = False
-        self.no_sudo = False
-        self.request_cert = False
-        self.ca_file = None
-        self.zonemgr = None
-        self.replica_file = None
-        # ServerReplicaInstall
-        self.subject_base = None
-        self.ca_subject = None
-        # others
-        self._ccache = None
-        self.password = None
-        self.reverse_zones = []
-        # def _is_promote(self):
-        #     return self.replica_file is None
-        # self.skip_conncheck = False
-        self._replica_install = False
-        # self.dnssec_master = False # future unknown
-        # self.disable_dnssec_master = False # future unknown
-        # self.domainlevel = MAX_DOMAIN_LEVEL # deprecated
-        # self.domain_level = self.domainlevel # deprecated
-        self.interactive = False
-        self.unattended = not self.interactive
-        # self.promote = self.replica_file is None
-        self.promote = True
-        self.skip_schema_check = None
-
-    # def __getattribute__(self, attr):
-    #     value = super(installer_obj, self).__getattribute__(attr)
-    #     if not attr.startswith("--") and not attr.endswith("--"):
-    #         logger.debug(
-    #             "  <-- Accessing installer.%s (%s)" % (attr, repr(value)))
-    #     return value
-
-    def __getattr__(self, attr):
-        logger.info("  --> ADDING missing installer.%s", attr)
-        setattr(self, attr, None)
-        return getattr(self, attr)
-
-    # def __setattr__(self, attr, value):
-    #    logger.debug("  --> Setting installer.%s to %s" % (attr, repr(value)))
-    #    return super(installer_obj, self).__setattr__(attr, value)
-
-    def knobs(self):
-        for name in self.__dict__:
-            yield self, name
-
-
-installer = installer_obj()
-options = installer
-
-# DNSInstallInterface
-options.dnssec_master = False
-options.disable_dnssec_master = False
-options.kasp_db_file = None
-options.force = False
-
-# ServerMasterInstall
-options.add_sids = False
-options.add_agents = False
-
-# ServerReplicaInstall
-options.subject_base = None
-options.ca_subject = None
-
-
-def gen_env_boostrap_finalize_core(etc_ipa, default_config):
-    env = Env()
-    # env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
-    # env._finalize_core(**dict(constants.DEFAULT_CONFIG))
-    env._bootstrap(context='installer', confdir=etc_ipa, log=None)
-    env._finalize_core(**dict(default_config))
-    return env
-
-
-def api_bootstrap_finalize(env):
-    # pylint: disable=no-member
-    xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host))
-    api.bootstrap(in_server=True,
-                  context='installer',
-                  confdir=paths.ETC_IPA,
-                  ldap_uri=installutils.realm_to_ldapi_uri(env.realm),
-                  xmlrpc_uri=xmlrpc_uri)
-    # pylint: enable=no-member
-    api.finalize()
-
-
-def gen_ReplicaConfig():
-    class ExtendedReplicaConfig(ReplicaConfig):
-        def __init__(self, top_dir=None):
-            super(ExtendedReplicaConfig, self).__init__(top_dir)
+        raise Exception("freeipa version '%s' is too old" % VERSION)
+
+    logger = logging.getLogger("ipa-server-install")
+
+    def setup_logging():
+        # logger.setLevel(logging.DEBUG)
+        standard_logging_setup(
+            paths.IPAREPLICA_INSTALL_LOG, verbose=False, debug=False,
+            filemode='a', console_format='%(message)s')
+
+    @contextlib_contextmanager
+    def redirect_stdout(f):
+        sys.stdout = f
+        try:
+            yield f
+        finally:
+            sys.stdout = sys.__stdout__
+
+    class AnsibleModuleLog():
+        def __init__(self, module):
+            self.module = module
+            _ansible_module_log = self
+
+            class AnsibleLoggingHandler(logging.Handler):
+                def emit(self, record):
+                    _ansible_module_log.write(self.format(record))
+
+            self.logging_handler = AnsibleLoggingHandler()
+            logger.setLevel(logging.DEBUG)
+            logger.root.addHandler(self.logging_handler)
+
+        def close(self):
+            self.flush()
+
+        def flush(self):
+            pass
+
+        def log(self, msg):
+            # self.write(msg+"\n")
+            self.write(msg)
+
+        def debug(self, msg):
+            self.module.debug(msg)
+
+        def info(self, msg):
+            self.module.debug(msg)
+
+        def write(self, msg):
+            self.module.debug(msg)
+            # self.module.warn(msg)
+
+    class installer_obj(object):
+        def __init__(self):
+            # CompatServerReplicaInstall
+            self.ca_cert_files = None
+            self.all_ip_addresses = False
+            self.no_wait_for_dns = True
+            self.nisdomain = None
+            self.no_nisdomain = False
+            self.no_sudo = False
+            self.request_cert = False
+            self.ca_file = None
+            self.zonemgr = None
+            self.replica_file = None
+            # ServerReplicaInstall
+            self.subject_base = None
+            self.ca_subject = None
+            # others
+            self._ccache = None
+            self.password = None
+            self.reverse_zones = []
+            # def _is_promote(self):
+            #     return self.replica_file is None
+            # self.skip_conncheck = False
+            self._replica_install = False
+            # self.dnssec_master = False # future unknown
+            # self.disable_dnssec_master = False # future unknown
+            # self.domainlevel = MAX_DOMAIN_LEVEL # deprecated
+            # self.domain_level = self.domainlevel # deprecated
+            self.interactive = False
+            self.unattended = not self.interactive
+            # self.promote = self.replica_file is None
+            self.promote = True
+            self.skip_schema_check = None
 
         # def __getattribute__(self, attr):
-        #    value = super(ExtendedReplicaConfig, self).__getattribute__(attr)
-        #    if attr not in ["__dict__", "knobs"]:
-        #        logger.debug("  <== Accessing config.%s (%s)" %
-        #                     (attr, repr(value)))
-        #    return value
+        #     value = super(installer_obj, self).__getattribute__(attr)
+        #     if not attr.startswith("--") and not attr.endswith("--"):
+        #         logger.debug(
+        #             "  <-- Accessing installer.%s (%s)" %
+        #             (attr, repr(value)))
+        #     return value
 
         def __getattr__(self, attr):
-            logger.info("  ==> ADDING missing config.%s", attr)
+            logger.info("  --> ADDING missing installer.%s", attr)
             setattr(self, attr, None)
             return getattr(self, attr)
 
         # def __setattr__(self, attr, value):
-        #   logger.debug("  ==> Setting config.%s to %s" % (attr, repr(value)))
-        #   return super(ExtendedReplicaConfig, self).__setattr__(attr, value)
+        #    logger.debug("  --> Setting installer.%s to %s" %
+        #                 (attr, repr(value)))
+        #    return super(installer_obj, self).__setattr__(attr, value)
 
         def knobs(self):
             for name in self.__dict__:
                 yield self, name
 
-    # config = ReplicaConfig()
-    config = ExtendedReplicaConfig()
-    config.realm_name = api.env.realm
-    config.host_name = api.env.host
-    config.domain_name = api.env.domain
-    config.master_host_name = api.env.server
-    config.ca_host_name = api.env.ca_host
-    config.kra_host_name = config.ca_host_name
-    config.ca_ds_port = 389
-    config.setup_ca = options.setup_ca
-    config.setup_kra = options.setup_kra
-    config.dir = options._top_dir
-    config.basedn = api.env.basedn
-    # config.subject_base = options.subject_base
-
-    return config
-
-
-def replica_ds_init_info(ansible_log,
-                         config, options, ca_is_configured, remote_api,
-                         ds_ca_subject, ca_file,
-                         promote=False, pkcs12_info=None):
-
-    dsinstance.check_ports()
-
-    # if we have a pkcs12 file, create the cert db from
-    # that. Otherwise the ds setup will create the CA
-    # cert
-    if pkcs12_info is None:
-        pkcs12_info = make_pkcs12_info(config.dir, "dscert.p12",
-                                       "dirsrv_pin.txt")
-
-    # during replica install, this gets invoked before local DS is
-    # available, so use the remote api.
-    # if ca_is_configured:
-    #     ca_subject = ca.lookup_ca_subject(_api, config.subject_base)
-    # else:
-    #     ca_subject = installutils.default_ca_subject_dn(config.subject_base)
-    ca_subject = ds_ca_subject
-
-    ds = dsinstance.DsInstance(
-        config_ldif=options.dirsrv_config_file)
-    ds.set_output(ansible_log)
-
-    # Source: ipaserver/install/dsinstance.py
-
-    # idstart and idmax are configured so that the range is seen as
-    # depleted by the DNA plugin and the replica will go and get a
-    # new range from the master.
-    # This way all servers use the initially defined range by default.
-    idstart = 1101
-    idmax = 1100
-
-    with redirect_stdout(ansible_log):
-        ds.init_info(
-            realm_name=config.realm_name,
-            fqdn=config.host_name,
-            domain_name=config.domain_name,
-            dm_password=config.dirman_password,
-            subject_base=config.subject_base,
-            ca_subject=ca_subject,
-            idstart=idstart,
-            idmax=idmax,
-            pkcs12_info=pkcs12_info,
-            ca_file=ca_file,
-            setup_pkinit=not options.no_pkinit,
-        )
-    ds.master_fqdn = config.master_host_name
-    if ca_is_configured is not None:
-        ds.ca_is_configured = ca_is_configured
-    ds.promote = promote
-    ds.api = remote_api
-
-    # from __setup_replica
-
-    # Always connect to ds over ldapi
-    ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=ds.realm)
-    conn = ipaldap.LDAPClient(ldap_uri)
-    conn.external_bind()
-
-    return ds
-
-
-def ansible_module_get_parsed_ip_addresses(ansible_module,
-                                           param='ip_addresses'):
-    ip_addrs = []
-    for ip in ansible_module.params.get(param):
-        try:
-            ip_parsed = ipautil.CheckedIPAddress(ip)
-        except Exception as e:
-            ansible_module.fail_json(msg="Invalid IP Address %s: %s" % (ip, e))
-        ip_addrs.append(ip_parsed)
-    return ip_addrs
-
-
-def gen_remote_api(master_host_name, etc_ipa):
-    ldapuri = 'ldaps://%s' % ipautil.format_netloc(master_host_name)
-    xmlrpc_uri = 'https://{}/ipa/xml'.format(
-        ipautil.format_netloc(master_host_name))
-    remote_api = create_api(mode=None)
-    remote_api.bootstrap(in_server=True,
-                         context='installer',
-                         confdir=etc_ipa,
-                         ldap_uri=ldapuri,
-                         xmlrpc_uri=xmlrpc_uri)
-    remote_api.finalize()
-    return remote_api
+    installer = installer_obj()
+    options = installer
+
+    # DNSInstallInterface
+    options.dnssec_master = False
+    options.disable_dnssec_master = False
+    options.kasp_db_file = None
+    options.force = False
+
+    # ServerMasterInstall
+    options.add_sids = False
+    options.add_agents = False
+
+    # ServerReplicaInstall
+    options.subject_base = None
+    options.ca_subject = None
+
+    def gen_env_boostrap_finalize_core(etc_ipa, default_config):
+        env = Env()
+        # env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
+        # env._finalize_core(**dict(constants.DEFAULT_CONFIG))
+        env._bootstrap(context='installer', confdir=etc_ipa, log=None)
+        env._finalize_core(**dict(default_config))
+        return env
+
+    def api_bootstrap_finalize(env):
+        # pylint: disable=no-member
+        xmlrpc_uri = \
+            'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host))
+        api.bootstrap(in_server=True,
+                      context='installer',
+                      confdir=paths.ETC_IPA,
+                      ldap_uri=installutils.realm_to_ldapi_uri(env.realm),
+                      xmlrpc_uri=xmlrpc_uri)
+        # pylint: enable=no-member
+        api.finalize()
+
+    def gen_ReplicaConfig():
+        class ExtendedReplicaConfig(ReplicaConfig):
+            def __init__(self, top_dir=None):
+                super(ExtendedReplicaConfig, self).__init__(top_dir)
+
+            # def __getattribute__(self, attr):
+            #     value = super(ExtendedReplicaConfig, self).__getattribute__(
+            #         attr)
+            #    if attr not in ["__dict__", "knobs"]:
+            #        logger.debug("  <== Accessing config.%s (%s)" %
+            #                     (attr, repr(value)))
+            #    return value
+
+            def __getattr__(self, attr):
+                logger.info("  ==> ADDING missing config.%s", attr)
+                setattr(self, attr, None)
+                return getattr(self, attr)
+
+            # def __setattr__(self, attr, value):
+            #   logger.debug("  ==> Setting config.%s to %s" %
+            #                (attr, repr(value)))
+            #   return super(ExtendedReplicaConfig, self).__setattr__(attr,
+            #                                                         value)
+
+            def knobs(self):
+                for name in self.__dict__:
+                    yield self, name
+
+        # config = ReplicaConfig()
+        config = ExtendedReplicaConfig()
+        config.realm_name = api.env.realm
+        config.host_name = api.env.host
+        config.domain_name = api.env.domain
+        config.master_host_name = api.env.server
+        config.ca_host_name = api.env.ca_host
+        config.kra_host_name = config.ca_host_name
+        config.ca_ds_port = 389
+        config.setup_ca = options.setup_ca
+        config.setup_kra = options.setup_kra
+        config.dir = options._top_dir
+        config.basedn = api.env.basedn
+        # config.subject_base = options.subject_base
+
+        return config
+
+    def replica_ds_init_info(ansible_log,
+                             config, options, ca_is_configured, remote_api,
+                             ds_ca_subject, ca_file,
+                             promote=False, pkcs12_info=None):
+
+        dsinstance.check_ports()
+
+        # if we have a pkcs12 file, create the cert db from
+        # that. Otherwise the ds setup will create the CA
+        # cert
+        if pkcs12_info is None:
+            pkcs12_info = make_pkcs12_info(config.dir, "dscert.p12",
+                                           "dirsrv_pin.txt")
+
+        # during replica install, this gets invoked before local DS is
+        # available, so use the remote api.
+        # if ca_is_configured:
+        #     ca_subject = ca.lookup_ca_subject(_api, config.subject_base)
+        # else:
+        #     ca_subject = installutils.default_ca_subject_dn(
+        #         config.subject_base)
+        ca_subject = ds_ca_subject
+
+        ds = dsinstance.DsInstance(
+            config_ldif=options.dirsrv_config_file)
+        ds.set_output(ansible_log)
+
+        # Source: ipaserver/install/dsinstance.py
+
+        # idstart and idmax are configured so that the range is seen as
+        # depleted by the DNA plugin and the replica will go and get a
+        # new range from the master.
+        # This way all servers use the initially defined range by default.
+        idstart = 1101
+        idmax = 1100
+
+        with redirect_stdout(ansible_log):
+            ds.init_info(
+                realm_name=config.realm_name,
+                fqdn=config.host_name,
+                domain_name=config.domain_name,
+                dm_password=config.dirman_password,
+                subject_base=config.subject_base,
+                ca_subject=ca_subject,
+                idstart=idstart,
+                idmax=idmax,
+                pkcs12_info=pkcs12_info,
+                ca_file=ca_file,
+                setup_pkinit=not options.no_pkinit,
+            )
+        ds.master_fqdn = config.master_host_name
+        if ca_is_configured is not None:
+            ds.ca_is_configured = ca_is_configured
+        ds.promote = promote
+        ds.api = remote_api
+
+        # from __setup_replica
+
+        # Always connect to ds over ldapi
+        ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=ds.realm)
+        conn = ipaldap.LDAPClient(ldap_uri)
+        conn.external_bind()
+
+        return ds
+
+    def ansible_module_get_parsed_ip_addresses(ansible_module,
+                                               param='ip_addresses'):
+        ip_addrs = []
+        for ip in ansible_module.params.get(param):
+            try:
+                ip_parsed = ipautil.CheckedIPAddress(ip)
+            except Exception as e:
+                ansible_module.fail_json(
+                    msg="Invalid IP Address %s: %s" % (ip, e))
+            ip_addrs.append(ip_parsed)
+        return ip_addrs
+
+    def gen_remote_api(master_host_name, etc_ipa):
+        ldapuri = 'ldaps://%s' % ipautil.format_netloc(master_host_name)
+        xmlrpc_uri = 'https://{}/ipa/xml'.format(
+            ipautil.format_netloc(master_host_name))
+        remote_api = create_api(mode=None)
+        remote_api.bootstrap(in_server=True,
+                             context='installer',
+                             confdir=etc_ipa,
+                             ldap_uri=ldapuri,
+                             xmlrpc_uri=xmlrpc_uri)
+        remote_api.finalize()
+        return remote_api
diff --git a/roles/ipaserver/module_utils/ansible_ipa_server.py b/roles/ipaserver/module_utils/ansible_ipa_server.py
index fcecd0df21061dbdc2f9f7534b69fc3a32f058a4..a5310f88c3ad89d081182a456e5fffde6d811645 100644
--- a/roles/ipaserver/module_utils/ansible_ipa_server.py
+++ b/roles/ipaserver/module_utils/ansible_ipa_server.py
@@ -41,352 +41,349 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
            "check_available_memory"]
 
 import sys
-import logging
-from contextlib import contextmanager as contextlib_contextmanager
-import six
-import base64
 
-
-from ipapython.version import NUM_VERSION, VERSION
-
-if NUM_VERSION < 30201:
-    # See ipapython/version.py
-    IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in VERSION.split(".", 2)]
-    IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
-else:
-    IPA_PYTHON_VERSION = NUM_VERSION
-
-
-if NUM_VERSION >= 40500:
-    # IPA version >= 4.5
-
-    from ipaclient.install.ipachangeconf import IPAChangeConf
-    from ipalib.install import certmonger
-    try:
-        from ipalib import sysrestore
-    except ImportError:
-        from ipalib.install import sysrestore
-    from ipapython import ipautil
-    from ipapython.ipa_log_manager import standard_logging_setup
-    try:
-        from ipapython.ipa_log_manager import root_logger
-    except ImportError:
-        root_logger = None
-    from ipapython.ipautil import (
-        ipa_generate_password, run)
-    from ipapython.admintool import ScriptError
-    from ipaplatform import services
-    from ipaplatform.paths import paths
-    from ipaplatform.tasks import tasks
-    from ipalib import api, errors, x509
-    from ipalib.constants import DOMAIN_LEVEL_0, MIN_DOMAIN_LEVEL, \
-        MAX_DOMAIN_LEVEL
-    try:
-        from ipalib.constants import IPAAPI_USER
-    except ImportError:
-        IPAAPI_USER = None
-    from ipalib.util import (
-        validate_domain_name,
-        no_matching_interface_for_ip_address_warning,
-    )
-    from ipapython.dnsutil import check_zone_overlap
-    from ipapython.dn import DN
-    try:
-        from ipaclient.install import timeconf
-        from ipaclient.install.client import sync_time
-        time_service = "chronyd"
-        ntpinstance = None
-    except ImportError:
-        try:
-            from ipaclient.install import ntpconf as timeconf
-        except ImportError:
-            from ipaclient import ntpconf as timeconf
-        from ipaserver.install import ntpinstance
-        time_service = "ntpd"
-        sync_time = None
-    from ipaserver.install import (
-        adtrust, bindinstance, ca, dns, dsinstance,
-        httpinstance, installutils, kra, krbinstance,
-        otpdinstance, custodiainstance, replication, service,
-        sysupgrade)
-    adtrust_imported = True
-    kra_imported = True
-    from ipaserver.install.installutils import (
-        BadHostError, get_fqdn, get_server_ip_address,
-        load_pkcs12, read_password, verify_fqdn,
-        update_hosts_file)
-    try:
-        from ipalib.facts import is_ipa_configured
-    except ImportError:
-        from ipaserver.install.installutils import is_ipa_configured
-    from ipaserver.install.server.install import (
-        check_dirsrv, validate_admin_password, validate_dm_password,
-        read_cache, write_cache)
-    try:
-        from ipaserver.install.dogtaginstance import PKIIniLoader
-    except ImportError:
-        PKIIniLoader = None
-    try:
-        from ipaserver.install.installutils import default_subject_base
-    except ImportError:
-        def default_subject_base(realm_name):
-            return DN(('O', realm_name))
-    try:
-        from ipalib.facts import IPA_MODULES
-    except ImportError:
-        from ipaserver.install.installutils import IPA_MODULES
-    try:
-        from ipaserver.install.installutils import default_ca_subject_dn
-    except ImportError:
-        def default_ca_subject_dn(subject_base):
-            return DN(('CN', 'Certificate Authority'), subject_base)
-    try:
-        from ipaserver.install.installutils import check_available_memory
-    except ImportError:
-        check_available_memory = None
-
-    try:
-        from ipaserver.install import adtrustinstance
-        _server_trust_ad_installed = True
-    except ImportError:
-        _server_trust_ad_installed = False
-
-    try:
-        from ipaclient.install.client import check_ldap_conf
-    except ImportError:
-        check_ldap_conf = None
-
-    try:
-        from ipalib.x509 import Encoding
-    except ImportError:
-        from cryptography.hazmat.primitives.serialization import Encoding
-
-    try:
-        from ipalib.x509 import load_pem_x509_certificate
-    except ImportError:
-        from ipalib.x509 import load_certificate
-        load_pem_x509_certificate = None
+# HACK: workaround for Ansible 2.9
+# https://github.com/ansible/ansible/issues/68361
+if 'ansible.executor' in sys.modules:
+    for attr in __all__:
+        setattr(sys.modules[__name__], attr, None)
 
 else:
-    # IPA version < 4.5
-
-    raise Exception("freeipa version '%s' is too old" % VERSION)
-
-
-logger = logging.getLogger("ipa-server-install")
-
-
-def setup_logging():
-    # logger.setLevel(logging.DEBUG)
-    standard_logging_setup(
-        paths.IPASERVER_INSTALL_LOG, verbose=False, debug=False,
-        filemode='a', console_format='%(message)s')
-
-
-@contextlib_contextmanager
-def redirect_stdout(f):
-    sys.stdout = f
-    try:
-        yield f
-    finally:
-        sys.stdout = sys.__stdout__
-
-
-class AnsibleModuleLog():
-    def __init__(self, module):
-        self.module = module
-        _ansible_module_log = self
-
-        class AnsibleLoggingHandler(logging.Handler):
-            def emit(self, record):
-                _ansible_module_log.write(self.format(record))
-
-        self.logging_handler = AnsibleLoggingHandler()
-        logger.setLevel(logging.DEBUG)
-        logger.root.addHandler(self.logging_handler)
-
-    def close(self):
-        self.flush()
 
-    def flush(self):
-        pass
+    import logging
+    from contextlib import contextmanager as contextlib_contextmanager
+    import six
+    import base64
 
-    def log(self, msg):
-        # self.write(msg+"\n")
-        self.write(msg)
+    from ipapython.version import NUM_VERSION, VERSION
 
-    def debug(self, msg):
-        self.module.debug(msg)
-
-    def info(self, msg):
-        self.module.debug(msg)
-
-    def write(self, msg):
-        self.module.debug(msg)
-        # self.module.warn(msg)
-
-
-class options_obj(object):
-    def __init__(self):
-        self._replica_install = False
-        self.dnssec_master = False  # future unknown
-        self.disable_dnssec_master = False  # future unknown
-        self.domainlevel = MAX_DOMAIN_LEVEL  # deprecated
-        self.domain_level = self.domainlevel  # deprecated
-        self.interactive = False
-        self.unattended = not self.interactive
-
-    # def __getattribute__(self, attr):
-    #    logger.info(" <-- Accessing options.%s" % attr)
-    #    return super(options_obj, self).__getattribute__(attr)
-
-    # def __getattr__(self, attr):
-    #    logger.info(" --> Adding missing options.%s" % attr)
-    #    setattr(self, attr, None)
-    #    return getattr(self, attr)
-
-    def knobs(self):
-        for name in self.__dict__:
-            yield self, name
-
-
-options = options_obj()
-installer = options
-
-# ServerMasterInstall
-options.add_sids = True
-options.add_agents = False
-
-
-# Installable
-options.uninstalling = False
+    if NUM_VERSION < 30201:
+        # See ipapython/version.py
+        IPA_MAJOR, IPA_MINOR, IPA_RELEASE = [int(x) for x in
+                                             VERSION.split(".", 2)]
+        IPA_PYTHON_VERSION = IPA_MAJOR*10000 + IPA_MINOR*100 + IPA_RELEASE
+    else:
+        IPA_PYTHON_VERSION = NUM_VERSION
 
-# ServerInstallInterface
-options.description = "Server"
+    if NUM_VERSION >= 40500:
+        # IPA version >= 4.5
 
-options.kinit_attempts = 1
-options.fixed_primary = True
-options.permit = False
-options.enable_dns_updates = False
-options.no_krb5_offline_passwords = False
-options.preserve_sssd = False
-options.no_sssd = False
+        from ipaclient.install.ipachangeconf import IPAChangeConf
+        from ipalib.install import certmonger
+        try:
+            from ipalib import sysrestore
+        except ImportError:
+            from ipalib.install import sysrestore
+        from ipapython import ipautil
+        from ipapython.ipa_log_manager import standard_logging_setup
+        try:
+            from ipapython.ipa_log_manager import root_logger
+        except ImportError:
+            root_logger = None
+        from ipapython.ipautil import (
+            ipa_generate_password, run)
+        from ipapython.admintool import ScriptError
+        from ipaplatform import services
+        from ipaplatform.paths import paths
+        from ipaplatform.tasks import tasks
+        from ipalib import api, errors, x509
+        from ipalib.constants import DOMAIN_LEVEL_0, MIN_DOMAIN_LEVEL, \
+            MAX_DOMAIN_LEVEL
+        try:
+            from ipalib.constants import IPAAPI_USER
+        except ImportError:
+            IPAAPI_USER = None
+        from ipalib.util import (
+            validate_domain_name,
+            no_matching_interface_for_ip_address_warning,
+        )
+        from ipapython.dnsutil import check_zone_overlap
+        from ipapython.dn import DN
+        try:
+            from ipaclient.install import timeconf
+            from ipaclient.install.client import sync_time
+            time_service = "chronyd"
+            ntpinstance = None
+        except ImportError:
+            try:
+                from ipaclient.install import ntpconf as timeconf
+            except ImportError:
+                from ipaclient import ntpconf as timeconf
+            from ipaserver.install import ntpinstance
+            time_service = "ntpd"
+            sync_time = None
+        from ipaserver.install import (
+            adtrust, bindinstance, ca, dns, dsinstance,
+            httpinstance, installutils, kra, krbinstance,
+            otpdinstance, custodiainstance, replication, service,
+            sysupgrade)
+        adtrust_imported = True
+        kra_imported = True
+        from ipaserver.install.installutils import (
+            BadHostError, get_fqdn, get_server_ip_address,
+            load_pkcs12, read_password, verify_fqdn,
+            update_hosts_file)
+        try:
+            from ipalib.facts import is_ipa_configured
+        except ImportError:
+            from ipaserver.install.installutils import is_ipa_configured
+        from ipaserver.install.server.install import (
+            check_dirsrv, validate_admin_password, validate_dm_password,
+            read_cache, write_cache)
+        try:
+            from ipaserver.install.dogtaginstance import PKIIniLoader
+        except ImportError:
+            PKIIniLoader = None
+        try:
+            from ipaserver.install.installutils import default_subject_base
+        except ImportError:
+            def default_subject_base(realm_name):
+                return DN(('O', realm_name))
+        try:
+            from ipalib.facts import IPA_MODULES
+        except ImportError:
+            from ipaserver.install.installutils import IPA_MODULES
+        try:
+            from ipaserver.install.installutils import default_ca_subject_dn
+        except ImportError:
+            def default_ca_subject_dn(subject_base):
+                return DN(('CN', 'Certificate Authority'), subject_base)
+        try:
+            from ipaserver.install.installutils import check_available_memory
+        except ImportError:
+            check_available_memory = None
 
-# ServerMasterInstall
-options.force_join = False
-options.servers = None
-options.no_wait_for_dns = True
-options.host_password = None
-options.keytab = None
-options.setup_ca = True
-# always run sidgen task and do not allow adding agents on first master
-options.add_sids = True
-options.add_agents = False
-
-# ADTrustInstallInterface
-# no_msdcs is deprecated
-options.no_msdcs = False
-
-# For pylint
-options.external_cert_files = None
-options.dirsrv_cert_files = None
-
-# Uninstall
-options.ignore_topology_disconnect = False
-options.ignore_last_of_role = False
+        try:
+            from ipaserver.install import adtrustinstance
+            _server_trust_ad_installed = True
+        except ImportError:
+            _server_trust_ad_installed = False
 
+        try:
+            from ipaclient.install.client import check_ldap_conf
+        except ImportError:
+            check_ldap_conf = None
 
-def api_Backend_ldap2(host_name, setup_ca, connect=False):
-    # we are sure we have the configuration file ready.
-    cfg = dict(context='installer', confdir=paths.ETC_IPA, in_server=True,
-               host=host_name)
-    if setup_ca:
-        # we have an IPA-integrated CA
-        cfg['ca_host'] = host_name
+        try:
+            from ipalib.x509 import Encoding
+        except ImportError:
+            from cryptography.hazmat.primitives.serialization import Encoding
 
-    api.bootstrap(**cfg)
-    api.finalize()
-    if connect:
-        api.Backend.ldap2.connect()
-
-
-def ds_init_info(ansible_log, fstore, domainlevel, dirsrv_config_file,
-                 realm_name, host_name, domain_name, dm_password,
-                 idstart, idmax, subject_base, ca_subject,
-                 no_hbac_allow, dirsrv_pkcs12_info, no_pkinit):
-
-    if not options.external_cert_files:
-        ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel,
-                                   config_ldif=dirsrv_config_file)
-        ds.set_output(ansible_log)
-
-        if options.dirsrv_cert_files:
-            _dirsrv_pkcs12_info = dirsrv_pkcs12_info
-        else:
-            _dirsrv_pkcs12_info = None
+        try:
+            from ipalib.x509 import load_pem_x509_certificate
+        except ImportError:
+            from ipalib.x509 import load_certificate
+            load_pem_x509_certificate = None
 
-        with redirect_stdout(ansible_log):
-            ds.init_info(realm_name, host_name, domain_name, dm_password,
-                         subject_base, ca_subject, idstart, idmax,
-                         # hbac_allow=not no_hbac_allow,
-                         _dirsrv_pkcs12_info, setup_pkinit=not no_pkinit)
     else:
-        ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel)
-        ds.set_output(ansible_log)
+        # IPA version < 4.5
 
-        with redirect_stdout(ansible_log):
-            ds.init_info(realm_name, host_name, domain_name, dm_password,
-                         subject_base, ca_subject, 1101, 1100, None,
-                         setup_pkinit=not no_pkinit)
+        raise Exception("freeipa version '%s' is too old" % VERSION)
 
-    return ds
+    logger = logging.getLogger("ipa-server-install")
 
+    def setup_logging():
+        # logger.setLevel(logging.DEBUG)
+        standard_logging_setup(
+            paths.IPASERVER_INSTALL_LOG, verbose=False, debug=False,
+            filemode='a', console_format='%(message)s')
 
-def ansible_module_get_parsed_ip_addresses(ansible_module,
-                                           param='ip_addresses'):
-    ip_addrs = []
-    for ip in ansible_module.params.get(param):
+    @contextlib_contextmanager
+    def redirect_stdout(f):
+        sys.stdout = f
         try:
-            ip_parsed = ipautil.CheckedIPAddress(ip)
-        except Exception as e:
-            ansible_module.fail_json(msg="Invalid IP Address %s: %s" % (ip, e))
-        ip_addrs.append(ip_parsed)
-    return ip_addrs
-
-
-def encode_certificate(cert):
-    """
-    Encode a certificate using base64.
-
-    It also takes FreeIPA and Python versions into account.
-    """
-    if isinstance(cert, (str, bytes)):
-        encoded = base64.b64encode(cert)
-    else:
-        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
-    if not six.PY2:
-        encoded = encoded.decode('ascii')
-    return encoded
-
-
-def decode_certificate(cert):
-    """
-    Decode a certificate using base64.
-
-    It also takes FreeIPA versions into account and returns a IPACertificate
-    for newer IPA versions.
-    """
-    if hasattr(x509, "IPACertificate"):
-        cert = cert.strip()
-        if not cert.startswith("-----BEGIN CERTIFICATE-----"):
-            cert = "-----BEGIN CERTIFICATE-----\n" + cert
-        if not cert.endswith("-----END CERTIFICATE-----"):
-            cert += "\n-----END CERTIFICATE-----"
-
-        if load_pem_x509_certificate is not None:
-            cert = load_pem_x509_certificate(cert.encode('utf-8'))
+            yield f
+        finally:
+            sys.stdout = sys.__stdout__
+
+    class AnsibleModuleLog():
+        def __init__(self, module):
+            self.module = module
+            _ansible_module_log = self
+
+            class AnsibleLoggingHandler(logging.Handler):
+                def emit(self, record):
+                    _ansible_module_log.write(self.format(record))
+
+            self.logging_handler = AnsibleLoggingHandler()
+            logger.setLevel(logging.DEBUG)
+            logger.root.addHandler(self.logging_handler)
+
+        def close(self):
+            self.flush()
+
+        def flush(self):
+            pass
+
+        def log(self, msg):
+            # self.write(msg+"\n")
+            self.write(msg)
+
+        def debug(self, msg):
+            self.module.debug(msg)
+
+        def info(self, msg):
+            self.module.debug(msg)
+
+        def write(self, msg):
+            self.module.debug(msg)
+            # self.module.warn(msg)
+
+    class options_obj(object):
+        def __init__(self):
+            self._replica_install = False
+            self.dnssec_master = False  # future unknown
+            self.disable_dnssec_master = False  # future unknown
+            self.domainlevel = MAX_DOMAIN_LEVEL  # deprecated
+            self.domain_level = self.domainlevel  # deprecated
+            self.interactive = False
+            self.unattended = not self.interactive
+
+        # def __getattribute__(self, attr):
+        #    logger.info(" <-- Accessing options.%s" % attr)
+        #    return super(options_obj, self).__getattribute__(attr)
+
+        # def __getattr__(self, attr):
+        #    logger.info(" --> Adding missing options.%s" % attr)
+        #    setattr(self, attr, None)
+        #    return getattr(self, attr)
+
+        def knobs(self):
+            for name in self.__dict__:
+                yield self, name
+
+    options = options_obj()
+    installer = options
+
+    # ServerMasterInstall
+    options.add_sids = True
+    options.add_agents = False
+
+    # Installable
+    options.uninstalling = False
+
+    # ServerInstallInterface
+    options.description = "Server"
+
+    options.kinit_attempts = 1
+    options.fixed_primary = True
+    options.permit = False
+    options.enable_dns_updates = False
+    options.no_krb5_offline_passwords = False
+    options.preserve_sssd = False
+    options.no_sssd = False
+
+    # ServerMasterInstall
+    options.force_join = False
+    options.servers = None
+    options.no_wait_for_dns = True
+    options.host_password = None
+    options.keytab = None
+    options.setup_ca = True
+    # always run sidgen task and do not allow adding agents on first master
+    options.add_sids = True
+    options.add_agents = False
+
+    # ADTrustInstallInterface
+    # no_msdcs is deprecated
+    options.no_msdcs = False
+
+    # For pylint
+    options.external_cert_files = None
+    options.dirsrv_cert_files = None
+
+    # Uninstall
+    options.ignore_topology_disconnect = False
+    options.ignore_last_of_role = False
+
+    def api_Backend_ldap2(host_name, setup_ca, connect=False):
+        # we are sure we have the configuration file ready.
+        cfg = dict(context='installer', confdir=paths.ETC_IPA, in_server=True,
+                   host=host_name)
+        if setup_ca:
+            # we have an IPA-integrated CA
+            cfg['ca_host'] = host_name
+
+        api.bootstrap(**cfg)
+        api.finalize()
+        if connect:
+            api.Backend.ldap2.connect()
+
+    def ds_init_info(ansible_log, fstore, domainlevel, dirsrv_config_file,
+                     realm_name, host_name, domain_name, dm_password,
+                     idstart, idmax, subject_base, ca_subject,
+                     no_hbac_allow, dirsrv_pkcs12_info, no_pkinit):
+
+        if not options.external_cert_files:
+            ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel,
+                                       config_ldif=dirsrv_config_file)
+            ds.set_output(ansible_log)
+
+            if options.dirsrv_cert_files:
+                _dirsrv_pkcs12_info = dirsrv_pkcs12_info
+            else:
+                _dirsrv_pkcs12_info = None
+
+            with redirect_stdout(ansible_log):
+                ds.init_info(realm_name, host_name, domain_name, dm_password,
+                             subject_base, ca_subject, idstart, idmax,
+                             # hbac_allow=not no_hbac_allow,
+                             _dirsrv_pkcs12_info, setup_pkinit=not no_pkinit)
         else:
-            cert = load_certificate(cert.encode('utf-8'))
-    else:
-        cert = base64.b64decode(cert)
-    return cert
+            ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel)
+            ds.set_output(ansible_log)
+
+            with redirect_stdout(ansible_log):
+                ds.init_info(realm_name, host_name, domain_name, dm_password,
+                             subject_base, ca_subject, 1101, 1100, None,
+                             setup_pkinit=not no_pkinit)
+
+        return ds
+
+    def ansible_module_get_parsed_ip_addresses(ansible_module,
+                                               param='ip_addresses'):
+        ip_addrs = []
+        for ip in ansible_module.params.get(param):
+            try:
+                ip_parsed = ipautil.CheckedIPAddress(ip)
+            except Exception as e:
+                ansible_module.fail_json(
+                    msg="Invalid IP Address %s: %s" % (ip, e))
+            ip_addrs.append(ip_parsed)
+        return ip_addrs
+
+    def encode_certificate(cert):
+        """
+        Encode a certificate using base64.
+
+        It also takes FreeIPA and Python versions into account.
+        """
+        if isinstance(cert, (str, bytes)):
+            encoded = base64.b64encode(cert)
+        else:
+            encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
+        if not six.PY2:
+            encoded = encoded.decode('ascii')
+        return encoded
+
+    def decode_certificate(cert):
+        """
+        Decode a certificate using base64.
+
+        It also takes FreeIPA versions into account and returns a
+        IPACertificate for newer IPA versions.
+        """
+        if hasattr(x509, "IPACertificate"):
+            cert = cert.strip()
+            if not cert.startswith("-----BEGIN CERTIFICATE-----"):
+                cert = "-----BEGIN CERTIFICATE-----\n" + cert
+            if not cert.endswith("-----END CERTIFICATE-----"):
+                cert += "\n-----END CERTIFICATE-----"
+
+            if load_pem_x509_certificate is not None:
+                cert = load_pem_x509_certificate(cert.encode('utf-8'))
+            else:
+                cert = load_certificate(cert.encode('utf-8'))
+        else:
+            cert = base64.b64decode(cert)
+        return cert