From 53d984f1e8fdd9fd6ff7e81493d1dab844a63eaf Mon Sep 17 00:00:00 2001
From: Thomas Woerner <twoerner@redhat.com>
Date: Wed, 4 Apr 2018 15:25:57 +0200
Subject: [PATCH] New role for ipareplica installation

The support for external cert files is not complete yet.

Please have a look at the example inventory file inventory/hosts.replica and
also the install and uninstall playbook files install-replica.yml and
uninstall-replica.yml
---
 install-replica.yml                           |   8 +
 inventory/hosts.replica                       |  10 +
 module_utils/ansible_ipa_replica.py           | 593 ++++++++++++++
 roles/ipareplica/defaults/main.yml            |  40 +
 roles/ipareplica/files/py3test.py             |   9 +
 .../library/ipareplica_add_to_ipaservers.py   | 138 ++++
 .../library/ipareplica_create_ipa_conf.py     | 303 +++++++
 .../ipareplica_custodia_import_dm_password.py | 178 ++++
 .../library/ipareplica_ds_apply_updates.py    | 167 ++++
 .../library/ipareplica_ds_enable_ssl.py       | 191 +++++
 .../library/ipareplica_install_ca_certs.py    | 309 +++++++
 .../library/ipareplica_krb_enable_ssl.py      | 147 ++++
 .../ipareplica/library/ipareplica_prepare.py  | 759 ++++++++++++++++++
 .../ipareplica_promote_openldap_conf.py       | 141 ++++
 .../library/ipareplica_promote_sssd.py        | 140 ++++
 .../library/ipareplica_restart_kdc.py         | 148 ++++
 .../library/ipareplica_setup_adtrust.py       | 144 ++++
 .../ipareplica/library/ipareplica_setup_ca.py | 218 +++++
 .../library/ipareplica_setup_certmonger.py    |  78 ++
 .../library/ipareplica_setup_custodia.py      | 180 +++++
 .../library/ipareplica_setup_dns.py           | 150 ++++
 .../ipareplica/library/ipareplica_setup_ds.py | 341 ++++++++
 .../library/ipareplica_setup_http.py          | 180 +++++
 .../library/ipareplica_setup_kra.py           | 225 ++++++
 .../library/ipareplica_setup_krb.py           | 160 ++++
 .../library/ipareplica_setup_otpd.py          | 171 ++++
 roles/ipareplica/library/ipareplica_test.py   | 316 ++++++++
 .../library/ipaserver_enable_ipa.py           | 100 +++
 .../library/ipaserver_master_password.py      |  93 +++
 .../ipareplica/library/ipaserver_setup_ntp.py |  79 ++
 roles/ipareplica/meta/main.yml                |  27 +
 roles/ipareplica/tasks/install.yml            | 619 ++++++++++++++
 roles/ipareplica/tasks/main.yml               |  18 +
 roles/ipareplica/tasks/python_2_3_test.yml    |  19 +
 roles/ipareplica/tasks/uninstall.yml          |  38 +
 roles/ipareplica/vars/Fedora-25.yml           |   5 +
 roles/ipareplica/vars/Fedora-26.yml           |   5 +
 roles/ipareplica/vars/Fedora.yml              |   5 +
 roles/ipareplica/vars/RedHat-7.3.yml          |   5 +
 roles/ipareplica/vars/RedHat-7.yml            |   5 +
 roles/ipareplica/vars/default.yml             |   5 +
 uninstall-replica.yml                         |   8 +
 42 files changed, 6475 insertions(+)
 create mode 100644 install-replica.yml
 create mode 100644 inventory/hosts.replica
 create mode 100644 module_utils/ansible_ipa_replica.py
 create mode 100644 roles/ipareplica/defaults/main.yml
 create mode 100644 roles/ipareplica/files/py3test.py
 create mode 100644 roles/ipareplica/library/ipareplica_add_to_ipaservers.py
 create mode 100644 roles/ipareplica/library/ipareplica_create_ipa_conf.py
 create mode 100644 roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
 create mode 100644 roles/ipareplica/library/ipareplica_ds_apply_updates.py
 create mode 100644 roles/ipareplica/library/ipareplica_ds_enable_ssl.py
 create mode 100644 roles/ipareplica/library/ipareplica_install_ca_certs.py
 create mode 100644 roles/ipareplica/library/ipareplica_krb_enable_ssl.py
 create mode 100644 roles/ipareplica/library/ipareplica_prepare.py
 create mode 100644 roles/ipareplica/library/ipareplica_promote_openldap_conf.py
 create mode 100644 roles/ipareplica/library/ipareplica_promote_sssd.py
 create mode 100644 roles/ipareplica/library/ipareplica_restart_kdc.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_adtrust.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_ca.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_certmonger.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_custodia.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_dns.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_ds.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_http.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_kra.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_krb.py
 create mode 100644 roles/ipareplica/library/ipareplica_setup_otpd.py
 create mode 100644 roles/ipareplica/library/ipareplica_test.py
 create mode 100644 roles/ipareplica/library/ipaserver_enable_ipa.py
 create mode 100644 roles/ipareplica/library/ipaserver_master_password.py
 create mode 100644 roles/ipareplica/library/ipaserver_setup_ntp.py
 create mode 100644 roles/ipareplica/meta/main.yml
 create mode 100644 roles/ipareplica/tasks/install.yml
 create mode 100644 roles/ipareplica/tasks/main.yml
 create mode 100644 roles/ipareplica/tasks/python_2_3_test.yml
 create mode 100644 roles/ipareplica/tasks/uninstall.yml
 create mode 100644 roles/ipareplica/vars/Fedora-25.yml
 create mode 100644 roles/ipareplica/vars/Fedora-26.yml
 create mode 100644 roles/ipareplica/vars/Fedora.yml
 create mode 100644 roles/ipareplica/vars/RedHat-7.3.yml
 create mode 100644 roles/ipareplica/vars/RedHat-7.yml
 create mode 100644 roles/ipareplica/vars/default.yml
 create mode 100644 uninstall-replica.yml

