diff --git a/utils/ansible-ipa-client-install b/utils/ansible-ipa-client-install
new file mode 100755
index 0000000000000000000000000000000000000000..f074e1662b19f74b2c30e08fc3fc17c5989d0e78
--- /dev/null
+++ b/utils/ansible-ipa-client-install
@@ -0,0 +1,377 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Copyright (C) 2019  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 shutil
+import tempfile
+import argparse
+import traceback
+import subprocess
+
+
+def parse_options():
+    usage = "Usage: ansible-ipa-client-install [options] <ansible host>"
+
+    parser = argparse.ArgumentParser(usage=usage)
+    parser.add_argument("--version", dest="version",
+                        action="store_true",
+                        help="show program's version number and exit")
+    parser.add_argument("-U", "--unattended", dest="unattended",
+                        action="store_true",
+                        help="unattended (un)installation never prompts the "
+                        "user")
+    parser.add_argument("--uninstall", dest="uninstall",
+                        action="store_true",
+                        help="uninstall an existing installation. The "
+                        "uninstall can be run with --unattended option")
+    # basic
+    parser.add_argument("-p", "--principal", dest="principal",
+                        default=None,
+                        help="principal to use to join the IPA realm")
+    parser.add_argument("--ca-cert-file", dest="ca_cert_file",
+                        default=None,
+                        help="load the CA certificate from this file")
+    parser.add_argument("--ip-address", dest="ip_addresses",
+                        metavar="IP_ADDRESS",
+                        action='append', default=None,
+                        help="Specify IP address that should be added to DNS. "
+                        "This option can be used multiple times")
+    parser.add_argument("--all-ip-addresses", dest="all_ip_addresses",
+                        action='store_true',
+                        help="All routable IP addresses configured on any "
+                        "interface will be added to DNS")
+    parser.add_argument("--domain", dest="domain",
+                        default=None,
+                        help="primary DNS domain of the IPA deployment (not "
+                        "necessarily related to the current hostname)")
+    parser.add_argument("--server", dest="servers",
+                        metavar="SERVER",
+                        action='append', default=None,
+                        help="FQDN of IPA server")
+    parser.add_argument("--realm", dest="realm",
+                        default=None,
+                        help="Kerberos realm name of the IPA deployment "
+                        "(typically an upper-cased name of the primary DNS "
+                        "domain)")
+    parser.add_argument("--hostname", dest="hostname",
+                        default=None,
+                        help="The hostname of this machine (FQDN). If "
+                        "specified, the hostname will be set and the system "
+                        "configuration will be updated to persist over "
+                        "reboot. By default the result of getfqdn() call "
+                        "from Python's socket module is used.")
+    # client
+    parser.add_argument("-w", "--password", dest="password",
+                        default=None,
+                        help="password to join the IPA realm (assumes bulk "
+                        "password unless principal is also set)")
+    parser.add_argument("-W", dest="password_prompt",
+                        action="store_true",
+                        help="Prompt for a password to join the IPA realm")
+    parser.add_argument("-f", "--force", dest="force",
+                        action="store_true",
+                        help="force setting of LDAP/Kerberos conf")
+    parser.add_argument("--configure-firefox", dest="configure_firefox",
+                        action="store_true",
+                        help="configure Firefox to use IPA domain credentials")
+    parser.add_argument("--firefox-dir", dest="firefox_dir",
+                        default=None,
+                        help="specify directory where Firefox is installed "
+                        "(for example: '/usr/lib/firefox')")
+    parser.add_argument("-k", "--keytab", dest="keytab",
+                        default=None,
+                        help="path to backed up keytab from previous "
+                        "enrollment")
+    parser.add_argument("--mkhomedir", dest="mkhomedir",
+                        action="store_true",
+                        help="create home directories for users on their "
+                        "first login")
+    parser.add_argument("--force-join", dest="force_join",
+                        action="store_true",
+                        help="Force client enrollment even if already "
+                        "enrolled")
+    parser.add_argument("--ntp-server", dest="ntp_servers",
+                        metavar="NTP_SERVER",
+                        action='append', default=None,
+                        help="ntp server to use. This option can be used "
+                        "multiple times")
+    parser.add_argument("--ntp-pool", dest="ntp_pool",
+                        default=None,
+                        help="ntp server pool to use")
+    parser.add_argument("-N", "--no-ntp", dest="no_ntp",
+                        action="store_true",
+                        help="do not configure ntp")
+    parser.add_argument("--nisdomain", dest="nisdomain",
+                        default=None,
+                        help="NIS domain name")
+    parser.add_argument("--no-nisdomain", dest="no_nisdomain",
+                        action="store_true",
+                        help="do not configure NIS domain name")
+    parser.add_argument("--ssh-trust-dns", dest="ssh_trust_dns",
+                        action="store_true",
+                        help="configure OpenSSH client to trust DNS SSHFP "
+                        "records")
+    parser.add_argument("--no-ssh", dest="no_ssh",
+                        action="store_true",
+                        help="do not configure OpenSSH client")
+    parser.add_argument("--no-sshd", dest="no_sshd",
+                        action="store_true",
+                        help="do not configure OpenSSH server")
+    parser.add_argument("--no-sudo", dest="no_sudo",
+                        action="store_true",
+                        help="do not configure SSSD as data source for sudo")
+    parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp",
+                        action="store_true",
+                        help="do not automatically create DNS SSHFP records")
+    parser.add_argument("--kinit-attempts", dest="kinit_attempts",
+                        type=int, default=None,
+                        help="number of attempts to obtain host TGT (defaults "
+                        "to 5)")
+    # sssd
+    parser.add_argument("--fixed-primary", dest="fixed_primary",
+                        action="store_true",
+                        help="Configure sssd to use fixed server as primary "
+                        "IPA server")
+    parser.add_argument("--permit", dest="permit",
+                        action="store_true",
+                        help="disable access rules by default, permit all "
+                        "access")
+    parser.add_argument("--enable-dns-updates", dest="enable_dns_updates",
+                        action="store_true",
+                        help="Configures the machine to attempt dns updates "
+                        "when the ip address changes")
+    parser.add_argument("--no-krb5-offline-passwords",
+                        dest="no_krb5_offline_passwords",
+                        action="store_true",
+                        help="Configure SSSD not to store user password when "
+                        "the server is offline")
+    parser.add_argument("--preserve-sssd", dest="preserve_sssd",
+                        action="store_true",
+                        help="Preserve old SSSD configuration if possible")
+
+    # automount
+    parser.add_argument("--automount-location", dest="automount_location",
+                        default=None,
+                        help="Automount location")
+    # logging and output
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true",
+                        help="print debugging information")
+    parser.add_argument("-d", "--debug", dest="verbose",
+                        action="store_true",
+                        help="alias for --verbose (deprecated)")
+    parser.add_argument("-q", "--quiet", dest="quiet",
+                        action="store_true",
+                        help="output only errors")
+    parser.add_argument("--log-file", dest="log_file",
+                        help="log to the given file")
+    # ansible
+    parser.add_argument("--ipaclient-use-otp", dest="ipaclient_use_otp",
+                        choices=("yes", "no"), default=None,
+                        help="The bool value defines if a one-time password "
+                        "will be generated to join a new or existing host. "
+                        "Default: no")
+    parser.add_argument("--ipaclient-allow-repair",
+                        dest="ipaclient_allow_repair",
+                        choices=("yes", "no"), default=None,
+                        help="The bool value defines if an already joined or "
+                        "partly set-up client can be repaired. Default: no")
+    parser.add_argument("--ipaserver-install-packages",
+                        dest="ipaserver_install_packages",
+                        choices=("yes", "no"), default=None,
+                        help="The bool value defines if the needed packages "
+                        "are installed on the node. Default: yes")
+
+    options, args = parser.parse_known_args()
+
+    if options.password_prompt:
+        parser.error("password prompt is not possible with ansible")
+    if options.log_file:
+        parser.error("log_file is not supported")
+
+    if len(args) < 1:
+        parser.error("ansible host not set")
+    elif len(args) > 1:
+        parser.error("too many arguments")
+
+    return options, args
+
+
+def run_cmd(args):
+    """
+    Execute an external command.
+    """
+    p_out = subprocess.PIPE
+    p_err = subprocess.STDOUT
+    try:
+        p = subprocess.Popen(args, stdout=p_out, stderr=p_err,
+                             close_fds=True, bufsize=1,
+                             universal_newlines=True)
+        with p.stdout:
+            for line in p.stdout:
+                sys.stdout.write(line)
+    except KeyboardInterrupt:
+        p.wait()
+        raise
+
+    return p.returncode
+
+
+def main(options, args):
+    temp_dir = tempfile.mkdtemp(prefix='ansible-ipa')
+
+    inventory = os.path.join(temp_dir, "client-inventory")
+    playbook = os.path.join(temp_dir, "client-playbook.yml")
+
+    with open(inventory, 'w') as f:
+        if options.servers:
+            f.write("[ipaservers]\n")
+            for server in options.servers:
+                f.write("%s\n" % server)
+            f.write("\n")
+        f.write("[ipaclients]\n")
+        f.write("%s\n" % args[0])
+        f.write("\n")
+        f.write("[ipaclients:vars]\n")
+        # basic
+        if options.principal:
+            f.write("ipaadmin_principal=%s\n" % options.principal)
+        if options.ca_cert_file:
+            f.write("ipaclient_ca_cert_file=%s\n" % options.ca_cert_file)
+        if options.ip_addresses:
+            f.write("ipaclient_ip_addresses=%s\n" %
+                    ",".join(options.ip_addresses))
+        if options.all_ip_addresses:
+            f.write("ipaclient_all_ip_addresses=yes\n")
+        if options.domain:
+            f.write("ipaclient_domain=%s\n" % options.domain)
+        # --servers are handled above
+        if options.realm:
+            f.write("ipaclient_realm=%s\n" % options.realm)
+        if options.hostname:
+            f.write("ipaclient_hostname=%s\n" % options.hostname)
+        # client
+        if options.password:
+            f.write("ipaadmin_password=%s\n" % options.password)
+        if options.force:
+            f.write("ipaclient_force=yes\n")
+        if options.configure_firefox:
+            f.write("ipaclient_configure_firefox=yes\n")
+        if options.firefox_dir:
+            f.write("ipaclient_firefox_dir=%s\n" % options.firefox_dir)
+        if options.keytab:
+            f.write("ipaclient_keytab=%s\n" % options.keytab)
+        if options.mkhomedir:
+            f.write("ipaclient_mkhomedir=yes\n")
+        if options.force_join:
+            f.write("ipaclient_force_join=%s\n" % options.force_join)
+        if options.ntp_servers:
+            f.write("ipaclient_ntp_servers=%s\n" %
+                    ",".join(options.ntp_servers))
+        if options.ntp_pool:
+            f.write("ipaclient_ntp_pool=%s\n" % options.ntp_pool)
+        if options.no_ntp:
+            f.write("ipaclient_no_ntp=yes\n")
+        if options.nisdomain:
+            f.write("ipaclient_nisdomain=%s\n" % options.nisdomain)
+        if options.no_nisdomain:
+            f.write("ipaclient_no_nisdomain=yes\n")
+        if options.ssh_trust_dns:
+            f.write("ipaclient_ssh_trust_dns=yes\n")
+        if options.no_ssh:
+            f.write("ipaclient_no_ssh=yes\n")
+        if options.no_sshd:
+            f.write("ipaclient_no_sshd=yes\n")
+        if options.no_sudo:
+            f.write("ipaclient_no_sudo=yes\n")
+        if options.no_dns_sshfp:
+            f.write("ipaclient_no_dns_sshfp=yes\n")
+        if options.kinit_attempts:
+            f.write("ipaclient_kinit_attempts=%d\n" % options.kinit_attempts)
+        # sssd
+        if options.fixed_primary:
+            f.write("ipassd_fixed_primary=yes\n")
+        if options.permit:
+            f.write("ipassd_permit=yes\n")
+        if options.enable_dns_updates:
+            f.write("ipassd_enable_dns_updates=yes\n")
+        if options.no_krb5_offline_passwords:
+            f.write("ipassd_no_krb5_offline_passwords=yes\n")
+        if options.preserve_sssd:
+            f.write("ipassd_preserve_sssd=yes\n")
+        # automount
+        if options.automount_location:
+            f.write("ipaclient_automount_location=%s\n" %
+                    options.automount_location)
+        # ansible
+        if options.ipaclient_use_otp:
+            f.write("ipaclient_use_otp=%s\n" % options.ipaclient_use_otp)
+        if options.ipaclient_allow_repair:
+            f.write("ipaclient_allow_repair=%s\n" %
+                    options.ipaclient_allow_repair)
+        if options.ipaclient_install_packages:
+            f.write("ipaclient_install-packages=%s\n" %
+                    options.ipaclient_install_packages)
+
+    if options.uninstall:
+        state = "absent"
+    else:
+        state = "present"
+
+    with open(playbook, 'w') as f:
+        f.write("""---
+- name: Playbook to configure IPA clients
+  hosts: ipaclients
+  become: true
+
+  roles:
+  - role: ipaclient
+    state: %s
+""" % state)
+
+    try:
+        returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook])
+        if returncode != 0:
+            raise RuntimeError()
+    finally:
+        shutil.rmtree(temp_dir, ignore_errors=True)
+
+
+options, args = parse_options()
+try:
+    main(options, args)
+except KeyboardInterrupt:
+    sys.exit(1)
+except SystemExit as e:
+    sys.exit(e)
+except RuntimeError as e:
+    sys.exit(e)
+except Exception as e:
+    if options.verbose:
+        traceback.print_exc(file=sys.stdout)
+    else:
+        print("Re-run %s with --verbose option to get more information" %
+              sys.argv[0])
+
+    print("Unexpected error: %s" % str(e))
+    sys.exit(1)
diff --git a/utils/ansible-ipa-replica-install b/utils/ansible-ipa-replica-install
new file mode 100755
index 0000000000000000000000000000000000000000..5c8eab4999a5fbab949b257e267ff6d077fc9449
--- /dev/null
+++ b/utils/ansible-ipa-replica-install
@@ -0,0 +1,493 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Copyright (C) 2019  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 shutil
+import tempfile
+import argparse
+import traceback
+import subprocess
+
+
+def parse_options():
+    usage = "Usage: anisble-ipa-replica-install [options] <ansible host>"
+
+    parser = argparse.ArgumentParser(usage=usage)
+    parser.add_argument("--version", dest="version",
+                        action="store_true",
+                        help="show program's version number and exit")
+    parser.add_argument("-U", "--unattended", dest="unattended",
+                        action="store_true",
+                        help="unattended (un)installation never prompts the "
+                        "user")
+    # basic
+    parser.add_argument("-w", "--admin-password", dest="admin_password",
+                        default=None,
+                        help="Kerberos password for the specified admin "
+                        "principal")
+    parser.add_argument("--ip-address", dest="ip_addresses",
+                        metavar="IP_ADDRESS",
+                        action='append', default=None,
+                        help="Replica server IP Address. This option can be "
+                        "used multiple times")
+    parser.add_argument("-n", "--domain", dest="domain",
+                        metavar="DOMAIN_NAME", default=None,
+                        help="primary DNS domain of the IPA deployment (not "
+                        "necessarily related to the current hostname)")
+    parser.add_argument("--server", dest="servers",
+                        metavar="SERVER",
+                        action='append', default=None,
+                        help="fully qualified name of IPA server to enroll to")
+    parser.add_argument("-r", "--realm", dest="realm",
+                        metavar="REALM_NAME", default=None,
+                        help="Kerberos realm name of the IPA deployment "
+                        "(typically un upper-cased name of the primary DNS "
+                        "domain)")
+    parser.add_argument("--hostname", dest="hostname",
+                        metavar="HOST_NAME", default=None,
+                        help="fully qualified name of this host")
+    parser.add_argument("-P", "--principal", dest="principal",
+                        default=None,
+                        help="User Principal allowed to promote replicas and "
+                        "join IPA realm")
+    parser.add_argument("--pki-config-override", dest="pki_config_override",
+                        default=None,
+                        help="Path to ini file with config overrides.")
+    parser.add_argument("--no-host-dns", dest="no_host_dns",
+                        action="store_true",
+                        help="Do not use DNS for hostname lookup during "
+                        "installation")
+    parser.add_argument("--skip-conncheck", dest="skip_conncheck",
+                        action="store_true",
+                        help="skip connection check to remote master")
+    # server
+    parser.add_argument("-p", "--password", dest="dm_password",
+                        default=None,
+                        help="Password to join the IPA realm. Assumes bulk "
+                        "password unless principal is also set. (domain "
+                        "level 1+) Directory Manager (existing master) "
+                        "password. (domain level 0)")
+    parser.add_argument("--hidden-replica", dest="hidden_replica",
+                        action="store_true",
+                        help="Install a hidden replica")
+    parser.add_argument("--setup-adtrust", dest="setup_adtrust",
+                        action="store_true",
+                        help="configure AD trust capability")
+    parser.add_argument("--setup-ca", dest="setup_ca",
+                        action="store_true",
+                        help="configure a dogtag CA")
+    parser.add_argument("--setup-kra", dest="setup_kra",
+                        action="store_true",
+                        help="configure a dogtag KRA")
+    parser.add_argument("--setup-dns", dest="setup_dns",
+                        action="store_true",
+                        help="configure bind with our zone")
+    parser.add_argument("--no-pkinit", dest="no_pkinit",
+                        action="store_true",
+                        help="disables pkinit setup steps")
+    parser.add_argument("--no-ui-redirect", dest="no_ui_redirect",
+                        action="store_true",
+                        help="Do not automatically redirect to the Web UI")
+    parser.add_argument("--dirsrv-config-file", dest="dirsrv_config_file",
+                        metavar="FILE", default=None,
+                        help="The path to LDIF file that will be used to "
+                        "modify configuration of dse.ldif during "
+                        "installation of the directory server instance")
+    # ssl certificate
+    parser.add_argument("--dirsrv-cert-file", dest="dirsrv_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Directory Server SSL "
+                        "certificate and private key")
+    parser.add_argument("--http-cert-file", dest="http_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Apache Server SSL "
+                        "certificate and private key")
+    parser.add_argument("--pkinit-cert-file", dest="pkinit_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Kerberos KDC SSL "
+                        "certificate and Private key")
+    parser.add_argument("--dirsrv-pin", dest="dirsrv_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Directory Server "
+                        "private key")
+    parser.add_argument("--http-pin", dest="http_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Apache Server "
+                        "private key")
+    parser.add_argument("--pkinit-pin", dest="pkinit_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Kerberos KDC "
+                        "private key")
+    parser.add_argument("--dirsrv-cert-name", dest="dirsrv_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Directory Server SSL certificate "
+                        "to install")
+    parser.add_argument("--http-cert-name", dest="http_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Apache Server SSL certificate to "
+                        "install")
+    parser.add_argument("--pkinit-cert-name", dest="pkinit_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Kerberos KDC SSL certificate to "
+                        "install")
+    # client
+    parser.add_argument("-k", "--keytab", dest="keytab",
+                        default=None,
+                        help="path to backed up keytab from previous "
+                        "enrollment")
+    parser.add_argument("--mkhomedir", dest="mkhomedir",
+                        action="store_true",
+                        help="create home directories for users on their "
+                        "first login")
+    parser.add_argument("--force-join", dest="force_join",
+                        action="store_true",
+                        help="Force client enrollment even if already "
+                        "enrolled")
+    parser.add_argument("--ntp-server", dest="ntp_servers",
+                        metavar="NTP_SERVER",
+                        action='append', default=None,
+                        help="ntp server to use. This option can be used "
+                        "multiple times")
+    parser.add_argument("--ntp-pool", dest="ntp_pool",
+                        default=None,
+                        help="ntp server pool to use")
+    parser.add_argument("-N", "--no-ntp", dest="no_ntp",
+                        action="store_true",
+                        help="do not configure ntp")
+    parser.add_argument("--ssh-trust-dns", dest="ssh_trust_dns",
+                        action="store_true",
+                        help="configure OpenSSH client to trust DNS SSHFP "
+                        "records")
+    parser.add_argument("--no-ssh", dest="no_ssh",
+                        action="store_true",
+                        help="do not configure OpenSSH client")
+    parser.add_argument("--no-sshd", dest="no_sshd",
+                        action="store_true",
+                        help="do not configure OpenSSH server")
+    parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp",
+                        action="store_true",
+                        help="do not automatically create DNS SSHFP records")
+    # certificate system
+    parser.add_argument("--skip-schema-check", dest="skip_schema_check",
+                        action="store_true",
+                        help="skip check for updated CA DS schema on the "
+                        "remote master")
+    # dns
+    parser.add_argument("--allow-zone-overlap", dest="allow_zone_overlap",
+                        action="store_true",
+                        help="Create DNS zone even if it already exists")
+    parser.add_argument("--reverse-zone", dest="reverse_zones",
+                        metavar="REVERSE_ZONE", action="append", default=None,
+                        help="The reverse DNS zone to use. This option can "
+                        "be used multiple times")
+    parser.add_argument("--no-reverse", dest="no_reverse",
+                        action="store_true",
+                        help="Do not create new reverse DNS zone")
+    parser.add_argument("--auto-reverse", dest="auto_reverse",
+                        action="store_true",
+                        help="Create necessary reverse zones")
+    parser.add_argument("--forwarder", dest="forwarders",
+                        action="append", default=None,
+                        help="Add a DNS forwarder. This option can be used "
+                        "multiple times")
+    parser.add_argument("--no-forwarders", dest="no_forwarders",
+                        action="store_true",
+                        help="Do not add any DNS forwarders, use root "
+                        "servers instead")
+    parser.add_argument("--auto-forwarders", dest="auto_forwarders",
+                        action="store_true",
+                        help="Use DNS forwarders configured in "
+                        "/etc/resolv.conf")
+    parser.add_argument("-forward-policy-", dest="forward_policy",
+                        choices=("only", "first"), default=None,
+                        help="DNS forwarding policy for global forwarders")
+    parser.add_argument("--no-dnssec-validation", dest="no_dnssec_validation",
+                        action="store_true",
+                        help="Disable DNSSEC validation")
+    # ad trust
+    parser.add_argument("--add-sids", dest="add_sids",
+                        action="store_true",
+                        help="Add SIDs for existing users and groups as the "
+                        "final step")
+    parser.add_argument("--add-agents", dest="add_agents",
+                        action="store_true",
+                        help="Add IPA masters to a list of hosts allowed to "
+                        "serve information about users from trusted forests")
+    parser.add_argument("--enable-compat", dest="enable_compat",
+                        action="store_true",
+                        help="Enable support for trusted domains for old "
+                        "clients")
+    parser.add_argument("--netbios-name", dest="netbios_name",
+                        default=None,
+                        help="NetBIOS name of the IPA domain")
+    parser.add_argument("--rid-base", dest="rid_base",
+                        default=None, type=int,
+                        help="Start value for mapping UIDs and GIDs to RIDs")
+    parser.add_argument("--secondary-rid-base", dest="secondary_rid_base",
+                        default=None, type=int,
+                        help="Start value of the secondary range for mapping "
+                        "UIDs and GIDs to RIDs")
+    # logging and output
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true",
+                        help="print debugging information")
+    parser.add_argument("-d", "--debug", dest="verbose",
+                        action="store_true",
+                        help="alias for --verbose (deprecated)")
+    parser.add_argument("-q", "--quiet", dest="quiet",
+                        action="store_true",
+                        help="output only errors")
+    parser.add_argument("--log-file", dest="log_file",
+                        help="log to the given file")
+    # ansible
+    parser.add_argument("--ipareplica-install-packages",
+                        dest="ipareplica_install_packages",
+                        choices=("yes", "no"), default=None,
+                        help="The bool value defines if the needed packages "
+                        "are installed on the node. Default: yes")
+    parser.add_argument("--ipareplica-setup-firewalld",
+                        dest="ipareplica_setup_firewalld",
+                        choices=("yes", "no"), default=None,
+                        help="The value defines if the needed services will "
+                        "automatically be openen in the firewall managed by "
+                        "firewalld. Default: yes")
+
+    options, args = parser.parse_known_args()
+
+    if options.log_file:
+        parser.error("log_file is not supported")
+
+    if len(args) < 1:
+        parser.error("ansible host not set")
+    elif len(args) > 1:
+        parser.error("too many arguments")
+
+    return options, args
+
+
+def run_cmd(args):
+    """
+    Execute an external command.
+    """
+    p_out = subprocess.PIPE
+    p_err = subprocess.STDOUT
+    try:
+        p = subprocess.Popen(args, stdout=p_out, stderr=p_err,
+                             close_fds=True, bufsize=1,
+                             universal_newlines=True)
+        with p.stdout:
+            for line in p.stdout:
+                sys.stdout.write(line)
+    except KeyboardInterrupt:
+        p.wait()
+        raise
+
+    return p.returncode
+
+
+def main(options, args):
+    temp_dir = tempfile.mkdtemp(prefix='ansible-ipa')
+
+    inventory = os.path.join(temp_dir, "replica-inventory")
+    playbook = os.path.join(temp_dir, "replica-playbook.yml")
+
+    with open(inventory, 'w') as f:
+        if options.servers:
+            f.write("[ipaservers]\n")
+            for server in options.servers:
+                f.write("%s\n" % server)
+            f.write("\n")
+        f.write("[ipareplica]\n")
+        f.write("%s\n" % args[0])
+        f.write("\n")
+        f.write("[ipareplica:vars]\n")
+        # basic
+        if options.admin_password:
+            f.write("ipaadmin_password=%s\n" % options.admin_password)
+        if options.ip_addresses:
+            f.write("ipareplica_ip_addresses=%s\n" %
+                    ",".join(options.ip_addresses))
+        if options.domain:
+            f.write("ipareplica_domain=%s\n" % options.domain)
+        # --servers are handled above
+        if options.realm:
+            f.write("ipareplica_realm=%s\n" % options.realm)
+        if options.hostname:
+            f.write("ipareplica_hostname=%s\n" % options.hostname)
+        if options.principal:
+            f.write("ipaadmin_principal=%s\n" % options.principal)
+        if options.pki_config_override:
+            f.write("ipareplica_pki_config_override=yes\n")
+        if options.no_host_dns:
+            f.write("ipareplica_no_host_dns=yes\n")
+        if options.skip_conncheck:
+            f.write("ipareplica_skip_conncheck=yes\n")
+        # server
+        if options.dm_password:
+            f.write("ipadm_password=%s\n" % options.dm_password)
+        if options.hidden_replica:
+            f.write("ipareplica_hidden_replica=yes\n")
+        if options.setup_adtrust:
+            f.write("ipareplica_setup_adtrust=yes\n")
+        if options.setup_ca:
+            f.write("ipareplica_setup_ca=yes\n")
+        if options.setup_kra:
+            f.write("ipareplica_setup_kra=yes\n")
+        if options.setup_dns:
+            f.write("ipareplica_setup_dns=yes\n")
+        if options.no_pkinit:
+            f.write("ipareplica_no_pkinit=yes\n")
+        if options.no_ui_redirect:
+            f.write("ipareplica_no_ui_redirect=yes\n")
+        # ssl certificate
+        if options.dirsrv_cert_files:
+            f.write("ipareplica_dirsrv_cert_files=%s\n" %
+                    ",".join(options.dirsrv_cert_files))
+        if options.http_cert_files:
+            f.write("ipareplica_http_cert_files=%s\n" %
+                    ",".join(options.http_cert_files))
+        if options.pkinit_cert_files:
+            f.write("ipareplica_pkinit_cert_files=%s\n" %
+                    ",".join(options.pkinit_cert_files))
+        if options.dirsrv_pin:
+            f.write("ipareplica_dirsrv_pin=%s\n" % options.dirsrv_pin)
+        if options.http_pin:
+            f.write("ipareplica_http_pin=%s\n" % options.http_pin)
+        if options.pkinit_pin:
+            f.write("ipareplica_pkinit_pin=%s\n" % options.pkinit_pin)
+        if options.dirsrv_cert_name:
+            f.write("ipareplica_dirsrv_cert_name=%s\n" %
+                    options.dirsrv_cert_name)
+        if options.http_cert_name:
+            f.write("ipareplica_http_cert_name=%s\n" % options.http_cert_name)
+        if options.pkinit_cert_name:
+            f.write("ipareplica_pkinit_cert_name=%s\n" %
+                    options.pkinit_cert_name)
+        # client
+        if options.keytab:
+            f.write("ipaclient_keytab=%s\n" % options.keytab)
+        if options.mkhomedir:
+            f.write("ipaclient_mkhomedir=yes\n")
+        if options.force_join:
+            f.write("ipaclient_force_join=yes\n")
+        if options.ntp_servers:
+            f.write("ipaclient_ntp_server=%s\n" %
+                    ",".join(options.ntp_replicas))
+        if options.ntp_pool:
+            f.write("ipaclient_ntp_pool=%s\n" % options.ntp_pool)
+        if options.no_ntp:
+            f.write("ipaclient_no_ntp=yes\n")
+        if options.ssh_trust_dns:
+            f.write("ipaclient_ssh_trust_dns=yes\n")
+        if options.no_ssh:
+            f.write("ipaclient_no_ssh=yes\n")
+        if options.no_sshd:
+            f.write("ipaclient_no_sshd=yes\n")
+        if options.no_dns_sshfp:
+            f.write("ipaclient_no_dns_sshfp=yes\n")
+        # certificate system
+        if options.skip_schema_check:
+            f.write("ipareplica_skip_schema_check=yes\n")
+        # dns
+        if options.allow_zone_overlap:
+            f.write("ipareplica_allow_zone_overlap=yes\n")
+        if options.reverse_zones:
+            f.write("ipareplica_reverse_zones=%s\n" %
+                    ",".join(options.reverse_zones))
+        if options.no_reverse:
+            f.write("ipareplica_no_reverse=yes\n")
+        if options.auto_reverse:
+            f.write("ipareplica_auto_reverse=yes\n")
+        if options.forwarders:
+            f.write("ipareplica_forwarders=%s\n" %
+                    ",".join(options.forwarders))
+        if options.no_forwarders:
+            f.write("ipareplica_no_forwarders=yes\n")
+        if options.auto_forwarders:
+            f.write("ipareplica_auto_forwarders=yes\n")
+        if options.forward_policy:
+            f.write("ipareplica_forward_policy=%s\n" % options.forward_policy)
+        if options.no_dnssec_validation:
+            f.write("ipareplica_no_dnssec_validation=yes\n")
+        # ad trust
+        if options.add_sids:
+            f.write("ipareplica_add_sids=yes\n")
+        if options.add_agents:
+            f.write("ipareplica_add_agents=yes\n")
+        if options.enable_compat:
+            f.write("ipareplica_enable_compat=yes\n")
+        if options.netbios_name:
+            f.write("ipareplica_netbios_name=%s\n" % options.netbios_name)
+        if options.rid_base:
+            f.write("ipareplica_rid_base=%s\n" % options.rid_base)
+        if options.secondary_rid_base:
+            f.write("ipareplica_secondary_rid_base=%s\n" %
+                    options.secondary_rid_base)
+        # ansible
+        if options.ipareplica_install_packages:
+            f.write("ipareplica_install-packages=%s\n" %
+                    options.ipareplica_install_packages)
+        if options.ipareplica_setup_firewalld:
+            f.write("ipareplica_setup_firewalld=%s\n" %
+                    options.ipareplica_setup_firewalld)
+
+    # uninstall done with ipaserver role
+    state = "present"
+
+    with open(playbook, 'w') as f:
+        f.write("""---
+- name: Playbook to configure IPA replica
+  hosts: ipareplica
+  become: true
+
+  roles:
+  - role: ipareplica
+    state: %s
+""" % state)
+
+    try:
+        returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook])
+        if returncode != 0:
+            raise RuntimeError()
+    finally:
+        shutil.rmtree(temp_dir, ignore_errors=True)
+
+
+options, args = parse_options()
+try:
+    main(options, args)
+except KeyboardInterrupt:
+    sys.exit(1)
+except SystemExit as e:
+    sys.exit(e)
+except RuntimeError as e:
+    sys.exit(e)
+except Exception as e:
+    if options.verbose:
+        traceback.print_exc(file=sys.stdout)
+    else:
+        print("Re-run %s with --verbose option to get more information" %
+              sys.argv[0])
+
+    print("Unexpected error: %s" % str(e))
+    sys.exit(1)
diff --git a/utils/ansible-ipa-server-install b/utils/ansible-ipa-server-install
new file mode 100755
index 0000000000000000000000000000000000000000..299adebd11b3134fb16aa2db361b4ed415d5a4e4
--- /dev/null
+++ b/utils/ansible-ipa-server-install
@@ -0,0 +1,550 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Copyright (C) 2019  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 shutil
+import tempfile
+import argparse
+import traceback
+import subprocess
+
+
+def parse_options():
+    usage = "Usage: anisble-ipa-server-install [options] <ansible host>"
+
+    parser = argparse.ArgumentParser(usage=usage)
+    parser.add_argument("--version", dest="version",
+                        action="store_true",
+                        help="show program's version number and exit")
+    parser.add_argument("-U", "--unattended", dest="unattended",
+                        action="store_true",
+                        help="unattended (un)installation never prompts the "
+                        "user")
+    parser.add_argument("--uninstall", dest="uninstall",
+                        action="store_true",
+                        help="uninstall an existing installation. The "
+                        "uninstall can be run with --unattended option")
+    # basic
+    parser.add_argument("-p", "--ds-password", dest="dm_password",
+                        default=None,
+                        help="Directory Manager password")
+    parser.add_argument("-a", "--admin-password", dest="admin_password",
+                        default=None,
+                        help="admin user kerberos password")
+    parser.add_argument("--ip-address", dest="ip_addresses",
+                        metavar="IP_ADDRESS",
+                        action='append', default=None,
+                        help="Master Server IP Address. This option can be "
+                        "used multiple times")
+    parser.add_argument("-n", "--domain", dest="domain",
+                        metavar="DOMAIN_NAME", default=None,
+                        help="primary DNS domain of the IPA deployment (not "
+                        "necessarily related to the current hostname)")
+    parser.add_argument("-r", "--realm", dest="realm",
+                        metavar="REALM_NAME", default=None,
+                        help="Kerberos realm name of the IPA deployment "
+                        "(typically un upper-cased name of the primary DNS "
+                        "domain)")
+    parser.add_argument("--hostname", dest="hostname",
+                        metavar="HOST_NAME", default=None,
+                        help="fully qualified name of this host")
+    parser.add_argument("--ca-cert-file", dest="ca_cert_file",
+                        metavar="FILE", default=None,
+                        help="File containing CA certificates for the "
+                        "service certificate files")
+    parser.add_argument("--pki-config-override", dest="pki_config_override",
+                        default=None,
+                        help="Path to ini file with config overrides.")
+    parser.add_argument("--no-host-dns", dest="no_host_dns",
+                        action="store_true",
+                        help="Do not use DNS for hostname lookup during "
+                        "installation")
+    # server
+    parser.add_argument("--setup-adtrust", dest="setup_adtrust",
+                        action="store_true",
+                        help="configure AD trust capability")
+    parser.add_argument("--setup-kra", dest="setup_kra",
+                        action="store_true",
+                        help="configure a dogtag KRA")
+    parser.add_argument("--setup-dns", dest="setup_dns",
+                        action="store_true",
+                        help="configure bind with our zone")
+    parser.add_argument("--idstart", dest="idstart",
+                        type=int, default=None,
+                        help="The starting value for the IDs range (default "
+                        "random)")
+    parser.add_argument("--idmax", dest="idmax",
+                        default=None, type=int,
+                        help="The max value for the IDs range (default: "
+                        "idstart+199999)")
+    parser.add_argument("--no-hbac-allow", dest="no_hbac_allow",
+                        action="store_true",
+                        help="Don't install allow_all HBAC rule")
+    parser.add_argument("--no-pkinit", dest="no_pkinit",
+                        action="store_true",
+                        help="disables pkinit setup steps")
+    parser.add_argument("--no-ui-redirect", dest="no_ui_redirect",
+                        action="store_true",
+                        help="Do not automatically redirect to the Web UI")
+    parser.add_argument("--dirsrv-config-file", dest="dirsrv_config_file",
+                        metavar="FILE", default=None,
+                        help="The path to LDIF file that will be used to "
+                        "modify configuration of dse.ldif during "
+                        "installation of the directory server instance")
+    # ssl certificate
+    parser.add_argument("--dirsrv-cert-file", dest="dirsrv_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Directory Server SSL "
+                        "certificate and private key")
+    parser.add_argument("--http-cert-file", dest="http_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Apache Server SSL "
+                        "certificate and private key")
+    parser.add_argument("--pkinit-cert-file", dest="pkinit_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the Kerberos KDC SSL "
+                        "certificate and Private key")
+    parser.add_argument("--dirsrv-pin", dest="dirsrv_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Directory Server "
+                        "private key")
+    parser.add_argument("--http-pin", dest="http_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Apache Server "
+                        "private key")
+    parser.add_argument("--pkinit-pin", dest="pkinit_pin",
+                        metavar="PIN", default=None,
+                        help="The password to unlock the Kerberos KDC "
+                        "private key")
+    parser.add_argument("--dirsrv-cert-name", dest="dirsrv_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Directory Server SSL certificate "
+                        "to install")
+    parser.add_argument("--http-cert-name", dest="http_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Apache Server SSL certificate to "
+                        "install")
+    parser.add_argument("--pkinit-cert-name", dest="pkinit_cert_name",
+                        metavar="NAME", default=None,
+                        help="Name of the Kerberos KDC SSL certificate to "
+                        "install")
+    # client
+    parser.add_argument("--mkhomedir", dest="mkhomedir",
+                        action="store_true",
+                        help="create home directories for users on their "
+                        "first login")
+    parser.add_argument("--ntp-server", dest="ntp_servers",
+                        metavar="NTP_SERVER",
+                        action='append', default=None,
+                        help="ntp server to use. This option can be used "
+                        "multiple times")
+    parser.add_argument("--ntp-pool", dest="ntp_pool",
+                        default=None,
+                        help="ntp server pool to use")
+    parser.add_argument("-N", "--no-ntp", dest="no_ntp",
+                        action="store_true",
+                        help="do not configure ntp")
+    parser.add_argument("--ssh-trust-dns", dest="ssh_trust_dns",
+                        action="store_true",
+                        help="configure OpenSSH client to trust DNS SSHFP "
+                        "records")
+    parser.add_argument("--no-ssh", dest="no_ssh",
+                        action="store_true",
+                        help="do not configure OpenSSH client")
+    parser.add_argument("--no-sshd", dest="no_sshd",
+                        action="store_true",
+                        help="do not configure OpenSSH server")
+    parser.add_argument("--no-dns-sshfp", dest="no_dns_sshfp",
+                        action="store_true",
+                        help="do not automatically create DNS SSHFP records")
+    # certificate system
+    parser.add_argument("--external-ca", dest="external_ca",
+                        action="store_true",
+                        help="Generate a CSR for the IPA CA certificate to "
+                        "be signed by an external CA")
+    parser.add_argument("--external-ca-type", dest="external_ca_type",
+                        choices=("generic", "ms-cs"), default=None,
+                        help="Type of the external CA")
+    parser.add_argument("--external-ca-profile", dest="external_ca_profile",
+                        default=None,
+                        help="Specify the certificate profile/template to "
+                        "use at the external CA")
+    parser.add_argument("--external-cert-file", dest="external_cert_files",
+                        metavar="FILE", default=None, action="append",
+                        help="File containing the IPA CA certificate and the "
+                        "external CA certificate chain")
+    parser.add_argument("--subject-base", dest="subject_base",
+                        default=None,
+                        help="The certificate subject base (default "
+                        "O=<realm-name>). RDNs are in LDAP order (most "
+                        "specific RDN first).")
+    parser.add_argument("--ca-subject", dest="ca_subject",
+                        default=None,
+                        help="The CA certificate subject DN (default "
+                        "CN=Certificate Authority,O=<realm-name>). RDNs are "
+                        "in LDAP order (most specific RDN first).")
+    parser.add_argument("--ca-signing-algorithm", dest="ca_signing_algorithm",
+                        choices=("SHA1withRSA", "SHA256withRSA",
+                                 "SHA512withRSA"),
+                        default=None,
+                        help="Signing algorithm of the IPA CA certificate")
+    # dns
+    parser.add_argument("--allow-zone-overlap", dest="allow_zone_overlap",
+                        action="store_true",
+                        help="Create DNS zone even if it already exists")
+    parser.add_argument("--reverse-zone", dest="reverse_zones",
+                        metavar="REVERSE_ZONE", action="append", default=None,
+                        help="The reverse DNS zone to use. This option can "
+                        "be used multiple times")
+    parser.add_argument("--no-reverse", dest="no_reverse",
+                        action="store_true",
+                        help="Do not create new reverse DNS zone")
+    parser.add_argument("--auto-reverse", dest="auto_reverse",
+                        action="store_true",
+                        help="Create necessary reverse zones")
+    parser.add_argument("--zonemgr", dest="zonemgr",
+                        default=None,
+                        help="DNS zone manager e-mail address. Defaults to "
+                        "hostmaster@DOMAIN")
+    parser.add_argument("--forwarder", dest="forwarders",
+                        action="append", default=None,
+                        help="Add a DNS forwarder. This option can be used "
+                        "multiple times")
+    parser.add_argument("--no-forwarders", dest="no_forwarders",
+                        action="store_true",
+                        help="Do not add any DNS forwarders, use root "
+                        "servers instead")
+    parser.add_argument("--auto-forwarders", dest="auto_forwarders",
+                        action="store_true",
+                        help="Use DNS forwarders configured in "
+                        "/etc/resolv.conf")
+    parser.add_argument("-forward-policy-", dest="forward_policy",
+                        choices=("only", "first"), default=None,
+                        help="DNS forwarding policy for global forwarders")
+    parser.add_argument("--no-dnssec-validation", dest="no_dnssec_validation",
+                        action="store_true",
+                        help="Disable DNSSEC validation")
+    # ad trust
+    parser.add_argument("--enable-compat", dest="enable_compat",
+                        action="store_true",
+                        help="Enable support for trusted domains for old "
+                        "clients")
+    parser.add_argument("--netbios-name", dest="netbios_name",
+                        default=None,
+                        help="NetBIOS name of the IPA domain")
+    parser.add_argument("--rid-base", dest="rid_base",
+                        default=None, type=int,
+                        help="Start value for mapping UIDs and GIDs to RIDs")
+    parser.add_argument("--secondary-rid-base", dest="secondary_rid_base",
+                        default=None, type=int,
+                        help="Start value of the secondary range for mapping "
+                        "UIDs and GIDs to RIDs")
+    # uninstall
+    parser.add_argument("--ignore-topology-disconnect",
+                        dest="ignore_topology_disconnect",
+                        action="store_true",
+                        help="do not check whether server uninstall "
+                        "disconnects the topology (domain level 1+)")
+    parser.add_argument("--ignore-last-of-role", dest="ignore_last_of_role",
+                        action="store_true",
+                        help="do not check whether server uninstall removes "
+                        "last CA/DNS server or DNSSec master (domain level "
+                        "1+)")
+    # logging and output
+    parser.add_argument("-v", "--verbose", dest="verbose",
+                        action="store_true",
+                        help="print debugging information")
+    parser.add_argument("-d", "--debug", dest="verbose",
+                        action="store_true",
+                        help="alias for --verbose (deprecated)")
+    parser.add_argument("-q", "--quiet", dest="quiet",
+                        action="store_true",
+                        help="output only errors")
+    parser.add_argument("--log-file", dest="log_file",
+                        help="log to the given file")
+
+    # ansible
+    parser.add_argument("--ipaserver-install-packages",
+                        dest="ipaserver_install_packages",
+                        choices=("yes", "no"), default=None,
+                        help="The bool value defines if the needed packages "
+                        "are installed on the node. Default: yes")
+    parser.add_argument("--ipaserver-setup-firewalld",
+                        dest="ipaserver_setup_firewalld",
+                        choices=("yes", "no"), default=None,
+                        help="The value defines if the needed services will "
+                        "automatically be openen in the firewall managed by "
+                        "firewalld. Default: yes")
+    parser.add_argument("--ipaserver-external-cert-files-from-controller",
+                        dest="ipaserver_external_cert_files_from_controller",
+                        default=None, action="append",
+                        help="Files containing the IPA CA certificates and "
+                        "the external CA certificate chains on the "
+                        "controller that will be copied to the ipaserver "
+                        "host to /root folder.")
+    parser.add_argument("--ipaserver-copy-csr-to-controller",
+                        dest="ipaserver_copy_csr_to_controller",
+                        choices=("yes", "no"), default=None,
+                        help="Copy the generated CSR from the ipaserver to "
+                        "the controller as <hostname>-ipa.csr.")
+
+    options, args = parser.parse_known_args()
+
+    if options.log_file:
+        parser.error("log_file is not supported")
+
+    if len(args) < 1:
+        parser.error("ansible host not set")
+    elif len(args) > 1:
+        parser.error("too many arguments")
+
+    return options, args
+
+
+def run_cmd(args):
+    """
+    Execute an external command.
+    """
+    p_out = subprocess.PIPE
+    p_err = subprocess.STDOUT
+    try:
+        p = subprocess.Popen(args, stdout=p_out, stderr=p_err,
+                             close_fds=True, bufsize=1,
+                             universal_newlines=True)
+        with p.stdout:
+            for line in p.stdout:
+                sys.stdout.write(line)
+    except KeyboardInterrupt:
+        p.wait()
+        raise
+
+    return p.returncode
+
+
+def main(options, args):
+    temp_dir = tempfile.mkdtemp(prefix='ansible-ipa')
+
+    inventory = os.path.join(temp_dir, "server-inventory")
+    playbook = os.path.join(temp_dir, "server-playbook.yml")
+
+    with open(inventory, 'w') as f:
+        f.write("[ipaserver]\n")
+        f.write("%s\n" % args[0])
+        f.write("\n")
+        f.write("[ipaserver:vars]\n")
+        # basic
+        if options.dm_password:
+            f.write("ipadm_password=%s\n" % options.dm_password)
+        if options.admin_password:
+            f.write("ipaadmin_password=%s\n" % options.admin_password)
+        if options.ip_addresses:
+            f.write("ipaserver_ip_addresses=%s\n" %
+                    ",".join(options.ip_addresses))
+        if options.domain:
+            f.write("ipaserver_domain=%s\n" % options.domain)
+        if options.realm:
+            f.write("ipaserver_realm=%s\n" % options.realm)
+        if options.hostname:
+            f.write("ipaserver_hostname=%s\n" % options.hostname)
+        if options.ca_cert_file:
+            f.write("ipaserver_ca_cert_files=%s\n" % options.ca_cert_file)
+        if options.pki_config_override:
+            f.write("ipaserver_pki_config_override=yes\n")
+        if options.no_host_dns:
+            f.write("ipaserver_no_host_dns=yes\n")
+        # server
+        if options.setup_adtrust:
+            f.write("ipaserver_setup_adtrust=yes\n")
+        if options.setup_kra:
+            f.write("ipaserver_setup_kra=yes\n")
+        if options.setup_dns:
+            f.write("ipaserver_setup_dns=yes\n")
+        if options.idstart:
+            f.write("ipaserver_idstart=%s\n" % options.idstart)
+        if options.idmax:
+            f.write("ipaserver_idmax=%s\n" % options.idmax)
+        if options.no_hbac_allow:
+            f.write("ipaserver_no_hbac_allow=yes\n")
+        if options.no_pkinit:
+            f.write("ipaserver_no_pkinit=yes\n")
+        if options.no_ui_redirect:
+            f.write("ipaserver_no_ui_redirect=yes\n")
+        if options.dirsrv_config_file:
+            f.write("ipaserver_dirsrv_config_file=%s\n" %
+                    options.dirsrv_config_file)
+        # ssl certificate
+        if options.dirsrv_cert_files:
+            f.write("ipaserver_dirsrv_cert_files=%s\n" %
+                    ",".join(options.dirsrv_cert_files))
+        if options.http_cert_files:
+            f.write("ipaserver_http_cert_files=%s\n" %
+                    ",".join(options.http_cert_files))
+        if options.pkinit_cert_files:
+            f.write("ipaserver_pkinit_cert_files=%s\n" %
+                    ",".join(options.pkinit_cert_files))
+        if options.dirsrv_pin:
+            f.write("ipaserver_dirsrv_pin=%s\n" % options.dirsrv_pin)
+        if options.http_pin:
+            f.write("ipaserver_http_pin=%s\n" % options.http_pin)
+        if options.pkinit_pin:
+            f.write("ipaserver_pkinit_pin=%s\n" % options.pkinit_pin)
+        if options.dirsrv_cert_name:
+            f.write("ipaserver_dirsrv_cert_name=%s\n" %
+                    options.dirsrv_cert_name)
+        if options.http_cert_name:
+            f.write("ipaserver_http_cert_name=%s\n" % options.http_cert_name)
+        if options.pkinit_cert_name:
+            f.write("ipaserver_pkinit_cert_name=%s\n" %
+                    options.pkinit_cert_name)
+        # client
+        if options.mkhomedir:
+            f.write("ipaclient_mkhomedir=yes\n")
+        if options.ntp_servers:
+            f.write("ipaclient_ntp_servers=%s\n" %
+                    ",".join(options.ntp_servers))
+        if options.ntp_pool:
+            f.write("ipaclient_ntp_pool=%s\n" % options.ntp_pool)
+        if options.no_ntp:
+            f.write("ipaclient_no_ntp=yes\n")
+        if options.ssh_trust_dns:
+            f.write("ipaclient_ssh_trust_dns=yes\n")
+        if options.no_ssh:
+            f.write("ipaclient_no_ssh=yes\n")
+        if options.no_sshd:
+            f.write("ipaclient_no_sshd=yes\n")
+        if options.no_dns_sshfp:
+            f.write("ipaclient_no_dns_sshfp=yes\n")
+        # certificate system
+        if options.external_ca:
+            f.write("ipaserver_external_ca=yes\n")
+        if options.external_ca_type:
+            f.write("ipaserver_external_ca_type=%s\n" %
+                    options.external_ca_type)
+        if options.external_ca_profile:
+            f.write("ipaserver_external_ca_profile=%s\n" %
+                    options.external_ca_profile)
+        if options.external_cert_files:
+            f.write("ipaserver_external_cert_files=%s\n" %
+                    ",".join(options.external_cert_files))
+        if options.subject_base:
+            f.write("ipaserver_subject_base=%s\n" % options.subject_base)
+        if options.ca_subject:
+            f.write("ipaserver_ca_subject=%s\n" % options.ca_subject)
+        if options.ca_signing_algorithm:
+            f.write("ipaserver_ca_signing_algorithm=%s\n" %
+                    options.ca_signing_algorithm)
+        # dns
+        if options.allow_zone_overlap:
+            f.write("ipaserver_allow_zone_overlap=yes\n")
+        if options.reverse_zones:
+            f.write("ipaserver_reverse_zones=%s\n" %
+                    ",".join(options.reverse_zones))
+        if options.no_reverse:
+            f.write("ipaserver_no_reverse=yes\n")
+        if options.auto_reverse:
+            f.write("ipaserver_auto_reverse=yes\n")
+        if options.zonemgr:
+            f.write("ipaserver_zonemgr=%s\n" % options.zonemgr)
+        if options.forwarders:
+            f.write("ipaserver_forwarders=%s\n" %
+                    ",".join(options.forwarders))
+        if options.no_forwarders:
+            f.write("ipaserver_no_forwarders=yes\n")
+        if options.auto_forwarders:
+            f.write("ipaserver_auto_forwarders=yes\n")
+        if options.forward_policy:
+            f.write("ipaserver_forward_policy=%s\n" % options.forward_policy)
+        if options.no_dnssec_validation:
+            f.write("ipaserver_no_dnssec_validation=yes\n")
+        # ad trust
+        if options.enable_compat:
+            f.write("ipaserver_enable_compat=yes\n")
+        if options.netbios_name:
+            f.write("ipaserver_netbios_name=%s\n" % options.netbios_name)
+        if options.rid_base:
+            f.write("ipaserver_rid_base=%s\n" % options.rid_base)
+        if options.secondary_rid_base:
+            f.write("ipaserver_secondary_rid_base=%s\n" %
+                    options.secondary_rid_base)
+        # uninstall
+        if options.ignore_topology_disconnect:
+            f.write("ipaserver_ignore_topology_disconnect=yes\n")
+        if options.ignore_last_of_role:
+            f.write("ipaserver_ignore_last_of_role=yes\n")
+        # ansible
+        if options.ipaserver_install_packages:
+            f.write("ipaserver_install-packages=%s\n" %
+                    options.ipaserver_install_packages)
+        if options.ipaserver_setup_firewalld:
+            f.write("ipaserver_setup_firewalld=%s\n" %
+                    options.ipaserver_setup_firewalld)
+        if options.ipaserver_external_cert_files_from_controller:
+            f.write("ipaserver_external_cert_files_from_controller=%s\n" %
+                    ",".join(
+                        options.ipaserver_external_cert_files_from_controller))
+        if options.ipaserver_copy_csr_to_controller:
+            f.write("ipaserver_copy_csr_to_controller=%s\n" %
+                    options.ipaserver_copy_csr_to_controller)
+
+    if options.uninstall:
+        state = "absent"
+    else:
+        state = "present"
+
+    with open(playbook, 'w') as f:
+        f.write("""---
+- name: Playbook to configure IPA server
+  hosts: ipaserver
+  become: true
+
+  roles:
+  - role: ipaserver
+    state: %s
+""" % state)
+
+    try:
+        returncode = run_cmd(['ansible-playbook', '-i', inventory, playbook])
+        if returncode != 0:
+            raise RuntimeError()
+    finally:
+        shutil.rmtree(temp_dir, ignore_errors=True)
+
+
+options, args = parse_options()
+try:
+    main(options, args)
+except KeyboardInterrupt:
+    sys.exit(1)
+except SystemExit as e:
+    sys.exit(e)
+except RuntimeError as e:
+    sys.exit(e)
+except Exception as e:
+    if options.verbose:
+        traceback.print_exc(file=sys.stdout)
+    else:
+        print("Re-run %s with --verbose option to get more information" %
+              sys.argv[0])
+
+    print("Unexpected error: %s" % str(e))
+    sys.exit(1)