diff --git a/install-replica.yml b/install-replica.yml
new file mode 100644
index 00000000..fef9654e
--- /dev/null
+++ b/install-replica.yml
@@ -0,0 +1,8 @@
+---
+- name: Playbook to configure IPA replicas
+  hosts: ipareplicas
+  become: true
+
+  roles:
+  - role: ipareplica
+    state: present
diff --git a/inventory/hosts.replica b/inventory/hosts.replica
new file mode 100644
index 00000000..653f9274
--- /dev/null
+++ b/inventory/hosts.replica
@@ -0,0 +1,10 @@
+[ipaservers]
+ipaserver.test.local
+
+[ipareplicas]
+ipareplica1.test.local
+
+[ipareplicas:vars]
+ipaadmin_password=password1
+ipadm_password=password1
+ipaclient_force_join=yes
diff --git a/module_utils/ansible_ipa_replica.py b/module_utils/ansible_ipa_replica.py
new file mode 100644
index 00000000..565912a3
--- /dev/null
+++ b/module_utils/ansible_ipa_replica.py
@@ -0,0 +1,593 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import logging
+#import fcntl
+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 logging
+
+    import dns.exception as dnsexception
+    import dns.name as dnsname
+    import dns.resolver as dnsresolver
+    import dns.reversename as dnsreversename
+    import os
+    import shutil
+    import socket
+    import tempfile
+    import traceback
+
+    from pkg_resources import parse_version
+    import six
+
+    from ipaclient.install.ipachangeconf import IPAChangeConf
+    import ipaclient.install.ntpconf
+    from ipalib.install import certstore, sysrestore
+    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 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,
+        ntpinstance, otpdinstance, custodiainstance, service,
+        upgradeinstance)
+    from ipaserver.install.installutils import (
+        create_replica_config, ReplicaConfig, load_pkcs12, 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
+
+    if six.PY3:
+        unicode = str
+
+    """
+    import errno
+    import pickle
+    import shutil
+    import tempfile
+    import textwrap
+    import random
+
+    import six
+
+    from ipalib.install import certmonger, sysrestore
+    from ipapython import ipautil
+    from ipapython.ipautil import (
+        format_netloc, 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
+    from ipalib.util import (
+        validate_domain_name,
+        no_matching_interface_for_ip_address_warning,
+    )
+    from ipapython.dnsutil import check_zone_overlap
+    from ipaclient.install import ntpconf
+    from ipaserver.install import (
+        adtrust, bindinstance, ca, dns, dsinstance,
+        httpinstance, installutils, kra, krbinstance,
+        ntpinstance, otpdinstance, custodiainstance, replication, service,
+        sysupgrade)
+    adtrust_imported = True
+    kra_imported = True
+    from ipaserver.install.installutils import (
+        IPA_MODULES, BadHostError, get_fqdn, get_server_ip_address,
+        is_ipa_configured, load_pkcs12, read_password, verify_fqdn,
+        update_hosts_file)
+    from ipaserver.install.server.install import (
+        check_dirsrv, validate_admin_password, validate_dm_password,
+        write_cache)
+    try:
+        from ipaserver.install.installutils import default_subject_base
+    except ImportError:
+        def default_subject_base(realm_name):
+            return DN(('O', realm_name))
+    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)
+
+    if six.PY3:
+        unicode = str
+
+    try:
+        from ipaserver.install import adtrustinstance
+        _server_trust_ad_installed = True
+    except ImportError:
+        _server_trust_ad_installed = False
+    """
+
+else:
+    # IPA version < 4.6
+
+    raise Exception("freeipa version '%s' is too old" % VERSION)
+
+
+logger = logging.getLogger("ipa-server-install")
+logger.setLevel(logging.DEBUG)
+
+
+@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 write(self, msg):
+        # self.module.debug(msg)
+        self.module.warn(msg)
+
+def show_obj(obj):
+    s="%s = {" % obj.__class__
+    for key in dir(obj):
+        #if key in [ "__class__", "__dict__" ]:
+        #    continue
+        if key.startswith("--") and key.endswith("--"):
+            continue
+        if not hasattr(obj, key):
+            continue
+        value = getattr(obj, key)
+        if callable(value):
+            continue
+        s += " '%s': %s," % (key, repr(value))
+    logger.info(s+" }")
+
+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
+
+    #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
+
+
+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 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
+
+    #show_obj(config)
+
+    return config
+
+
+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,
+                 external_cert_files, dirsrv_cert_files):
+
+    if not external_cert_files:
+        ds = dsinstance.DsInstance(fstore=fstore, domainlevel=domainlevel,
+                                   config_ldif=dirsrv_config_file)
+        ds.set_output(ansible_log)
+
+        if 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:
+        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 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 krb_init_info(ansible_log, fstore, realm_name, host_name, no_pkinit,
+                  subject_base):
+    krb = krbinstance.KrbInstance(fstore)
+    krb.set_output(ansible_log)
+    with redirect_stdout(ansible_log):
+        krb.init_info(realm_name, host_name, etup_pkinit=not no_pkinit,
+                      subject_base=subject_base)
+
+
+def replica_krb_init_info(ansible_log, fstore, realm_name, master_host_name,
+                          host_name, domain_name, admin_password,
+                          no_pkinit, subject_base, pkcs12_info=None):
+    # promote is not needed here
+
+    # From replicainstall.install_krb
+    krb = krbinstance.KrbInstance(fstore=fstore)
+    krb.set_output(ansible_log)
+
+    # pkinit files
+    if pkcs12_info is None:
+        pkcs12_info = make_pkcs12_info(config.dir, "pkinitcert.p12",
+                                       "pkinit_pin.txt")
+
+    #krb.create_replica(realm_name,
+    #                   master_host_name, host_name,
+    #                   domain_name, dirman_password,
+    #                   setup_pkinit, pkcs12_info,
+    #                   subject_base=subject_base,
+    #                   promote=promote)
+    with redirect_stdout(ansible_log):
+        krb.init_info(realm_name, host_name, setup_pkinit=not no_pkinit,
+                      subject_base=subject_base)
+
+        # From ipaserver.install.krbinstance.create_replica
+
+        krb.pkcs12_info = pkcs12_info
+        krb.subject_base = subject_base
+        krb.master_fqdn = master_host_name
+        krb.config_pkinit = not no_pkinit
+
+        #krb.__common_setup(realm_name, host_name, domain_name, admin_password)
+        krb.fqdn = host_name
+        krb.realm = realm_name.upper()
+        krb.host = host_name.split(".")[0]
+        krb.ip = socket.getaddrinfo(host_name, None, socket.AF_UNSPEC, socket.SOCK_STREAM)[0][4][0]
+        krb.domain = domain_name
+        krb.suffix = ipautil.realm_to_suffix(krb.realm)
+        krb.kdc_password = ipautil.ipa_generate_password()
+        krb.admin_password = admin_password
+        krb.dm_password = admin_password
+
+        #krb.__setup_sub_dict()
+        if os.path.exists(paths.COMMON_KRB5_CONF_DIR):
+            includes = 'includedir {}'.format(paths.COMMON_KRB5_CONF_DIR)
+        else:
+            includes = ''
+
+        krb.sub_dict = dict(FQDN=krb.fqdn,
+                             IP=krb.ip,
+                             PASSWORD=krb.kdc_password,
+                             SUFFIX=krb.suffix,
+                             DOMAIN=krb.domain,
+                             HOST=krb.host,
+                             SERVER_ID=installutils.realm_to_serverid(krb.realm),
+                             REALM=krb.realm,
+                             KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL,
+                             DICT_WORDS=paths.DICT_WORDS,
+                             KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB,
+                             KDC_CERT=paths.KDC_CERT,
+                             KDC_KEY=paths.KDC_KEY,
+                             CACERT_PEM=paths.CACERT_PEM,
+                             KDC_CA_BUNDLE_PEM=paths.KDC_CA_BUNDLE_PEM,
+                             CA_BUNDLE_PEM=paths.CA_BUNDLE_PEM,
+                             INCLUDES=includes)
+
+        # IPA server/KDC is not a subdomain of default domain
+        # Proper domain-realm mapping needs to be specified
+        domain = dnsname.from_text(krb.domain)
+        fqdn = dnsname.from_text(krb.fqdn)
+        if not fqdn.is_subdomain(domain):
+            logger.debug("IPA FQDN '%s' is not located in default domain '%s'",
+                         fqdn, domain)
+            server_domain = fqdn.parent().to_unicode(omit_final_dot=True)
+            logger.debug("Domain '%s' needs additional mapping in krb5.conf",
+                         server_domain)
+            dr_map = " .%(domain)s = %(realm)s\n %(domain)s = %(realm)s\n" \
+                        % dict(domain=server_domain, realm=krb.realm)
+        else:
+            dr_map = ""
+        krb.sub_dict['OTHER_DOMAIN_REALM_MAPS'] = dr_map
+
+        # Configure KEYRING CCACHE if supported
+        if kernel_keyring.is_persistent_keyring_supported():
+            logger.debug("Enabling persistent keyring CCACHE")
+            krb.sub_dict['OTHER_LIBDEFAULTS'] = \
+                " default_ccache_name = KEYRING:persistent:%{uid}\n"
+        else:
+            logger.debug("Persistent keyring CCACHE is not enabled")
+            krb.sub_dict['OTHER_LIBDEFAULTS'] = ''
+
+    return krb
+
+
+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/ipareplica/defaults/main.yml b/roles/ipareplica/defaults/main.yml
new file mode 100644
index 00000000..fb8c904b
--- /dev/null
+++ b/roles/ipareplica/defaults/main.yml
@@ -0,0 +1,40 @@
+---
+# defaults file for ipareplica
+
+### basic ###
+ipareplica_no_host_dns: no
+ipareplica_skip_conncheck: no
+### server ###
+ipareplica_setup_adtrust: no
+ipareplica_setup_ca: no
+ipareplica_setup_kra: no
+ipareplica_setup_dns: no
+ipareplica_no_pkinit: no
+ipareplica_no_ui_redirect: no
+### client ###
+ipaclient_mkhomedir: no
+ipaclient_force_join: no
+ipaclient_no_ntp: no
+#ipaclient_ssh_trust_dns: no
+#ipaclient_no_ssh: no
+#ipaclient_no_sshd: no
+#ipaclient_no_dns_sshfp: no
+### certificate system ###
+ipareplica_skip_schema_check: no
+### dns ###
+ipareplica_allow_zone_overlap: no
+ipareplica_no_reverse: no
+ipareplica_auto_reverse: no
+ipareplica_no_forwarders: no
+ipareplica_auto_forwarders: no
+ipareplica_no_dnssec_validation: no
+### ad trust ###
+ipareplica_add_sids: no
+ipareplica_add_agents: no
+ipareplica_enable_compat: no
+### uninstall ###
+ipareplica_ignore_topology_disconnect: no
+ipareplica_ignore_last_of_role: no
+### additional ###
+ipareplica_no_package_install: no
+ipareplica_no_firewalld: no
diff --git a/roles/ipareplica/files/py3test.py b/roles/ipareplica/files/py3test.py
new file mode 100644
index 00000000..a7617cff
--- /dev/null
+++ b/roles/ipareplica/files/py3test.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python3
+
+# Test ipaerver python3 binding
+from ipaserver.install.server.replicainstall import install_check
+
+# Check ipapython version to be >= 4.6
+from ipapython.version import NUM_VERSION, VERSION
+if NUM_VERSION < 40590:
+    raise Exception("ipa %s not usable with python3" % VERSION)
diff --git a/roles/ipareplica/library/ipareplica_add_to_ipaservers.py b/roles/ipareplica/library/ipareplica_add_to_ipaservers.py
new file mode 100644
index 00000000..a7253e3d
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_add_to_ipaservers.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_add_to_ipaservers
+short description: Add to ipaservers
+description:
+  Add to ipaservers
+options:
+  setup_kra:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  installer_ccache:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### server ###
+            setup_kra=dict(required=True, type='bool'),
+            ### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            installer_ccache=dict(required=True),
+            _top_dir = dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    ### additional ###
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    options._ccache = ansible_module.params.get('installer_ccache')
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    options._top_dir = ansible_module.params.get('_top_dir')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALLER ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    #config = gen_ReplicaConfig()
+
+    remote_api = gen_remote_api(config_master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    ansible_log.debug("-- HOSTGROUP_ADD_MEMBER --")
+    try:
+        ansible_log.debug("-- CONNECT --")
+        conn.connect(ccache=installer._ccache)
+        remote_api.Command['hostgroup_add_member'](
+            u'ipaservers',
+            host=[unicode(api.env.host)],
+        )
+    finally:
+        if conn.isconnected():
+            ansible_log.debug("-- DISCONNECT --")
+            conn.disconnect()
+        os.environ['KRB5CCNAME'] = ccache
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_create_ipa_conf.py b/roles/ipareplica/library/ipareplica_create_ipa_conf.py
new file mode 100644
index 00000000..529988b0
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_create_ipa_conf.py
@@ -0,0 +1,303 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_create_ipa_conf
+short description: Create ipa.conf
+description:
+  Create ipa.conf
+options:
+  dm_password:
+    description: Directory Manager password
+    required: yes
+  password:
+    description: Admin user kerberos password
+    required: yes
+  ip_addresses:
+    description: List of Master Server IP Addresses
+    required: no
+  domain:
+    description: Primary DNS domain of the IPA deployment
+    required: yes
+  realm:
+    description: Kerberos realm name of the IPA deployment
+    required: yes
+  hostname:
+    description: Fully qualified name of this host
+    required: yes
+  ca_cert_files:
+    description: List of iles containing CA certificates for the service certificate files
+    required: yes
+  no_host_dns:
+    description: Do not use DNS for hostname lookup during installation
+    required: yes
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  dirserv_cert_files:
+    description: 
+    required: yes
+  force_join:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  server:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  installer_ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _kra_enabled:
+    description: 
+    required: yes
+  _dirsrv_pkcs12_info:
+    description: 
+    required: yes
+  _http_pkcs12_info:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  _add_to_ipaservers:
+    description: 
+    required: yes
+  _ca_subject:
+    description: 
+    required: yes
+  _subject_base:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  config_ca_host_name:
+    description: 
+    required: yes
+  config_ips:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            dm_password=dict(required=False, no_log=True),
+            password=dict(required=False, no_log=True),
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            no_host_dns=dict(required=False, type='bool', default=False),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            ### client ###
+            force_join=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            server=dict(required=True),
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            installer_ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _kra_enabled=dict(required=False, type='bool'),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _http_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            _add_to_ipaservers = dict(required=True),
+            _ca_subject=dict(required=True),
+            _subject_base=dict(required=True),
+
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    options.dm_password = ansible_module.params.get('dm_password')
+    options.password = options.dm_password
+    options.admin_password = ansible_module.params.get('password')
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    options.no_host_dns = ansible_module.params.get('no_host_dns')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    ### client ###
+    options.force_join = ansible_module.params.get('force_join')
+    ### certificate system ###
+    options.external_ca = ansible_module.params.get('external_ca')
+    options.external_cert_files = ansible_module.params.get(
+        'external_cert_files')
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options.ca_subject = ansible_module.params.get('ca_subject')
+    ### dns ###
+    options.reverse_zones = ansible_module.params.get('reverse_zones')
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+
+    ### additional ###
+    #options._host_name_overridden = ansible_module.params.get(
+    #    '_hostname_overridden')
+    options.server = ansible_module.params.get('server')
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    kra_enabled = ansible_module.params.get('_kra_enabled')
+
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    http_pkcs12_info = ansible_module.params.get('_http_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options._top_dir = ansible_module.params.get('_top_dir')
+    options._add_to_ipaservers = ansible_module.params.get('_add_to_ipaservers')
+
+    options._ca_subject = ansible_module.params.get('_ca_subject')
+    options._subject_base = ansible_module.params.get('_subject_base')
+
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    # prepare (install prepare, install checks) #
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+    config.dirman_password = dirman_password
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    cafile = paths.IPA_CA_CRT
+
+    if promote:
+        ansible_log.debug("-- CREATE_IPA_CONF --")
+        # Create the management framework config file. Do this irregardless
+        # of the state of DS installation. Even if it fails,
+        # we need to have master-like configuration in order to perform a
+        # successful uninstallation
+        # The configuration creation has to be here otherwise previous call
+        # To config certmonger would try to connect to local server
+        create_ipa_conf(fstore, config, ca_enabled)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py b/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
new file mode 100644
index 00000000..4b3284f0
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
@@ -0,0 +1,178 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_custodia_import_dm_password
+short description: Import dm password into custodia
+description:
+  Import dm password into custodia
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  no_ui_redirect:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _dirsrv_pkcs12_info:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # do the work #
+
+    with redirect_stdout(ansible_log):
+        custodia = custodiainstance.CustodiaInstance(config.host_name,
+                                                     config.realm_name)
+
+        ansible_log.debug("-- CUSTODIA IMPORT DM PASSWORD --")
+
+        custodia.import_dm_password(config.master_host_name)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_ds_apply_updates.py b/roles/ipareplica/library/ipareplica_ds_apply_updates.py
new file mode 100644
index 00000000..a2fc4bb3
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_ds_apply_updates.py
@@ -0,0 +1,167 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_ds_apply_updates
+short description: DS apply updates
+description:
+  DS apply updates
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            dirsrv_config_file=dict(required=False),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+            ds_ca_subject=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    options.dirsrv_config_file = ansible_module.params.get('dirsrv_config_file')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    installer._dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    installer._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+    ds_ca_subject = ansible_module.params.get('ds_ca_subject')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance 
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    cafile = paths.IPA_CA_CRT
+    with redirect_stdout(ansible_log):
+        ds = replica_ds_init_info(ansible_log,
+                                  config, options, ca_enabled,
+                                  remote_api, ds_ca_subject,
+                                  ca_file=paths.IPA_CA_CRT,
+                                  promote=promote,
+                                  pkcs12_info=installer._dirsrv_pkcs12_info)
+
+        ansible_log.debug("-- DS APPLY_UPDATES --")
+
+        # Apply any LDAP updates. Needs to be done after the replica is
+        # synced-up
+        #service.print_msg("Applying LDAP updates")
+        #ds.apply_updates()
+        schema_files = dsinstance.get_all_external_schema_files(
+            paths.EXTERNAL_SCHEMA_DIR)
+        data_upgrade = upgradeinstance.IPAUpgrade(ds.realm,
+                                                  schema_files=schema_files)
+        data_upgrade.set_output(ansible_log)
+        try:
+            data_upgrade.create_instance()
+        except Exception as e:
+            # very fatal errors only will raise exception
+            raise RuntimeError("Update failed: %s" % e)
+        installutils.store_version()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_ds_enable_ssl.py b/roles/ipareplica/library/ipareplica_ds_enable_ssl.py
new file mode 100644
index 00000000..d82a0704
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_ds_enable_ssl.py
@@ -0,0 +1,191 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_ds_enable_ssl
+short description: DS enable SSL
+description:
+  DS enable SSL
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _dirsrv_pkcs12_info:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+  ds_ca_subject:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            dirsrv_config_file=dict(required=False),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+            ds_ca_subject=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    options.dirsrv_config_file = ansible_module.params.get('dirsrv_config_file')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    options._dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    options._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+    ds_ca_subject = ansible_module.params.get('ds_ca_subject')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    with redirect_stdout(ansible_log):
+        ds = replica_ds_init_info(ansible_log,
+                                  config, options, ca_enabled,
+                                  remote_api, ds_ca_subject,
+                                  ca_file=paths.IPA_CA_CRT,
+                                  promote=promote,
+                                  pkcs12_info=installer._dirsrv_pkcs12_info)
+
+        ansible_log.debug("-- DS.ENABLE_SSL --")
+
+        # we now need to enable ssl on the ds
+        ds.enable_ssl()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_install_ca_certs.py b/roles/ipareplica/library/ipareplica_install_ca_certs.py
new file mode 100644
index 00000000..ef1a4d3b
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_install_ca_certs.py
@@ -0,0 +1,309 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_install_ca_cert
+short description: Install CA certs
+description:
+  Install CA certs
+options:
+  dm_password:
+    description: Directory Manager password
+    required: yes
+  password:
+    description: Admin user kerberos password
+    required: yes
+  ip_addresses:
+    description: List of Master Server IP Addresses
+    required: no
+  domain:
+    description: Primary DNS domain of the IPA deployment
+    required: yes
+  realm:
+    description: Kerberos realm name of the IPA deployment
+    required: yes
+  hostname:
+    description: Fully qualified name of this host
+    required: yes
+  ca_cert_files:
+    description: List of iles containing CA certificates for the service certificate files
+    required: yes
+  no_host_dns:
+    description: Do not use DNS for hostname lookup during installation
+    required: yes
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  external_ca:
+    description: 
+    required: yes
+  external_cert_files:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  ca_subject:
+    description: 
+    required: yes
+  reverse_zones:
+    description: 
+    required: yes
+  no_reverse:
+    description: 
+    required: yes
+  auto_reverse:
+    description: 
+    required: yes
+  forwarders:
+    description: 
+    required: yes
+  no_forwarders:
+    description: 
+    required: yes
+G  auto_forwarders:
+    description: 
+    required: yes
+  forward_policy:
+    description: 
+    required: yes
+  enable_compat:
+    description: 
+    required: yes
+  netbios_name:
+    description: 
+    required: yes
+  rid_base:
+    description: 
+    required: yes
+  secondary_rid_base:
+    description: 
+    required: yes
+  setup_ca:
+    description: 
+    required: yes
+  _hostname_overridden:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            dm_password=dict(required=False, no_log=True),
+            password=dict(required=False, no_log=True),
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            no_host_dns=dict(required=False, type='bool', default=False),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            ### client ###
+            force_join=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            server=dict(required=True),
+            ccache=dict(required=True),
+            installer_ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _kra_enabled=dict(required=False, type='bool'),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _http_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            _add_to_ipaservers = dict(required=True),
+            _ca_subject=dict(required=True),
+            _subject_base=dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+            config_ca_host_name=dict(required=True),
+            config_ips=dict(required=False, type='list', default=[]),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### basic ###
+    options.dm_password = ansible_module.params.get('dm_password')
+    options.password = options.dm_password
+    options.admin_password = ansible_module.params.get('password')
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    options.no_host_dns = ansible_module.params.get('no_host_dns')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    ### client ###
+    options.force_join = ansible_module.params.get('force_join')
+    ### certificate system ###
+    options.external_ca = ansible_module.params.get('external_ca')
+    options.external_cert_files = ansible_module.params.get(
+        'external_cert_files')
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options.ca_subject = ansible_module.params.get('ca_subject')
+    ### dns ###
+    options.reverse_zones = ansible_module.params.get('reverse_zones')
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+    ### additional ###
+    options.server = ansible_module.params.get('server')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    kra_enabled = ansible_module.params.get('_kra_enabled')
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    http_pkcs12_info = ansible_module.params.get('_http_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options._top_dir = ansible_module.params.get('_top_dir')
+    options._add_to_ipaservers = ansible_module.params.get('_add_to_ipaservers')
+    options._ca_subject = ansible_module.params.get('_ca_subject')
+    options._subject_base = ansible_module.params.get('_subject_base')
+    dirman_password = ansible_module.params.get('dirman_password')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+    config_ca_host_name = ansible_module.params.get('config_ca_host_name')
+    config_ips = ansible_module_get_parsed_ip_addresses(ansible_module,
+                                                        "config_ips")
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALLER ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+    config.setup_ca = config_setup_ca
+    config.master_host_name = config_master_host_name
+    config.ca_host_name = config_ca_host_name
+    config.ips = config_ips
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    cafile = paths.IPA_CA_CRT
+    with redirect_stdout(ansible_log):
+        try:
+            ansible_log.debug("-- CONNECT --")
+            if promote:
+                conn.connect(ccache=ccache)
+            else:
+                # dmlvl 0 replica install should always use DM credentials
+                # to create remote LDAP connection. Since ACIs permitting hosts
+                # to manage their own services were added in 4.2 release,
+                # the master denies this operations.
+                conn.connect(bind_dn=ipaldap.DIRMAN_DN, cacert=cafile,
+                             bind_pw=dirman_password)
+
+            ansible_log.debug("-- INSTALL_CA_CERT --")
+            # Update and istall updated CA file
+            cafile = install_ca_cert(conn, api.env.basedn, api.env.realm, cafile)
+            install_ca_cert(conn, api.env.basedn, api.env.realm, cafile,
+                            destfile=paths.KDC_CA_BUNDLE_PEM)
+            install_ca_cert(conn, api.env.basedn, api.env.realm, cafile,
+                            destfile=paths.CA_BUNDLE_PEM)
+
+        finally:
+            if conn.isconnected():
+                ansible_log.debug("-- DISCONNECT --")
+                conn.disconnect()
+
+    # done #
+
+    ansible_module.exit_json(changed=True,
+                             config_master_host_name=config.master_host_name,
+                             config_ca_host_name=config.ca_host_name)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_krb_enable_ssl.py b/roles/ipareplica/library/ipareplica_krb_enable_ssl.py
new file mode 100644
index 00000000..26ff48df
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_krb_enable_ssl.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_krb_enable_ssl
+short description: KRB enable SSL
+description:
+  KRB enable SSL
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    options._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    # krb
+    krb = krbinstance.KrbInstance(fstore)
+    krb.set_output(ansible_log)
+    with redirect_stdout(ansible_log):
+        krb.init_info(api.env.realm, api.env.host,
+                      setup_pkinit=not options.no_pkinit,
+                      subject_base=options.subject_base)
+
+        ansible_log.debug("-- KRB ENABLE_SSL --")
+
+        # configure PKINIT now that all required services are in place
+        krb.enable_ssl()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_prepare.py b/roles/ipareplica/library/ipareplica_prepare.py
new file mode 100644
index 00000000..ab9313a3
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_prepare.py
@@ -0,0 +1,759 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_prepare
+short description: Prepare ipa replica installation
+description:
+  Prepare ipa replica installation: Create IPA configuration file, run install
+  checks again and also update the host name and the hosts file if needed.
+  The tests and also the results from ipareplica_test are needed.
+ptions:
+  dm_password:
+    description: Directory Manager password
+    required: yes
+  password:
+    description: Admin user kerberos password
+    required: yes
+  ip_addresses:
+    description: List of Master Server IP Addresses
+    required: no
+  domain:
+    description: Primary DNS domain of the IPA deployment
+    required: yes
+  realm:
+    description: Kerberos realm name of the IPA deployment
+    required: yes
+  hostname:
+    description: Fully qualified name of this host
+    required: yes
+  ca_cert_files:
+    description: List of iles containing CA certificates for the service certificate files
+    required: yes
+  no_host_dns:
+    description: Do not use DNS for hostname lookup during installation
+    required: yes
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  external_ca:
+    description: 
+    required: yes
+  external_cert_files:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  ca_subject:
+    description: 
+    required: yes
+  reverse_zones:
+    description: 
+    required: yes
+  no_reverse:
+    description: 
+    required: yes
+  auto_reverse:
+    description: 
+    required: yes
+  forwarders:
+    description: 
+    required: yes
+  no_forwarders:
+    description: 
+    required: yes
+  auto_forwarders:
+    description: 
+    required: yes
+  forward_policy:
+    description: 
+    required: yes
+  enable_compat:
+    description: 
+    required: yes
+  netbios_name:
+    description: 
+    required: yes
+  rid_base:
+    description: 
+    required: yes
+  secondary_rid_base:
+    description: 
+    required: yes
+  setup_ca:
+    description: 
+    required: yes
+  _hostname_overridden:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            dm_password=dict(required=False, no_log=True),
+            password=dict(required=False, no_log=True),
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            principal=dict(required=True),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            no_host_dns=dict(required=False, type='bool', default=False),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            dirsrv_pin=dict(required=False),
+            http_cert_files=dict(required=False, type='list', default=[]),
+            http_pin=dict(required=False),
+            pkinit_cert_files=dict(required=False, type='list', default=[]),
+            pkinit_pin=dict(required=False),
+            ### client ###
+            keytab=dict(required=False),
+            mkhomedir=dict(required=False, type='bool'),
+            force_join=dict(required=False, type='bool'),
+            no_ntp=dict(required=False, type='bool'),
+            ssh_trust_dns=dict(required=False, type='bool'),
+            no_ssh=dict(required=False, type='bool'),
+            no_sshd=dict(required=False, type='bool'),
+            no_dns_sshfp=dict(required=False, type='bool'),
+            ### certificate system ###
+            #subject_base=dict(required=False),
+            no_dnssec_validation=dict(required=False, type='bool'),
+            ### dns ###
+            ### ad trust ###
+            ### additional ###
+            server=dict(required=True),
+            skip_conncheck=dict(required=False, type='bool'),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options.dm_password = ansible_module.params.get('dm_password')
+    options.password = options.dm_password
+    options.admin_password = ansible_module.params.get('password')
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.principal = ansible_module.params.get('principal')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    options.no_host_dns = ansible_module.params.get('no_host_dns')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    options.http_cert_files = ansible_module.params.get('http_cert_files')
+    options.pkinit_cert_files = ansible_module.params.get('pkinit_cert_files')
+    ### client ###
+    options.keytab = ansible_module.params.get('keytab')
+    options.mkhomedir = ansible_module.params.get('mkhomedir')
+    options.force_join = ansible_module.params.get('force_join')
+    options.no_ntp = ansible_module.params.get('no_ntp')
+    options.ssh_trust_dns = ansible_module.params.get('ssh_trust_dns')
+    options.no_ssh = ansible_module.params.get('no_ssh')
+    options.no_sshd = ansible_module.params.get('no_sshd')
+    options.no_dns_sshfp = ansible_module.params.get('no_dns_sshfp')
+    ### certificate system ###
+    options.external_ca = ansible_module.params.get('external_ca')
+    options.external_cert_files = ansible_module.params.get(
+        'external_cert_files')
+    #options.subject_base = ansible_module.params.get('subject_base')
+    #options.ca_subject = ansible_module.params.get('ca_subject')
+    options.no_dnssec_validation = ansible_module.params.get('no_dnssec_validation')
+    ### dns ###
+    options.reverse_zones = ansible_module.params.get('reverse_zones')
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+
+    ### additional ###
+    #options._host_name_overridden = ansible_module.params.get(
+    #    '_hostname_overridden')
+    options.server = ansible_module.params.get('server')
+    options.skip_conncheck = ansible_module.params.get('skip_conncheck')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    # prepare (install prepare, install checks) #
+
+    ##########################################################################
+    # replica promote_check ##################################################
+    ##########################################################################
+
+    ansible_log.debug("== PROMOTE CHECK ==")
+
+    #ansible_log.debug("-- NO_NTP --") # already done in test
+
+    ## check selinux status, http and DS ports, NTP conflicting services
+    #common_check(options.no_ntp)
+
+    ansible_log.debug("-- ENROLLED? --")
+
+    client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+    if not client_fstore.has_files():
+        try:
+            with redirect_stdout(ansible_log):
+                # do not use ensure_enrolled, it uses redirect_output
+                # ensure_enrolled(installer)
+
+                args = [paths.IPA_CLIENT_INSTALL, "--unattended", "--no-ntp"]
+                stdin = None
+                nolog = []
+
+                if installer.domain_name:
+                    args.extend(["--domain", installer.domain_name])
+                if installer.server:
+                    args.extend(["--server", installer.server])
+                if installer.realm_name:
+                    args.extend(["--realm", installer.realm_name])
+                if installer.host_name:
+                    args.extend(["--hostname", installer.host_name])
+
+                if installer.password:
+                    args.extend(["--password", installer.password])
+                    nolog.append(installer.password)
+                else:
+                    if installer.admin_password:
+                        # Always set principal if password was set explicitly,
+                        # the password itself gets passed directly via stdin
+                        args.extend(["--principal", installer.principal or "admin"])
+                        stdin = installer.admin_password
+                    if installer.keytab:
+                        args.extend(["--keytab", installer.keytab])
+
+                if installer.no_dns_sshfp:
+                    args.append("--no-dns-sshfp")
+                if installer.ssh_trust_dns:
+                    args.append("--ssh-trust-dns")
+                if installer.no_ssh:
+                    args.append("--no-ssh")
+                if installer.no_sshd:
+                    args.append("--no-sshd")
+                if installer.mkhomedir:
+                    args.append("--mkhomedir")
+                if installer.force_join:
+                    args.append("--force-join")
+
+                ansible_log.debug(" ".join(args))
+                try:
+                    # Call client install script
+                    service.print_msg("Configuring client side components")
+                    installer._enrollment_performed = True
+                    ipautil.run(args, stdin=stdin, nolog=nolog) #, redirect_output=True)
+                    #print()
+                except ipautil.CalledProcessError:
+                    raise ScriptError("Configuration of client side components failed!")
+
+        except ScriptError as msg:
+            ansible_module.fail_json(msg=str(msg))
+    else:
+        if (options.domain_name or options.server or options.realm_name or
+            options.host_name or options.password or options.keytab):
+            ansible_module.log(
+                "IPA client is already configured on this system, ignoring "
+                "the --domain, --server, --realm, --hostname, --password "
+                "and --keytab options.")
+
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+
+    installer._enrollment_performed = False
+    installer._top_dir = tempfile.mkdtemp("ipa")
+
+    #with ipautil.private_ccache():
+    dir_path = tempfile.mkdtemp(prefix='krbcc')
+    os.environ['KRB5CCNAME'] = os.path.join(dir_path, 'ccache')
+
+    ansible_log.debug("-- API --")
+
+    env = Env()
+    env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
+    env._finalize_core(**dict(constants.DEFAULT_CONFIG))
+
+    # 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()
+
+    ansible_log.debug("-- REPLICA_CONFIG --")
+
+    config = ReplicaConfig()
+    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 = installer._top_dir
+    config.basedn = api.env.basedn
+
+    # load and check certificates #
+
+    ansible_log.debug("-- CERT_FILES --")
+
+    http_pkcs12_file = None
+    http_pkcs12_info = None
+    http_ca_cert = None
+    dirsrv_pkcs12_file = None
+    dirsrv_pkcs12_info = None
+    dirsrv_ca_cert = None
+    pkinit_pkcs12_file = None
+    pkinit_pkcs12_info = None
+    pkinit_ca_cert = None
+
+    if options.http_cert_files:
+        ansible_log.debug("-- HTTP_CERT_FILES --")
+        if options.http_pin is None:
+            ansible_module.fail_json(msg=
+                "Apache Server private key unlock password required")
+        http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12(
+            cert_files=options.http_cert_files,
+            key_password=options.http_pin,
+            key_nickname=options.http_cert_name,
+            ca_cert_files=options.ca_cert_files,
+            host_name=config.host_name)
+        http_pkcs12_info = (http_pkcs12_file.name, http_pin)
+
+    if options.dirsrv_cert_files:
+        ansible_log.debug("-- DIRSRV_CERT_FILES --")
+        if options.dirsrv_pin is None:
+            ansible_module.fail_json(msg=
+                "Directory Server private key unlock password required")
+        dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12(
+            cert_files=options.dirsrv_cert_files,
+            key_password=options.dirsrv_pin,
+            key_nickname=options.dirsrv_cert_name,
+            ca_cert_files=options.ca_cert_files,
+            host_name=config.host_name)
+        dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin)
+
+    if options.pkinit_cert_files:
+        ansible_log.debug("-- PKINIT_CERT_FILES --")
+        if options.pkinit_pin is None:
+            ansible_module.fail_json(msg=
+                "Kerberos KDC private key unlock password required")
+        pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12(
+            cert_files=options.pkinit_cert_files,
+            key_password=options.pkinit_pin,
+            key_nickname=options.pkinit_cert_name,
+            ca_cert_files=options.ca_cert_files,
+            realm_name=config.realm_name)
+        pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin)
+
+    if (options.http_cert_files and options.dirsrv_cert_files and
+            http_ca_cert != dirsrv_ca_cert):
+        ansible_module.fail_json(
+            msg="Apache Server SSL certificate and Directory "
+            "Server SSL certificate are not signed by the same"
+            " CA certificate")
+
+    if (options.http_cert_files and
+            options.pkinit_cert_files and
+            http_ca_cert != pkinit_ca_cert):
+        ansible_module.fail_json(
+            msg="Apache Server SSL certificate and PKINIT KDC "
+            "certificate are not signed by the same CA "
+            "certificate")
+
+    ansible_log.debug("-- FQDN --")
+
+    installutils.verify_fqdn(config.host_name, options.no_host_dns)
+    installutils.verify_fqdn(config.master_host_name, options.no_host_dns)
+
+    ansible_log.debug("-- KINIT_KEYTAB --")
+
+    ccache = os.environ['KRB5CCNAME']
+    kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env),
+                 paths.KRB5_KEYTAB,
+                 ccache)
+
+    ansible_log.debug("-- CA_CRT --")
+
+    cafile = paths.IPA_CA_CRT
+    if not os.path.isfile(cafile):
+        ansible_module.fail_json(
+            msg="CA cert file is not available! Please reinstall"
+            "the client and try again.")
+
+    ansible_log.debug("-- REMOTE_API --")
+
+    ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
+    xmlrpc_uri = 'https://{}/ipa/xml'.format(
+        ipautil.format_netloc(config.master_host_name))
+    remote_api = create_api(mode=None)
+    remote_api.bootstrap(in_server=True,
+                         context='installer',
+                         confdir=paths.ETC_IPA,
+                         ldap_uri=ldapuri,
+                         xmlrpc_uri=xmlrpc_uri)
+    remote_api.finalize()
+    installer._remote_api = remote_api
+
+    ansible_log.debug("-- RPC_CLIENT --")
+
+    with rpc_client(remote_api) as client:
+        check_remote_version(client, parse_version(api.env.version))
+        check_remote_fips_mode(client, api.env.fips_mode)
+
+    conn = remote_api.Backend.ldap2
+    replman = None
+    try:
+        ansible_log.debug("-- CONNECT --")
+        # Try out authentication
+        conn.connect(ccache=ccache)
+        replman = ReplicationManager(config.realm_name,
+                                     config.master_host_name, None)
+
+        ansible_log.debug("-- CHECK IPA_DOMAIN --")
+
+        promotion_check_ipa_domain(conn, remote_api.env.basedn)
+
+        ansible_log.debug("-- CHECK DOMAIN_LEVEL --")
+
+        domain_level = current_domain_level(remote_api)
+        check_domain_level_is_supported(domain_level)
+        if domain_level < constants.DOMAIN_LEVEL_1:
+            ansible_module.fail_json(msg=
+                "You used the wrong mechanism to install a replica in "
+                "domain level {dl}:\n"
+                "\tFor domain level >= 1 replica installation, first join the "
+                "domain by running ipa-client-install, then run "
+                "ipa-replica-install without a replica file."
+                .format(dl=domain_level)
+            )
+
+        ansible_log.debug("-- CHECK AUTHORIZATION --")
+
+        # Check authorization
+        result = remote_api.Command['hostgroup_find'](
+            cn=u'ipaservers',
+            host=[unicode(api.env.host)]
+        )['result']
+        add_to_ipaservers = not result
+
+        ansible_log.debug("-- ADD_TO_IPASERVERS --")
+
+        if add_to_ipaservers:
+            if options.password and not options.admin_password:
+                raise errors.ACIError(info="Not authorized")
+
+            if installer._ccache is None:
+                del os.environ['KRB5CCNAME']
+            else:
+                os.environ['KRB5CCNAME'] = installer._ccache
+
+            try:
+                installutils.check_creds(options, config.realm_name)
+                installer._ccache = os.environ.get('KRB5CCNAME')
+            finally:
+                os.environ['KRB5CCNAME'] = ccache
+
+            conn.disconnect()
+            conn.connect(ccache=installer._ccache)
+
+            try:
+                result = remote_api.Command['hostgroup_show'](
+                    u'ipaservers',
+                    all=True,
+                    rights=True
+                )['result']
+
+                if 'w' not in result['attributelevelrights']['member']:
+                    raise errors.ACIError(info="Not authorized")
+            finally:
+                ansible_log.debug("-- RECONNECT --")
+                conn.disconnect()
+                conn.connect(ccache=ccache)
+
+        ansible_log.debug("-- CHECK FOR REPLICATION AGREEMENT --")
+
+        # Check that we don't already have a replication agreement
+        if replman.get_replication_agreement(config.host_name):
+            msg = ("A replication agreement for this host already exists. "
+                   "It needs to be removed.\n"
+                   "Run this command:\n"
+                   "    %% ipa-replica-manage del {host} --force"
+                   .format(host=config.host_name))
+            raise ScriptError(msg, rval=3)
+
+        ansible_log.debug("-- DETECT REPLICATION MANAGER GROUP --")
+
+        # Detect if the other master can handle replication managers
+        # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX
+        dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
+                ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name))
+        try:
+            conn.get_entry(dn)
+        except errors.NotFound:
+            msg = ("The Replication Managers group is not available in "
+                   "the domain. Replica promotion requires the use of "
+                   "Replication Managers to be able to replicate data. "
+                   "Upgrade the peer master or use the ipa-replica-prepare "
+                   "command on the master and use a prep file to install "
+                   "this replica.")
+            logger.error("%s", msg)
+            raise ScriptError(rval=3)
+
+        ansible_log.debug("-- CHECK DNS_MASTERS --")
+
+        dns_masters = remote_api.Object['dnsrecord'].get_dns_masters()
+        if dns_masters:
+            if not options.no_host_dns:
+                logger.debug('Check forward/reverse DNS resolution')
+                resolution_ok = (
+                    check_dns_resolution(config.master_host_name,
+                                         dns_masters) and
+                    check_dns_resolution(config.host_name, dns_masters))
+                if not resolution_ok and installer.interactive:
+                    if not ipautil.user_input("Continue?", False):
+                        raise ScriptError(rval=0)
+        else:
+            logger.debug('No IPA DNS servers, '
+                         'skipping forward/reverse resolution check')
+
+        ansible_log.debug("-- GET_IPA_CONFIG --")
+
+        entry_attrs = conn.get_ipa_config()
+        subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0]
+        if subject_base is not None:
+            config.subject_base = DN(subject_base)
+
+        ansible_log.debug("-- SEARCH FOR CA --")
+
+        # Find if any server has a CA
+        ca_host = service.find_providing_server(
+                'CA', conn, config.ca_host_name)
+        if ca_host is not None:
+            config.ca_host_name = ca_host
+            ca_enabled = True
+            if options.dirsrv_cert_files:
+                logger.error("Certificates could not be provided when "
+                             "CA is present on some master.")
+                raise ScriptError(rval=3)
+        else:
+            if options.setup_ca:
+                logger.error("The remote master does not have a CA "
+                             "installed, can't set up CA")
+                raise ScriptError(rval=3)
+            ca_enabled = False
+            if not options.dirsrv_cert_files:
+                logger.error("Cannot issue certificates: a CA is not "
+                             "installed. Use the --http-cert-file, "
+                             "--dirsrv-cert-file options to provide "
+                             "custom certificates.")
+                raise ScriptError(rval=3)
+
+        ansible_log.debug("-- SEARCH FOR KRA --")
+
+        kra_host = service.find_providing_server(
+                'KRA', conn, config.kra_host_name)
+        if kra_host is not None:
+            config.kra_host_name = kra_host
+            kra_enabled = True
+        else:
+            if options.setup_kra:
+                logger.error("There is no KRA server in the domain, "
+                             "can't setup a KRA clone")
+                raise ScriptError(rval=3)
+            kra_enabled = False
+
+        ansible_log.debug("-- CHECK CA --")
+
+        if ca_enabled:
+            options.realm_name = config.realm_name
+            options.host_name = config.host_name
+            ca.install_check(False, config, options)
+
+            ansible_log.debug("  ca.external_cert_file=%s" % repr(ca.external_cert_file))
+            ansible_log.debug("  ca.external_ca_file=%s" % repr(ca.external_ca_file))
+
+            # TODO
+            # TODO
+            # Save global vars external_cert_file, external_ca_file for
+            # later use
+            # TODO
+            # TODO
+
+        ansible_log.debug("-- CHECK KRA --")
+
+        if kra_enabled:
+            try:
+                kra.install_check(remote_api, config, options)
+            except RuntimeError as e:
+                raise ScriptError(e)
+
+        ansible_log.debug("-- CHECK DNS --")
+
+        if options.setup_dns:
+            dns.install_check(False, remote_api, True, options,
+                              config.host_name)
+            config.ips = dns.ip_addresses
+        else:
+            config.ips = installutils.get_server_ip_address(
+                config.host_name, not installer.interactive,
+                False, options.ip_addresses)
+
+            # check addresses here, dns module is doing own check
+            no_matching_interface_for_ip_address_warning(config.ips)
+
+        ansible_log.debug("-- CHECK ADTRUST --")
+
+        if options.setup_adtrust:
+            adtrust.install_check(False, options, remote_api)
+
+    except errors.ACIError:
+        logger.debug("%s", traceback.format_exc())
+        raise ScriptError("\nInsufficient privileges to promote the server."
+                          "\nPossible issues:"
+                          "\n- A user has insufficient privileges"
+                          "\n- This client has insufficient privileges "
+                          "to become an IPA replica")
+    except errors.LDAPError:
+        logger.debug("%s", traceback.format_exc())
+        raise ScriptError("\nUnable to connect to LDAP server %s" %
+                          config.master_host_name)
+    finally:
+        if replman and replman.conn:
+            ansible_log.debug("-- UNBIND REPLMAN--")
+            replman.conn.unbind()
+        if conn.isconnected():
+            ansible_log.debug("-- DISCONNECT --")
+            conn.disconnect()
+
+    ansible_log.debug("-- CHECK CONNECTION --")
+
+    # check connection
+    if not options.skip_conncheck:
+        if add_to_ipaservers:
+            # use user's credentials when the server host is not ipaservers
+            if installer._ccache is None:
+                del os.environ['KRB5CCNAME']
+            else:
+                os.environ['KRB5CCNAME'] = installer._ccache
+
+        try:
+            with redirect_stdout(ansible_log):
+                replica_conn_check(
+                    config.master_host_name, config.host_name,
+                    config.realm_name, options.setup_ca, 389,
+                    options.admin_password, principal=options.principal,
+                    ca_cert_file=cafile)
+        finally:
+            if add_to_ipaservers:
+                os.environ['KRB5CCNAME'] = ccache
+
+    installer._ca_enabled = ca_enabled
+    installer._kra_enabled = kra_enabled
+    installer._ca_file = cafile
+    installer._fstore = fstore
+    installer._sstore = sstore
+    installer._config = config
+    installer._add_to_ipaservers = add_to_ipaservers
+
+    # done #
+
+    ansible_module.exit_json(changed=True,
+                             ccache=ccache,
+                             installer_ccache=installer._ccache,
+                             subject_base=str(config.subject_base),
+                             _ca_enabled=ca_enabled,
+                             _ca_subject=str(options._ca_subject),
+                             _subject_base=str(options._subject_base) if options._subject_base is not None else None,
+                             _kra_enabled=kra_enabled,
+                             _ca_file=cafile,
+                             _top_dir=installer._top_dir,
+                             _add_to_ipaservers=add_to_ipaservers,
+                             _dirsrv_pkcs12_file=dirsrv_pkcs12_file,
+                             _dirsrv_pkcs12_info=dirsrv_pkcs12_info,
+                             _dirsrv_ca_cert=dirsrv_ca_cert,
+                             _http_pkcs12_file=http_pkcs12_file,
+                             _http_pkcs12_info=http_pkcs12_info,
+                             _http_ca_cert=http_ca_cert,
+                             _pkinit_pkcs12_file=pkinit_pkcs12_file,
+                             _pkinit_pkcs12_info=pkinit_pkcs12_info,
+                             _pkinit_ca_cert=pkinit_ca_cert,
+                             no_dnssec_validation=options.no_dnssec_validation,
+                             config_setup_ca=config.setup_ca,
+                             config_master_host_name=config.master_host_name,
+                             config_ca_host_name=config.ca_host_name,
+                             config_ips=[ str(ip) for ip in config.ips ])
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_promote_openldap_conf.py b/roles/ipareplica/library/ipareplica_promote_openldap_conf.py
new file mode 100644
index 00000000..308c6e2d
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_promote_openldap_conf.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_promote_openldap_conf
+short description: Promote openldap.conf
+description:
+  Promote openldap.conf
+options:
+  setup_kra:
+    description: 
+    required: no
+  subject_base:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### server ###
+            setup_kra=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            ccache=dict(required=True),
+            _top_dir = dict(required=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    options._top_dir = ansible_module.params.get('_top_dir')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    installer.setup_ca = config_setup_ca
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+
+    ansible_log.debug("== INSTALL ==")
+
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+    config.setup_ca = config_setup_ca
+    config.master_host_name = config_master_host_name
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- PROMOTE OPENLDAP_CONF--")
+
+        promote_openldap_conf(config.host_name, config.master_host_name)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_promote_sssd.py b/roles/ipareplica/library/ipareplica_promote_sssd.py
new file mode 100644
index 00000000..f8bcd317
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_promote_sssd.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_promote_sssd
+short description: Promote sssd
+description:
+  Promote sssd
+options:
+  setup_kra:
+    description: 
+    required: no
+  subject_base:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### server ###
+            setup_kra=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            ccache=dict(required=True),
+            _top_dir = dict(required=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    options._top_dir = ansible_module.params.get('_top_dir')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    installer.setup_ca = config_setup_ca
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+    config.setup_ca = config_setup_ca
+    config.master_host_name = config_master_host_name
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- PROMOTE SSSD --")
+
+        promote_sssd(config.host_name)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_restart_kdc.py b/roles/ipareplica/library/ipareplica_restart_kdc.py
new file mode 100644
index 00000000..05b91596
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_restart_kdc.py
@@ -0,0 +1,148 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_restart_kdc
+short description: Restart KDC
+description:
+  Restart KDC
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    # krb
+    krb = krbinstance.KrbInstance(fstore)
+    krb.set_output(ansible_log)
+    with redirect_stdout(ansible_log):
+        krb.init_info(api.env.realm, api.env.host,
+                      setup_pkinit=not options.no_pkinit,
+                      subject_base=options.subject_base)
+
+        ansible_log.debug("-- RESTART KDC --")
+
+        service.print_msg("Restarting the KDC")
+        krb.restart()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_adtrust.py b/roles/ipareplica/library/ipareplica_setup_adtrust.py
new file mode 100644
index 00000000..b892f330
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_adtrust.py
@@ -0,0 +1,144 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_adtrust
+short description: Setup adtrust
+description:
+  Setup adtrust
+options:
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            ccache=dict(required=True),
+            _top_dir = dict(required=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    options._top_dir = ansible_module.params.get('_top_dir')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    with redirect_stdout(ansible_log):
+        #if options.setup_adtrust:
+        ansible_log.debug("-- INSTALL ADTRUST --")
+
+        adtrust.install(False, options, fstore, api)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_ca.py b/roles/ipareplica/library/ipareplica_setup_ca.py
new file mode 100644
index 00000000..b322da58
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_ca.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_ca
+short description: Setup CA
+description:
+  Setup CA
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  no_ui_redirect:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _dirsrv_pkcs12_info:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  _ca_subject:
+    description: 
+    required: yes
+  _subject_base:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  config_ca_host_name:
+    description: 
+    required: yes
+  config_ips:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            _ca_subject=dict(required=True),
+            _subject_base=dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+            config_ca_host_name=dict(required=True),
+            config_ips=dict(required=False, type='list', default=[]),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    installer._dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    installer._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    options._ca_subject = ansible_module.params.get('_ca_subject')
+    if options._ca_subject is not None:
+        options._ca_subject = DN(options._ca_subject)
+    options._subject_base = ansible_module.params.get('_subject_base')
+    if options._subject_base is not None:
+        options._subject_base = DN(options._subject_base)
+    dirman_password = ansible_module.params.get('dirman_password')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+    config_ca_host_name = ansible_module.params.get('config_ca_host_name')
+    config_ips = ansible_module_get_parsed_ip_addresses(ansible_module,
+                                                        "config_ips")
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+    config.setup_ca = config_setup_ca
+    config.master_host_name = config_master_host_name
+    config.ca_host_name = config_ca_host_name
+    config.ips = config_ips
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    options._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    #conn.connect(ccache=ccache)
+
+    ansible_log.debug("-- INSTALL CA --")
+
+    with redirect_stdout(ansible_log):
+        options.realm_name = config.realm_name
+        options.domain_name = config.domain_name
+        options.host_name = config.host_name
+        options.dm_password = config.dirman_password
+        ca.install(False, config, options)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_certmonger.py b/roles/ipareplica/library/ipareplica_setup_certmonger.py
new file mode 100644
index 00000000..d9829757
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_certmonger.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_certmonger
+short description: Setup certmonger
+description:
+  Setup certmonger
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- CONFIGURE_CERTMONGER --")
+
+        # FIXME: allow to use passed in certs instead
+        configure_certmonger()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_custodia.py b/roles/ipareplica/library/ipareplica_setup_custodia.py
new file mode 100644
index 00000000..628eb495
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_custodia.py
@@ -0,0 +1,180 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_custodia
+short description: Setup custodia
+description:
+  Setup custodia
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  no_ui_redirect:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    options._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- INSTALL_CUSTODIA --")
+
+        custodia = custodiainstance.CustodiaInstance(config.host_name,
+                                                     config.realm_name)
+        if promote:
+            ansible_log.debug("-- CUSTODIA CREATE_REPLICA --")
+            custodia.create_replica(config.master_host_name)
+        else:
+            ansible_log.debug("-- CUSTODIA CREATE_INSTANCE --")
+            custodia.create_instance()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_dns.py b/roles/ipareplica/library/ipareplica_setup_dns.py
new file mode 100644
index 00000000..6d6701a7
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_dns.py
@@ -0,0 +1,150 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_dns
+short description: Setup DNS
+description:
+  Setup DNS
+options:
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### server ###
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            ccache=dict(required=True),
+            _top_dir = dict(required=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    options._top_dir = ansible_module.params.get('_top_dir')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+    config.master_host_name = config_master_host_name
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+
+    with redirect_stdout(ansible_log):
+        if options.setup_dns:
+            ansible_log.debug("-- INSTALL DNS --")
+            dns.install(False, True, options, api)
+        else:
+            ansible_log.debug("-- DNS UPDATE_SYSTEM_RECORDS --")
+            api.Command.dns_update_system_records()
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_ds.py b/roles/ipareplica/library/ipareplica_setup_ds.py
new file mode 100644
index 00000000..24b976aa
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_ds.py
@@ -0,0 +1,341 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_ds
+short description: Setup DS
+description:
+  Setup DS
+options:
+  dm_password:
+    description: Directory Manager password
+    required: yes
+  password:
+    description: Admin user kerberos password
+    required: yes
+  ip_addresses:
+    description: List of Master Server IP Addresses
+    required: no
+  domain:
+    description: Primary DNS domain of the IPA deployment
+    required: yes
+  realm:
+    description: Kerberos realm name of the IPA deployment
+    required: yes
+  hostname:
+    description: Fully qualified name of this host
+    required: yes
+  ca_cert_files:
+    description: List of iles containing CA certificates for the service certificate files
+    required: yes
+  no_host_dns:
+    description: Do not use DNS for hostname lookup during installation
+    required: yes
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  dirserv_cert_files:
+    description: 
+    required: yes
+  force_join:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  server:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  installer_ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _kra_enabled:
+    description: 
+    required: yes
+  _dirsrv_pkcs12_info:
+    description: 
+    required: yes
+  _http_pkcs12_info:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  _add_to_ipaservers:
+    description: 
+    required: yes
+  _ca_subject:
+    description: 
+    required: yes
+  _subject_base:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+  config_setup_ca:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  config_ca_host_name:
+    description: 
+    required: yes
+  config_ips:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            dm_password=dict(required=False, no_log=True),
+            password=dict(required=False, no_log=True),
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            no_host_dns=dict(required=False, type='bool', default=False),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool', default=False),
+            dirsrv_config_file=dict(required=False),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            ### client ###
+            force_join=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            server=dict(required=True),
+            ccache=dict(required=True),
+            installer_ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _kra_enabled=dict(required=False, type='bool'),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _http_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            _add_to_ipaservers = dict(required=True),
+            _ca_subject=dict(required=True),
+            _subject_base=dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+            config_setup_ca=dict(required=True),
+            config_master_host_name=dict(required=True),
+            config_ca_host_name=dict(required=True),
+            config_ips=dict(required=False, type='list', default=[]),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    options.dm_password = ansible_module.params.get('dm_password')
+    options.password = options.dm_password
+    options.admin_password = ansible_module.params.get('password')
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    options.no_host_dns = ansible_module.params.get('no_host_dns')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    options.dirsrv_config_file = ansible_module.params.get('dirsrv_config_file')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    ### client ###
+    options.force_join = ansible_module.params.get('force_join')
+    ### certificate system ###
+    options.external_ca = ansible_module.params.get('external_ca')
+    options.external_cert_files = ansible_module.params.get(
+        'external_cert_files')
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options.ca_subject = ansible_module.params.get('ca_subject')
+    ### dns ###
+    options.reverse_zones = ansible_module.params.get('reverse_zones')
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+
+    ### additional ###
+    #options._host_name_overridden = ansible_module.params.get(
+    #    '_hostname_overridden')
+    options.server = ansible_module.params.get('server')
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    kra_enabled = ansible_module.params.get('_kra_enabled')
+
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    http_pkcs12_info = ansible_module.params.get('_http_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options._top_dir = ansible_module.params.get('_top_dir')
+    options._add_to_ipaservers = ansible_module.params.get('_add_to_ipaservers')
+
+    options._ca_subject = ansible_module.params.get('_ca_subject')
+    options._subject_base = ansible_module.params.get('_subject_base')
+
+    dirman_password = ansible_module.params.get('dirman_password')
+    config_setup_ca = ansible_module.params.get('config_setup_ca')
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+    config_ca_host_name = ansible_module.params.get('config_ca_host_name')
+    config_ips = ansible_module_get_parsed_ip_addresses(ansible_module,
+                                                        "config_ips")
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+    config.dirman_password = dirman_password
+    config.setup_ca = config_setup_ca
+    config.master_host_name = config_master_host_name
+    config.ca_host_name = config_ca_host_name
+    config.ips = config_ips
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    cafile = paths.IPA_CA_CRT
+    try:
+        ansible_log.debug("-- CONNECT --")
+        if promote:
+            conn.connect(ccache=ccache)
+        else:
+            # dmlvl 0 replica install should always use DM credentials
+            # to create remote LDAP connection. Since ACIs permitting hosts
+            # to manage their own services were added in 4.2 release,
+            # the master denies this operations.
+            conn.connect(bind_dn=ipaldap.DIRMAN_DN, cacert=cafile,
+                         bind_pw=dirman_password)
+
+        ansible_log.debug("-- CONFIGURE DIRSRV --")
+        # Configure dirsrv
+        with redirect_stdout(ansible_log):
+            ds = install_replica_ds(config, options, ca_enabled,
+                                    remote_api,
+                                    ca_file=cafile,
+                                    promote=promote,
+                                    pkcs12_info=dirsrv_pkcs12_info)
+            #show_obj(ds)
+
+        ansible_log.debug("-- INSTALL DNS RECORDS --")
+        # Always try to install DNS records
+        install_dns_records(config, options, remote_api)
+
+        ansible_log.debug("-- NTP LDAP ENABLE --")
+        ntpinstance.ntp_ldap_enable(config.host_name, ds.suffix,
+                                    remote_api.env.realm)
+    finally:
+        if conn.isconnected():
+            ansible_log.debug("-- DISCONNECT --")
+            conn.disconnect()
+
+    # done #
+
+    ansible_module.exit_json(changed=True,
+                             ds_suffix=str(ds.suffix),
+                             ds_ca_subject=str(ds.ca_subject))
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_http.py b/roles/ipareplica/library/ipareplica_setup_http.py
new file mode 100644
index 00000000..ff6450a3
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_http.py
@@ -0,0 +1,180 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_http
+short description: Setup HTTP
+description:
+  Setup HTTP
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  no_ui_redirect:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _http_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _http_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    options.no_ui_redirect = ansible_module.params.get('no_ui_redirect')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    http_pkcs12_info = ansible_module.params.get('_http_pkcs12_info')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    cafile = paths.IPA_CA_CRT
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- INSTALL_HTTP --")
+
+        install_http(
+            config,
+            auto_redirect=not options.no_ui_redirect,
+            promote=promote,
+            pkcs12_info=http_pkcs12_info,
+            ca_is_configured=ca_enabled,
+            ca_file=cafile)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_kra.py b/roles/ipareplica/library/ipareplica_setup_kra.py
new file mode 100644
index 00000000..5a96fd9f
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_kra.py
@@ -0,0 +1,225 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_kra
+short description: Setup KRA
+description:
+  Setup KRA
+options:
+  dm_password:
+    description: Directory Manager password
+    required: yes
+  password:
+    description: Admin user kerberos password
+    required: yes
+  ip_addresses:
+    description: List of Master Server IP Addresses
+    required: no
+  domain:
+    description: Primary DNS domain of the IPA deployment
+    required: yes
+  realm:
+    description: Kerberos realm name of the IPA deployment
+    required: yes
+  hostname:
+    description: Fully qualified name of this host
+    required: yes
+  ca_cert_files:
+    description: List of iles containing CA certificates for the service certificate files
+    required: yes
+  no_host_dns:
+    description: Do not use DNS for hostname lookup during installation
+    required: yes
+  setup_adtrust:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  setup_dns:
+    description: 
+    required: yes
+  external_ca:
+    description: 
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            dm_password=dict(required=False, no_log=True),
+            password=dict(required=False, no_log=True),
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            no_host_dns=dict(required=False, type='bool', default=False),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool'),
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            setup_dns=dict(required=False, type='bool'),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            ### client ###
+            force_join=dict(required=False, type='bool'),
+            ### certificate system ###
+            subject_base=dict(required=True),
+            ### additional ###
+            server=dict(required=True),
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            installer_ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _kra_enabled=dict(required=False, type='bool'),
+            _dirsrv_pkcs12_info = dict(required=False),
+            _http_pkcs12_info = dict(required=False),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+            _add_to_ipaservers = dict(required=True),
+            _ca_subject=dict(required=True),
+            _subject_base=dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    options.dm_password = ansible_module.params.get('dm_password')
+    options.password = options.dm_password
+    options.admin_password = ansible_module.params.get('password')
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    options.no_host_dns = ansible_module.params.get('no_host_dns')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    ### client ###
+    options.force_join = ansible_module.params.get('force_join')
+    ### certificate system ###
+    options.external_ca = ansible_module.params.get('external_ca')
+    options.external_cert_files = ansible_module.params.get(
+        'external_cert_files')
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options.ca_subject = ansible_module.params.get('ca_subject')
+    ### dns ###
+    options.reverse_zones = ansible_module.params.get('reverse_zones')
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+    ### additional ###
+    options.server = ansible_module.params.get('server')
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    #os.environ['KRB5CCNAME'] = ccache
+    os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    kra_enabled = ansible_module.params.get('_kra_enabled')
+
+    dirsrv_pkcs12_info = ansible_module.params.get('_dirsrv_pkcs12_info')
+    http_pkcs12_info = ansible_module.params.get('_http_pkcs12_info')
+    pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    options._top_dir = ansible_module.params.get('_top_dir')
+    options._add_to_ipaservers = ansible_module.params.get('_add_to_ipaservers')
+
+    options._ca_subject = ansible_module.params.get('_ca_subject')
+    options._subject_base = ansible_module.params.get('_subject_base')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- INSTALL KRA --")
+
+        kra.install(api, config, options)
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_krb.py b/roles/ipareplica/library/ipareplica_setup_krb.py
new file mode 100644
index 00000000..a5c19520
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_krb.py
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_krb
+short description: Setup KRB
+description:
+  Setup KRB
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _pkinit_pkcs12_info:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _pkinit_pkcs12_info = dict(required=False),
+            _top_dir = dict(required=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    ### server ###
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    config_master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    installer._pkinit_pkcs12_info = ansible_module.params.get('_pkinit_pkcs12_info')
+
+    options._top_dir = ansible_module.params.get('_top_dir')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+    pkinit_pkcs12_info = installer._pkinit_pkcs12_info
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.master_host_name = config_master_host_name
+    config.subject_base = options.subject_base
+
+    remote_api = gen_remote_api(config.master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+
+    ansible_log.debug("-- INSTALL_KRB --")
+
+    with redirect_stdout(ansible_log):
+        krb = install_krb(
+            config,
+            setup_pkinit=not options.no_pkinit,
+            pkcs12_info=pkinit_pkcs12_info,
+            promote=promote)
+
+    # done #
+
+    ansible_module.exit_json(changed=True,
+                             config_master_host_name=config.master_host_name)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_setup_otpd.py b/roles/ipareplica/library/ipareplica_setup_otpd.py
new file mode 100644
index 00000000..71d1ef84
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_setup_otpd.py
@@ -0,0 +1,171 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_setup_otpd
+short description: Setup OTPD
+description:
+  Setup OTPD
+options:
+  setup_ca:
+    description: 
+    required: yes
+  setup_kra:
+    description: 
+    required: yes
+  no_pkinit:
+    description: 
+    required: yes
+  no_ui_redirect:
+    description: 
+    required: yes
+  subject_base:
+    description: 
+    required: yes
+  config_master_host_name:
+    description: 
+    required: yes
+  ccache:
+    description: 
+    required: yes
+  _ca_enabled:
+    description: 
+    required: yes
+  _ca_file:
+    description: 
+    required: yes
+  _top_dir:
+    description: 
+    required: yes
+  dirman_password:
+    description: 
+    required: yes
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            #### server ###
+            setup_ca=dict(required=False, type='bool'),
+            setup_kra=dict(required=False, type='bool'),
+            no_pkinit=dict(required=False, type='bool'),
+            no_ui_redirect=dict(required=False, type='bool'),
+            #### certificate system ###
+            subject_base=dict(required=True),
+            #### additional ###
+            config_master_host_name=dict(required=True),
+            ccache=dict(required=True),
+            _ca_enabled=dict(required=False, type='bool'),
+            _ca_file=dict(required=False),
+            _top_dir = dict(required=True),
+            dirman_password=dict(required=True, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    options = installer
+    options.setup_ca = ansible_module.params.get('setup_ca')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    ### certificate system ###
+    options.subject_base = ansible_module.params.get('subject_base')
+    if options.subject_base is not None:
+        options.subject_base = DN(options.subject_base)
+    ### additional ###
+    master_host_name = ansible_module.params.get('config_master_host_name')
+    ccache = ansible_module.params.get('ccache')
+    os.environ['KRB5CCNAME'] = ccache
+    #os.environ['KRB5CCNAME'] = ansible_module.params.get('installer_ccache')
+    #installer._ccache = ansible_module.params.get('installer_ccache')
+    ca_enabled = ansible_module.params.get('_ca_enabled')
+    options._top_dir = ansible_module.params.get('_top_dir')
+    dirman_password = ansible_module.params.get('dirman_password')
+
+    # init #
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ansible_log.debug("== INSTALL ==")
+
+    options = installer
+    promote = installer.promote
+
+    env = gen_env_boostrap_finalize_core(paths.ETC_IPA,
+                                         constants.DEFAULT_CONFIG)
+    api_bootstrap_finalize(env)
+    config = gen_ReplicaConfig()
+    config.dirman_password = dirman_password
+
+    remote_api = gen_remote_api(master_host_name, paths.ETC_IPA)
+    #installer._remote_api = remote_api
+
+    conn = remote_api.Backend.ldap2
+    ccache = os.environ['KRB5CCNAME']
+
+    # There is a api.Backend.ldap2.connect call somewhere in ca, ds, dns or
+    # ntpinstance
+    api.Backend.ldap2.connect()
+    conn.connect(ccache=ccache)
+
+    cafile = paths.IPA_CA_CRT
+    with redirect_stdout(ansible_log):
+        ansible_log.debug("-- INSTALL_OTPD --")
+
+        otpd = otpdinstance.OtpdInstance()
+        otpd.set_output(ansible_log)
+        otpd.create_instance('OTPD', config.host_name,
+                             ipautil.realm_to_suffix(config.realm_name))
+
+    # done #
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipareplica_test.py b/roles/ipareplica/library/ipareplica_test.py
new file mode 100644
index 00000000..9af95a80
--- /dev/null
+++ b/roles/ipareplica/library/ipareplica_test.py
@@ -0,0 +1,316 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-replica-install code
+#
+# Copyright (C) 2018  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: ipareplica_test
+short description:
+description:
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+import os
+import sys
+import logging
+import tempfile, shutil
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_replica import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            ### basic ###
+            ip_addresses=dict(required=False, type='list', default=[]),
+            domain=dict(required=False),
+            servers=dict(required=False, type='list', default=[]),
+            realm=dict(required=False),
+            hostname=dict(required=False),
+            ca_cert_files=dict(required=False, type='list', default=[]),
+            ### server ###
+            setup_adtrust=dict(required=False, type='bool', default=False),
+            setup_kra=dict(required=False, type='bool', default=False),
+            setup_dns=dict(required=False, type='bool', default=False),
+            no_pkinit=dict(required=False, type='bool', default=False),
+            dirsrv_config_file=dict(required=False),
+            ### ssl certificate ###
+            dirsrv_cert_files=dict(required=False, type='list', default=[]),
+            http_cert_files=dict(required=False, type='list', default=[]),
+            pkinit_cert_files=dict(required=False, type='list', default=[]),
+            ### client ###
+            no_ntp=dict(required=False, type='bool', default=False),
+            ### dns ###
+            no_reverse=dict(required=False, type='bool', default=False),
+            auto_reverse=dict(required=False, type='bool', default=False),
+            forwarders=dict(required=False, type='list', default=[]),
+            no_forwarders=dict(required=False, type='bool', default=False),
+            auto_forwarders=dict(required=False, type='bool', default=False),
+            forward_policy=dict(default=None, choices=['first', 'only']),
+            no_dnssec_validation=dict(required=False, type='bool',
+                                      default=False),
+        ),
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # get parameters #
+
+    ### basic ###
+    options.ip_addresses = ansible_module_get_parsed_ip_addresses(
+        ansible_module)
+    options.domain_name = ansible_module.params.get('domain')
+    options.servers = ansible_module.params.get('servers')
+    options.realm_name = ansible_module.params.get('realm')
+    options.host_name = ansible_module.params.get('hostname')
+    options.ca_cert_files = ansible_module.params.get('ca_cert_files')
+    ### server ###
+    options.setup_adtrust = ansible_module.params.get('setup_adtrust')
+    options.setup_kra = ansible_module.params.get('setup_kra')
+    options.setup_dns = ansible_module.params.get('setup_dns')
+    options.no_pkinit = ansible_module.params.get('no_pkinit')
+    options.dirsrv_config_file = ansible_module.params.get('dirsrv_config_file')
+    ### ssl certificate ###
+    options.dirsrv_cert_files = ansible_module.params.get('dirsrv_cert_files')
+    options.http_cert_files = ansible_module.params.get('http_cert_files')
+    options.pkinit_cert_files = ansible_module.params.get('pkinit_cert_files')
+    ### client ###
+    options.no_ntp = ansible_module.params.get('no_ntp')
+    ### dns ###
+    options.no_reverse = ansible_module.params.get('no_reverse')
+    options.auto_reverse = ansible_module.params.get('auto_reverse')
+    options.forwarders = ansible_module.params.get('forwarders')
+    options.no_forwarders = ansible_module.params.get('no_forwarders')
+    options.auto_forwarders = ansible_module.params.get('auto_forwarders')
+    options.forward_policy = ansible_module.params.get('forward_policy')
+    options.no_dnssec_validation = ansible_module.params.get(
+        'no_dnssec_validation')
+
+    ##########################################################################
+    # replica init ###########################################################
+    ##########################################################################
+
+    if installer.servers:
+        installer.server = installer.servers[0]
+    else:
+        installer.server = None
+    # TODO: Kills ipa-client-install
+    #if installer.replica_file is None:
+    #    installer.password = installer.admin_password
+    #else:
+    #    installer.password = installer.dm_password
+
+    #installer._ccache = os.environ.get('KRB5CCNAME')
+
+    # If not defined, set domain from server name
+    if installer.domain_name is None and installer.server is not None:
+        installer.domain_name = installer.server[installer.server.find(".")+1:]
+    # If not defined, set realm from domain name
+    if installer.realm_name is None and installer.domain_name is not None:
+        installer.realm_name = installer.domain_name.upper()
+
+    ##########################################################################
+    # other checks ###########################################################
+    ##########################################################################
+
+    # version specific tests #
+
+    if options.setup_adtrust and not adtrust_imported:
+        #if "adtrust" not in options._allow_missing:
+        ansible_module.fail_json(msg="adtrust can not be imported")
+        #else:
+        #  options.setup_adtrust = False
+        #  ansible_module.warn(msg="adtrust is not supported, disabling")
+
+    if options.setup_kra and not kra_imported:
+        #if "kra" not in options._allow_missing:
+        ansible_module.fail_json(msg="kra can not be imported")
+        #else:
+        #  options.setup_kra = False
+        #  ansible_module.warn(msg="kra is not supported, disabling")
+
+    # From ipa installer classes
+
+    # pkinit is not supported on DL0, don't allow related options
+    if installer.replica_file is not None:
+        ansible_module.fail_json(
+            msg="Replica installation using a replica file is not supported")
+
+    # If any of the key file options are selected, all are required.
+    cert_file_req = (installer.dirsrv_cert_files, installer.http_cert_files)
+    cert_file_opt = (installer.pkinit_cert_files,)
+    if not installer.no_pkinit:
+        cert_file_req += cert_file_opt
+    if installer.no_pkinit and installer.pkinit_cert_files:
+        ansible_module.fail_json(
+            msg="--no-pkinit and --pkinit-cert-file cannot be specified "
+            "together")
+    if any(cert_file_req + cert_file_opt) and not all(cert_file_req):
+        ansible_module.fail_json(
+            msg="--dirsrv-cert-file, --http-cert-file, and --pkinit-cert-file "
+            "or --no-pkinit are required if any key file options are used.")
+
+    if not installer.setup_dns:
+        if installer.forwarders:
+            ansible_module.fail_json(
+                msg="You cannot specify a --forwarder option without the "
+                "--setup-dns option")
+        if installer.auto_forwarders:
+            ansible_module.fail_json(
+                msg="You cannot specify a --auto-forwarders option without "
+                "the --setup-dns option")
+        if installer.no_forwarders:
+            ansible_module.fail_json(
+                msg="You cannot specify a --no-forwarders option without the "
+                "--setup-dns option")
+        if installer.forward_policy:
+            ansible_module.fail_json(
+                msg="You cannot specify a --forward-policy option without the "
+                "--setup-dns option")
+        if installer.reverse_zones:
+            ansible_module.fail_json(
+                msg="You cannot specify a --reverse-zone option without the "
+                "--setup-dns option")
+        if installer.auto_reverse:
+            ansible_module.fail_json(
+                msg="You cannot specify a --auto-reverse option without the "
+                "--setup-dns option")
+        if installer.no_reverse:
+            ansible_module.fail_json(
+                msg="You cannot specify a --no-reverse option without the "
+                "--setup-dns option")
+        if installer.no_dnssec_validation:
+            ansible_module.fail_json(
+                msg="You cannot specify a --no-dnssec-validation option "
+                "without the --setup-dns option")
+    elif installer.forwarders and installer.no_forwarders:
+        ansible_module.fail_json(
+            msg="You cannot specify a --forwarder option together with "
+            "--no-forwarders")
+    elif installer.auto_forwarders and installer.no_forwarders:
+        ansible_module.fail_json(
+            msg="You cannot specify a --auto-forwarders option together with "
+            "--no-forwarders")
+    elif installer.reverse_zones and installer.no_reverse:
+        ansible_module.fail_json(
+            msg="You cannot specify a --reverse-zone option together with "
+            "--no-reverse")
+    elif installer.auto_reverse and installer.no_reverse:
+        ansible_module.fail_json(
+            msg="You cannot specify a --auto-reverse option together with "
+            "--no-reverse")
+
+    # replica installers
+    if installer.servers and not installer.domain_name:
+        ansible_module.fail_json(
+            msg="The --server option cannot be used without providing "
+            "domain via the --domain option")
+
+    if installer.setup_dns:
+        if (not installer.forwarders and
+                not installer.no_forwarders and
+                not installer.auto_forwarders):
+            ansible_module.fail_json(
+                msg="You must specify at least one of --forwarder, "
+                "--auto-forwarders, or --no-forwarders options")
+
+    if installer.dirsrv_config_file is not None and not os.path.exists(installer.dirsrv_config_file):
+        ansible_module.fail_json(msg="File %s does not exist." % installer.dirsrv_config_file)
+
+    if installer.ca_cert_files is not None:
+        if not isinstance(installer.ca_cert_files, list):
+            ansible_module.fail_json(msg="Expected list, got {!r}".format(installer.ca_cert_files))
+        for cert in installer.ca_cert_files:
+            if not os.path.exists(cert):
+                ansible_module.fail_json(msg="'%s' does not exist" % cert)
+            if not os.path.isfile(cert):
+                ansible_module.fail_json(msg="'%s' is not a file" % cert)
+            if not os.path.isabs(cert):
+                ansible_module.fail_json(msg="'%s' is not an absolute file path" % cert)
+
+            try:
+                x509.load_certificate_from_file(cert)
+            except Exception:
+                ansible_module.fail_json(msg="'%s' is not a valid certificate file" % cert)
+
+    if installer.ip_addresses is not None:
+        for value in installer.ip_addresses:
+            try:
+                CheckedIPAddress(value)
+            except Exception as e:
+                ansible_module.fail_json(msg="invalid IP address {0}: {1}".format(
+                    value, e))
+
+    if installer.domain_name is not None:
+        validate_domain_name(installer.domain_name)
+
+    ##########################################################################
+    # replica promote_check excerpts #########################################
+    ##########################################################################
+
+    # check selinux status, http and DS ports, NTP conflicting services
+    try:
+        with redirect_stdout(ansible_log):
+            common_check(options.no_ntp)
+    except Exception as msg: #ScriptError as msg:
+        ansible_module.fail_json(msg=str(msg))
+
+    # client enrolled?
+
+    client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+    client_enrolled = client_fstore.has_files()
+
+    # done #
+
+    #ip_addresses = [ ]
+    ansible_module.exit_json(changed=True,
+                             ipa_python_version=IPA_PYTHON_VERSION,
+                             ### basic ###
+                             domain=options.domain_name,
+                             realm=options.realm_name,
+                             hostname=options.host_name,
+                             ### server ###
+                             setup_adtrust=options.setup_adtrust,
+                             setup_kra=options.setup_kra,
+                             server=options.server,
+                             ### additional ###
+                             client_enrolled=client_enrolled,
+    )
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipaserver_enable_ipa.py b/roles/ipareplica/library/ipaserver_enable_ipa.py
new file mode 100644
index 00000000..77e4aade
--- /dev/null
+++ b/roles/ipareplica/library/ipaserver_enable_ipa.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-server-install code
+#
+# Copyright (C) 2017  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: enable_ipa
+short description:
+description:
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_server import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(
+            hostname=dict(required=False),
+            setup_ca=dict(required=True, type='bool'),
+        ),
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # set values #############################################################
+
+    options.host_name = ansible_module.params.get('hostname')
+    options.setup_ca = ansible_module.params.get('setup_ca')
+
+    # Configuration for ipalib, we will bootstrap and finalize later, after
+    # we are sure we have the configuration file ready.
+    cfg = dict(
+        context='installer',
+        confdir=paths.ETC_IPA,
+        in_server=True,
+        # make sure host name specified by user is used instead of default
+        host=options.host_name,
+    )
+    if options.setup_ca:
+        # we have an IPA-integrated CA
+        cfg['ca_host'] = options.host_name
+
+    api.bootstrap(**cfg)
+    api.finalize()
+    api.Backend.ldap2.connect()
+
+    # setup ds ######################################################
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    if NUM_VERSION < 40600:
+        # Make sure the files we crated in /var/run are recreated at startup
+        tasks.configure_tmpfiles()
+
+    with redirect_stdout(ansible_log):
+        services.knownservices.ipa.enable()
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipaserver_master_password.py b/roles/ipareplica/library/ipaserver_master_password.py
new file mode 100644
index 00000000..d3dc7686
--- /dev/null
+++ b/roles/ipareplica/library/ipaserver_master_password.py
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-server-install code
+#
+# Copyright (C) 2017  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: master_password
+short description: Generate kerberos master password if not given
+description:
+  Generate kerberos master password if not given
+options:
+  master_password:
+    description: kerberos master password (normally autogenerated)
+    required: false
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+password:
+  description: The master password
+  returned: always
+'''
+
+import os
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_server import *
+
+def main():
+    module = AnsibleModule(
+        argument_spec = dict(
+            #basic
+            dm_password=dict(required=True, no_log=True),
+            master_password=dict(required=False, no_log=True),
+        ),
+        supports_check_mode = True,
+    )
+
+    module._ansible_debug = True
+
+    options.dm_password = module.params.get('dm_password')
+    options.master_password = module.params.get('master_password')
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    ## This will override any settings passed in on the cmdline
+    #if os.path.isfile(paths.ROOT_IPA_CACHE):
+    #    # dm_password check removed, checked already
+    #    try:
+    #        cache_vars = read_cache(options.dm_password)
+    #        options.__dict__.update(cache_vars)
+    #    except Exception as e:
+    #        module.fail_json(msg="Cannot process the cache file: %s" % str(e))
+
+    if not options.master_password:
+        options.master_password = ipa_generate_password()
+
+    module.exit_json(changed=True,
+                     password=options.master_password)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/library/ipaserver_setup_ntp.py b/roles/ipareplica/library/ipaserver_setup_ntp.py
new file mode 100644
index 00000000..0fd0f776
--- /dev/null
+++ b/roles/ipareplica/library/ipaserver_setup_ntp.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-server-install code
+#
+# Copyright (C) 2017  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+ANSIBLE_METADATA = {
+    'metadata_version': '1.0',
+    'supported_by': 'community',
+    'status': ['preview'],
+}
+
+DOCUMENTATION = '''
+---
+module: setup_ntp
+short description:
+description:
+options:
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_ipa_server import *
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec = dict(),
+    )
+
+    ansible_module._ansible_debug = True
+    ansible_log = AnsibleModuleLog(ansible_module)
+
+    # init ##########################################################
+
+    fstore = sysrestore.FileStore(paths.SYSRESTORE)
+    sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+    # setup NTP #####################################################
+
+    ntpconf.force_ntpd(sstore)
+    ntp = ntpinstance.NTPInstance(fstore)
+    ntp.set_output(ansible_log)
+    with redirect_stdout(ansible_log):
+        if not ntp.is_configured():
+            ntp.create_instance()
+
+    # done ##########################################################
+
+    ansible_module.exit_json(changed=True)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipareplica/meta/main.yml b/roles/ipareplica/meta/main.yml
new file mode 100644
index 00000000..7b5740af
--- /dev/null
+++ b/roles/ipareplica/meta/main.yml
@@ -0,0 +1,27 @@
+galaxy_info:
+  author: Thomas Woerner
+  description: A role to setup an IPA domain replica
+  company: Red Hat, Inc
+
+  # issue_tracker_url: http://example.com/issue/tracker
+
+  license: GPLv3
+
+  min_ansible_version: 2.0
+
+  #github_branch:
+
+  platforms:
+  - name: Fedora
+    versions:
+    - 25
+    - 26
+    - 27
+  - name: rhel
+    versions:
+    - 7.3
+    - 7.4
+
+  galaxy_tags: [ 'identity', 'ipa']
+
+dependencies: []
diff --git a/roles/ipareplica/tasks/install.yml b/roles/ipareplica/tasks/install.yml
new file mode 100644
index 00000000..260c29f4
--- /dev/null
+++ b/roles/ipareplica/tasks/install.yml
@@ -0,0 +1,619 @@
+---
+# tasks file for ipareplica
+
+- name: Install - Ensure IPA replica packages are installed
+  package:
+    name: "{{ item }}"
+    state: present
+  with_items: "{{ ipareplica_packages }}"
+  when: not ipareplica_no_package_install | bool
+
+- name: Install - Ensure IPA replica packages for dns are installed
+  package:
+    name: "{{ item }}"
+    state: present
+  with_items: "{{ ipareplica_packages_dns }}"
+  when: not ipareplica_no_package_install | bool and ipareplica_setup_dns | bool
+
+- name: Install - Ensure IPA replica packages for adtrust are installed
+  package:
+    name: "{{ item }}"
+    state: present
+  with_items: "{{ ipareplica_packages_adtrust }}"
+  when: not ipareplica_no_package_install | bool and ipareplica_setup_adtrust | bool
+
+- name: Install - Include Python2/3 import test
+  include: "{{role_path}}/tasks/python_2_3_test.yml"
+  static: yes
+
+- name: Install - Set default principal if no keytab is given
+  set_fact:
+    ipaadmin_principal: admin
+  when: ipaadmin_principal is undefined and ipaclient_keytab is undefined
+
+- name: Install - Replica installation test
+  ipareplica_test:
+    ### basic ###
+    ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+    domain: "{{ ipareplica_domain | default(ipaserver_domain) | default(omit) }}"
+    servers: "{{ groups.ipaservers | default(groups.ipaserver) | default(omit) }}"
+    realm: "{{ ipareplica_realm | default(omit) }}"
+    hostname: "{{ ipareplica_hostname | default(ansible_fqdn) }}"
+    ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+    ### server ###
+    setup_adtrust: "{{ ipareplica_setup_adtrust }}"
+    setup_kra: "{{ ipareplica_setup_kra }}"
+    setup_dns: "{{ ipareplica_setup_dns }}"
+    no_pkinit: "{{ ipareplica_no_pkinit }}"
+    dirsrv_config_file: "{{ ipareplica_dirsrv_config_file | default(omit) }}"
+    ### ssl certificate ###
+    dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+    http_cert_files: "{{ ipareplica_http_cert_files | default([]) }}"
+    pkinit_cert_files: "{{ ipareplica_pkinit_cert_files | default([]) }}"
+    ### client ###
+    no_ntp: "{{ ipaclient_no_ntp }}"
+    ### dns ###
+    no_reverse: "{{ ipareplica_no_reverse }}"
+    auto_reverse: "{{ ipareplica_auto_reverse }}"
+    forwarders: "{{ ipareplica_forwarders | default([]) }}"
+    no_forwarders: "{{ ipareplica_no_forwarders }}"
+    auto_forwarders: "{{ ipareplica_auto_forwarders }}"
+    forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
+    no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
+  register: result_ipareplica_test
+
+- block:
+
+  #- name: Install - Setup client
+  #  include_role:
+  #    name: ipaclient
+  #  vars:
+  #    state: present
+  #    ipaclient_domain: "{{ result_ipareplica_test.domain }}"
+  #    ipaclient_realm: "{{ result_ipareplica_test.realm }}"
+  #    ipaclient_server: "{{ result_ipareplica_test.server }}"
+  #    ipaclient_hostname: "{{ result_ipareplica_test.hostname }}"
+  #    #ipaclient_no_dns_sshfp: "{{ ipaclient_no_dns_sshfp }}"
+  #    #ipaclient_ssh_trust_dns: "{{ ipaclient_ssh_trust_dns }}"
+  #    #ipaclient_no_ssh: "{{ ipaclient_no_ssh }}"
+  #    #ipaclient_no_sshd: "{{ ipaclient_no_sshd }}"
+  #    #ipaclient_mkhomedir: "{{ ipaclient_mkhomedir }}"
+
+  #- name: Install - Setup client
+  #  command: >
+  #    /usr/sbin/ipa-client-install
+  #    --unattended
+  #    --no-ntp
+  #    --domain "{{ result_ipareplica_test.domain }}"
+  #    --realm "{{ result_ipareplica_test.realm }}"
+  #    --server "{{ result_ipareplica_test.server }}"
+  #    --hostname "{{ result_ipareplica_test.hostname }}"
+  #    {{ "--principal" if ipaadmin_password is defined else "" }} {{ ipaadmin_principal if ipaadmin_password is defined else "" }}
+  #    {{ "--password" if ipaadmin_password is defined else "" }} {{ ipaadmin_password if ipaadmin_password is defined else "" }}
+  #    {{ "--mkhomedir" if ipaclient_mkhomedir | bool else "" }}
+  #  #  {{ "--no-dns-sshfp" if ipaclient_no_dns_sshfp | bool else "" }}
+  #  #  {{ "--ssh-trust-dns" if ipaclient_ssh_trust_dns | bool else "" }}
+  #  #  {{ "--no-ssh" if ipaclient_no_ssh | bool else "" }}
+  #  #  {{ "--no-sshd" if ipaclient_no_sshd | bool else "" }}
+  #  when: not result_ipareplica_test.client_enrolled
+
+  - name: Install - Configure firewalld
+    command: >
+      firewall-cmd
+      --permanent
+      --add-service=freeipa-ldap
+      --add-service=freeipa-ldaps
+      --add-service=freeipa-replication
+      {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }}
+      {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
+    when: not ipareplica_no_firewalld | bool
+
+  - name: Install - Configure firewalld runtime
+    command: >
+      firewall-cmd
+      --add-service=freeipa-ldap
+      --add-service=freeipa-ldaps
+      --add-service=freeipa-replication
+      {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }}
+      {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
+    when: not ipareplica_no_firewalld | bool
+
+  - name: Install - Replica preparation
+    ipareplica_prepare:
+      ### basic ###
+      password: "{{ ipaadmin_password | default(omit) }}"
+      ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+      domain: "{{ result_ipareplica_test.domain }}"
+      realm: "{{ result_ipareplica_test.realm }}"
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      principal: "{{ ipaadmin_principal | default(omit) }}"
+      ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+      no_host_dns: "{{ ipareplica_no_host_dns }}"
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      ### ssl certificate ###
+      dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+      dirsrv_pin: "{{ ipareplica_dirsrv_pin | default(omit) }}"
+      http_cert_files: "{{ ipareplica_http_cert_files | default([]) }}"
+      http_pin: "{{ ipareplica_http_pin | default(omit) }}"
+      pkinit_cert_files: "{{ ipareplica_pkinit_cert_files | default([]) }}"
+      pkinit_pin: "{{ ipareplica_pkinit_pin | default(omit) }}"
+      ### client ###
+      keytab: "{{ ipaclient_keytab | default(omit) }}"
+      mkhomedir: "{{ ipaclient_mkhomedir | default(omit) }}"
+      force_join: "{{ ipaclient_force_join | default(omit) }}"
+      no_ntp: "{{ ipaclient_no_ntp | default(omit) }}"
+      ssh_trust_dns: "{{ ipaclient_ssh_trust_dns | default(omit) }}"
+      no_ssh: no
+      no_sshd: no
+      no_dns_sshfp: no
+      ### dns ###
+      no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
+      forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
+      ### ad trust ###
+      netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
+      rid_base: "{{ ipareplica_rid_base | default(omit) }}"
+      secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
+      ### additional ###
+      server: "{{ result_ipareplica_test.server }}"
+      skip_conncheck: "{{ ipareplica_skip_conncheck }}"
+    register: result_ipareplica_prepare
+
+  - name: Install - Setup NTP
+    ipaserver_setup_ntp:
+    when: not ipaclient_no_ntp | bool
+
+  - name: Install - Add to ipaservers
+    ipareplica_add_to_ipaservers:
+      ### server ###
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+    when: result_ipareplica_prepare._add_to_ipaservers
+
+  - name: Install - Create dirman password
+    no_log: yes
+    ipaserver_master_password:
+      dm_password: "{{ ipadm_password }}"
+      master_password: "{{ ipaserver_master_password | default(omit) }}"
+    register: result_ipaserver_master_password
+
+  - name: Install - Set dirman password
+    no_log: yes
+    set_fact:
+      ipareplica_dirman_password: "{{ result_ipaserver_master_password.password }}"
+
+  - name: Install - Setup certmonger
+    ipareplica_setup_certmonger:
+    when: result_ipareplica_prepare._ca_enabled
+
+  - name: Install - Install CA certs
+    ipareplica_install_ca_certs:
+      ### basic ###
+      dm_password: "{{ ipadm_password | default(omit) }}"
+      password: "{{ ipaadmin_password | default(omit) }}"
+      ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+      domain: "{{ result_ipareplica_test.domain }}"
+      realm: "{{ result_ipareplica_test.realm }}"
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+      no_host_dns: "{{ ipareplica_no_host_dns }}"
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      ### ssl certificate ###
+      dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+      ### client ###
+      force_join: "{{ ipaclient_force_join }}"
+      ### dns ###
+      forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
+      ### ad trust ###
+      netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
+      rid_base: "{{ ipareplica_rid_base | default(omit) }}"
+      secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
+      ### additional ###
+      server: "{{ result_ipareplica_test.server }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
+      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      _add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
+      _ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
+      _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+      config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
+      config_ips: "{{ result_ipareplica_prepare.config_ips }}"
+    register: result_ipareplica_install_ca_certs
+
+  - name: Install - Setup DS
+    ipareplica_setup_ds:
+      ### basic ###
+      dm_password: "{{ ipadm_password | default(omit) }}"
+      password: "{{ ipaadmin_password | default(omit) }}"
+      ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+      domain: "{{ result_ipareplica_test.domain }}"
+      realm: "{{ result_ipareplica_test.realm }}"
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+      no_host_dns: "{{ ipareplica_no_host_dns }}"
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      dirsrv_config_file: "{{ ipareplica_dirsrv_config_file | default(omit) }}"
+      ### ssl certificate ###
+      dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+      ### client ###
+      force_join: "{{ ipaclient_force_join }}"
+      ### dns ###
+      forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
+      ### ad trust ###
+      netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
+      rid_base: "{{ ipareplica_rid_base | default(omit) }}"
+      secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
+      ### additional ###
+      server: "{{ result_ipareplica_test.server }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
+      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      _add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
+      _ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
+      _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
+      config_ips: "{{ result_ipareplica_prepare.config_ips }}"
+    register: result_ipareplica_setup_ds
+
+  - name: Install - Create IPA conf
+    ipareplica_create_ipa_conf:
+      ### basic ###
+      dm_password: "{{ ipadm_password | default(omit) }}"
+      password: "{{ ipaadmin_password | default(omit) }}"
+      ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+      domain: "{{ result_ipareplica_test.domain }}"
+      realm: "{{ result_ipareplica_test.realm }}"
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+      no_host_dns: "{{ ipareplica_no_host_dns }}"
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      ### ssl certificate ###
+      dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+      ### client ###
+      force_join: "{{ ipaclient_force_join }}"
+      ### dns ###
+      forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
+      ### ad trust ###
+      netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
+      rid_base: "{{ ipareplica_rid_base | default(omit) }}"
+      secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
+      ### additional ###
+      server: "{{ result_ipareplica_test.server }}"
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
+      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      _add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
+      _ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
+      _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Setup KRB
+    ipareplica_setup_krb:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+
+  - name: Install - DS enable SSL
+    ipareplica_ds_enable_ssl:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      dirsrv_config_file: "{{ ipareplica_dirsrv_config_file | default(omit) }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+      ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
+
+  - name: Install - Setup http
+    ipareplica_setup_http:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Setup otpd
+    ipareplica_setup_otpd:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Setup custodia
+    ipareplica_setup_custodia:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Setup CA
+    ipareplica_setup_ca:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
+      _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      config_ca_host_name: "{{ result_ipareplica_install_ca_certs.config_ca_host_name }}"
+      config_ips: "{{ result_ipareplica_prepare.config_ips }}"
+    when: result_ipareplica_prepare._ca_enabled
+
+  - name: Install - KRB enable SSL
+    ipareplica_krb_enable_ssl:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      #no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - DS apply updates
+    ipareplica_ds_apply_updates:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      dirsrv_config_file: "{{ ipareplica_dirsrv_config_file | default(omit) }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+      ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
+
+  - name: Install - Setup kra
+    ipareplica_setup_kra:
+      ### basic ###
+      dm_password: "{{ ipadm_password | default(omit) }}"
+      password: "{{ ipaadmin_password | default(omit) }}"
+      ip_addresses: "{{ ipareplica_ip_addresses | default([]) }}"
+      domain: "{{ result_ipareplica_test.domain }}"
+      realm: "{{ result_ipareplica_test.realm }}"
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
+      no_host_dns: "{{ ipareplica_no_host_dns }}"
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      ### ssl certificate ###
+      dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
+      ### client ###
+      force_join: "{{ ipaclient_force_join }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      server: "{{ result_ipareplica_test.server }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
+      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      _add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
+      _ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
+      _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+    when: result_ipareplica_test.setup_kra
+
+  - name: Install - Restart KDC
+    ipareplica_restart_kdc:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Custodia import dm password
+    ipareplica_custodia_import_dm_password:
+      ### server ###
+      setup_ca: "{{ ipareplica_setup_ca }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      no_pkinit: "{{ ipareplica_no_pkinit }}"
+      no_ui_redirect: "{{ ipareplica_no_ui_redirect }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+      _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      dirman_password: "{{ ipareplica_dirman_password }}"
+
+  - name: Install - Promote SSSD
+    ipareplica_promote_sssd:
+      ### replica ###
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+
+  - name: Install - Promote openldap.conf
+    ipareplica_promote_openldap_conf:
+      ### replica ###
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+
+  - name: Install - Setup DNS
+    ipareplica_setup_dns:
+      ### server ###
+      setup_dns: "{{ ipareplica_setup_dns }}"
+      ### replica ###
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+
+  - name: Install - Setup adtrust
+    ipareplica_setup_adtrust:
+      ### replica ###
+      setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
+      setup_kra: "{{ result_ipareplica_test.setup_kra }}"
+      ### certificate system ###
+      subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+      ### additional ###
+      ccache: "{{ result_ipareplica_prepare.ccache }}"
+      _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+      config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+      config_master_host_name: "{{ result_ipareplica_prepare.config_master_host_name }}"
+    when: result_ipareplica_test.setup_adtrust
+
+  #- name: Install - Disconnect backend
+  #  ipareplica_backend_disconnect:
+
+  - name: Install - Enable IPA
+    ipaserver_enable_ipa:
+      hostname: "{{ result_ipareplica_test.hostname }}"
+      setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+    register: result_ipareplica_enable_ipa
+
+  - name: Install - Cleanup root IPA cache
+    file:
+      path: "/root/.ipa_cache"
+      state: absent
+    when: result_ipareplica_enable_ipa.changed
diff --git a/roles/ipareplica/tasks/main.yml b/roles/ipareplica/tasks/main.yml
new file mode 100644
index 00000000..59f337b4
--- /dev/null
+++ b/roles/ipareplica/tasks/main.yml
@@ -0,0 +1,18 @@
+---
+# tasks file for ipareplica
+
+- name: Import variables specific to distribution
+  include_vars: "{{ item }}"
+  with_first_found:
+    - "vars/{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml"
+    - "vars/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
+    - "vars/{{ ansible_distribution }}.yml"
+    - "vars/default.yml"
+
+- name: Install IPA replica
+  include: tasks/install.yml
+  when: state|default('present') == 'present'
+
+- name: Uninstall IPA replica
+  include: tasks/uninstall.yml
+  when: state|default('present') == 'absent'
diff --git a/roles/ipareplica/tasks/python_2_3_test.yml b/roles/ipareplica/tasks/python_2_3_test.yml
new file mode 100644
index 00000000..64353ee6
--- /dev/null
+++ b/roles/ipareplica/tasks/python_2_3_test.yml
@@ -0,0 +1,19 @@
+- block:
+  - name: Verify Python3 import
+    script: py3test.py
+    register: py3test
+    failed_when: False
+
+  - name: Set python interpreter to 3
+    set_fact:
+      ansible_python_interpreter: "/usr/bin/python3"
+    when: py3test.rc == 0
+
+  - name: Fail for IPA 4.5.90
+    fail: msg="You need to install python2 bindings for ipa server usage"
+    when: py3test.rc != 0 and "not usable with python3" in py3test.stdout
+
+  - name: Set python interpreter to 2
+    set_fact:
+      ansible_python_interpreter: "/usr/bin/python2"
+    when: py3test.failed or py3test.rc != 0
diff --git a/roles/ipareplica/tasks/uninstall.yml b/roles/ipareplica/tasks/uninstall.yml
new file mode 100644
index 00000000..98269f07
--- /dev/null
+++ b/roles/ipareplica/tasks/uninstall.yml
@@ -0,0 +1,38 @@
+---
+# tasks to uninstall IPA replica
+
+#- name: Uninstall - Include Python2/3 import test
+#  include: "{{role_path}}/tasks/python_2_3_test.yml"
+#  static: yes
+
+- name: Uninstall - Uninstall IPA replica
+  command: >
+    /usr/sbin/ipa-server-install
+    --uninstall
+    -U
+    {{ "--ignore-topology-disconnect" if ipareplica_ignore_topology_disconnect | bool else "" }}
+    {{ "--ignore-last-of-role" if ipareplica_ignore_last_of_role | bool else "" }}
+  register: result_uninstall
+  # 2 means that uninstall failed because IPA replica was not configured
+  failed_when: result_uninstall.rc != 0 and "'Env' object has no attribute 'basedn'" not in result_uninstall.stderr
+  #IPA server is not configured on this system" not in result_uninstall.stdout_lines
+  #changed_when: result_uninstall.rc == 0
+  #until: result_uninstall.rc == 0
+  retries: 2
+  delay: 1
+
+- name: Uninstall - Remove all replication agreements and data about replica
+  command: >
+    /usr/sbin/ipa-replica-manage
+    del
+    {{ ipareplica_hostname | default(ansible_fqdn) }}
+    --force
+    --password={{ ipadm_password }}
+  failed_when: False
+  delegate_to: "{{ groups.ipaserver[0] | default(fail) }}"
+
+#- name: Remove IPA replica packages
+#  package:
+#    name: "{{ item }}"
+#    state: absent
+#  with_items: "{{ ipareplica_packages }}"
diff --git a/roles/ipareplica/vars/Fedora-25.yml b/roles/ipareplica/vars/Fedora-25.yml
new file mode 100644
index 00000000..9c0827f3
--- /dev/null
+++ b/roles/ipareplica/vars/Fedora-25.yml
@@ -0,0 +1,5 @@
+# Fedora-25 defaults file for ipareplica
+# vars/Fedora-25.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ ]
\ No newline at end of file
diff --git a/roles/ipareplica/vars/Fedora-26.yml b/roles/ipareplica/vars/Fedora-26.yml
new file mode 100644
index 00000000..bd24033d
--- /dev/null
+++ b/roles/ipareplica/vars/Fedora-26.yml
@@ -0,0 +1,5 @@
+# Fedora defaults file for ipareplica
+# vars/Fedora-26.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ ]
\ No newline at end of file
diff --git a/roles/ipareplica/vars/Fedora.yml b/roles/ipareplica/vars/Fedora.yml
new file mode 100644
index 00000000..ced045cb
--- /dev/null
+++ b/roles/ipareplica/vars/Fedora.yml
@@ -0,0 +1,5 @@
+# Fedora defaults file for ipareplica
+# vars/Fedora.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ "samba" ]
\ No newline at end of file
diff --git a/roles/ipareplica/vars/RedHat-7.3.yml b/roles/ipareplica/vars/RedHat-7.3.yml
new file mode 100644
index 00000000..04481290
--- /dev/null
+++ b/roles/ipareplica/vars/RedHat-7.3.yml
@@ -0,0 +1,5 @@
+# defaults file for ipareplica
+# vars/RedHat-7.3.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ ]
\ No newline at end of file
diff --git a/roles/ipareplica/vars/RedHat-7.yml b/roles/ipareplica/vars/RedHat-7.yml
new file mode 100644
index 00000000..27d796d7
--- /dev/null
+++ b/roles/ipareplica/vars/RedHat-7.yml
@@ -0,0 +1,5 @@
+# defaults file for ipareplica
+# vars/RedHat-7.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ ]
\ No newline at end of file
diff --git a/roles/ipareplica/vars/default.yml b/roles/ipareplica/vars/default.yml
new file mode 100644
index 00000000..4b2275ab
--- /dev/null
+++ b/roles/ipareplica/vars/default.yml
@@ -0,0 +1,5 @@
+# defaults file for ipareplica
+# vars/default.yml
+ipareplica_packages: [ "ipa-server", "libselinux-python" ]
+ipareplica_packages_dns: [ "ipa-server-dns" ]
+ipareplica_packages_adtrust: [ "samba" ]
diff --git a/uninstall-replica.yml b/uninstall-replica.yml
new file mode 100644
index 00000000..53f06542
--- /dev/null
+++ b/uninstall-replica.yml
@@ -0,0 +1,8 @@
+---
+- name: Playbook to unconfigure IPA replicas
+  hosts: ipareplicas
+  become: true
+
+  roles:
+  - role: ipareplica
+    state: absent
-- 
GitLab