Hi,

the attached patch fixes part of <https://fedorahosted.org/freeipa/ticket/4517>.


See also Martin Babinsky's patch 51: <https://www.redhat.com/archives/freeipa-devel/2015-August/msg00012.html>.

Honza

--
Jan Cholasta
From f8c2de11794777f406fec377ed404108cdce1655 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 6 Aug 2015 08:14:17 +0200
Subject: [PATCH 1/3] install: Support overriding knobs in subclasses

https://fedorahosted.org/freeipa/ticket/4517
---
 ipapython/install/cli.py  |   2 -
 ipapython/install/core.py | 203 ++++++++++++++++++++++++++--------------------
 2 files changed, 117 insertions(+), 88 deletions(-)

diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
index 1ba9a81..5ee445d 100644
--- a/ipapython/install/cli.py
+++ b/ipapython/install/cli.py
@@ -86,8 +86,6 @@ class ConfigureTool(admintool.AdminTool):
         transformed_cls = cls._transform(cls.configurable_class)
         for owner_cls, name in transformed_cls.knobs():
             knob_cls = getattr(owner_cls, name)
-            if not knob_cls.initializable:
-                continue
             if cls.positional_arguments and name in cls.positional_arguments:
                 continue
 
diff --git a/ipapython/install/core.py b/ipapython/install/core.py
index c313c27..3af9371 100644
--- a/ipapython/install/core.py
+++ b/ipapython/install/core.py
@@ -6,9 +6,11 @@
 The framework core.
 """
 
-import sys
 import abc
+import collections
+import functools
 import itertools
+import sys
 
 from ipapython.ipa_log_manager import root_logger
 
@@ -31,7 +33,8 @@ _missing = object()
 _counter = itertools.count()
 
 
-def _class_cmp(a, b):
+@functools.cmp_to_key
+def _class_key(a, b):
     if a is b:
         return 0
     elif issubclass(a, b):
@@ -52,27 +55,48 @@ class KnobValueError(ValueError):
         self.name = name
 
 
-class InnerClass(object):
+class AttributeBase(object):
     __metaclass__ = util.InnerClassMeta
+
+    # shut up pylint
     __outer_class__ = None
     __outer_name__ = None
 
-
-class PropertyBase(InnerClass):
     @property
     def default(self):
         raise AttributeError('default')
 
     def __init__(self, outer):
-        self.outer = outer
+        pass
 
     def __get__(self, obj, obj_type):
+        while obj is not None:
+            try:
+                return obj.__dict__[self.__outer_name__]
+            except KeyError:
+                pass
+            obj = obj._get_fallback()
+
         try:
-            return obj._get_property(self.__outer_name__)
-        except AttributeError:
-            if not hasattr(self, 'default'):
-                raise
             return self.default
+        except AttributeError:
+            raise AttributeError(self.__outer_name__)
+
+    def __set__(self, obj, value):
+        try:
+            obj.__dict__[self.__outer_name__] = value
+        except KeyError:
+            raise AttributeError(self.__outer_name__)
+
+    def __delete__(self, obj):
+        try:
+            del obj.__dict__[self.__outer_name__]
+        except KeyError:
+            raise AttributeError(self.__outer_name__)
+
+
+class PropertyBase(AttributeBase):
+    pass
 
 
 def Property(default=_missing):
@@ -83,9 +107,8 @@ def Property(default=_missing):
     return util.InnerClassMeta('Property', (PropertyBase,), class_dict)
 
 
-class KnobBase(PropertyBase):
+class KnobBase(AttributeBase):
     type = None
-    initializable = True
     sensitive = False
     deprecated = False
     description = None
@@ -96,21 +119,8 @@ class KnobBase(PropertyBase):
 
     _order = None
 
-    def __set__(self, obj, value):
-        try:
-            self.validate(value)
-        except KnobValueError:
-            raise
-        except ValueError as e:
-            raise KnobValueError(self.__outer_name__, str(e))
-
-        obj.__dict__[self.__outer_name__] = value
-
-    def __delete__(self, obj):
-        try:
-            del obj.__dict__[self.__outer_name__]
-        except KeyError:
-            raise AttributeError(self.__outer_name__)
+    def __init__(self, outer):
+        self.outer = outer
 
     def validate(self, value):
         pass
@@ -134,12 +144,17 @@ class KnobBase(PropertyBase):
         return cls
 
 
-def Knob(type, default=_missing, initializable=_missing, sensitive=_missing,
+def Knob(type_or_base, default=_missing, sensitive=_missing,
          deprecated=_missing, description=_missing, cli_name=_missing,
          cli_short_name=_missing, cli_aliases=_missing, cli_metavar=_missing):
     class_dict = {}
     class_dict['_order'] = next(_counter)
-    class_dict['type'] = type
+
+    if (not isinstance(type_or_base, type) or
+            not issubclass(type_or_base, KnobBase)):
+        class_dict['type'] = type_or_base
+        type_or_base = KnobBase
+
     if default is not _missing:
         class_dict['default'] = default
     if sensitive is not _missing:
@@ -157,7 +172,7 @@ def Knob(type, default=_missing, initializable=_missing, sensitive=_missing,
     if cli_metavar is not _missing:
         class_dict['cli_metavar'] = cli_metavar
 
-    return util.InnerClassMeta('Knob', (KnobBase,), class_dict)
+    return util.InnerClassMeta('Knob', (type_or_base,), class_dict)
 
 
 class Configurable(object):
@@ -177,14 +192,27 @@ class Configurable(object):
 
         assert not hasattr(super(Configurable, cls), 'knobs')
 
-        result = []
-        for name in dir(cls):
-            knob_cls = getattr(cls, name)
-            if isinstance(knob_cls, type) and issubclass(knob_cls, KnobBase):
-                result.append(knob_cls)
-        result = sorted(result, key=lambda knob_cls: knob_cls._order)
-        for knob_cls in result:
-            yield knob_cls.__outer_class__, knob_cls.__outer_name__
+        seen = set()
+
+        for owner_cls in cls.__mro__:
+            result = []
+
+            for name, knob_cls in owner_cls.__dict__.iteritems():
+                if name in seen:
+                    continue
+                seen.add(name)
+
+                if not isinstance(knob_cls, type):
+                    continue
+                if not issubclass(knob_cls, KnobBase):
+                    continue
+
+                result.append((knob_cls._order, owner_cls, name))
+
+            result = sorted(result, key=lambda r: r[0])
+
+            for order, owner_cls, name in result:
+                yield owner_cls, name
 
     @classmethod
     def group(cls):
@@ -199,26 +227,14 @@ class Configurable(object):
 
         self.log = root_logger
 
-        for name in dir(self.__class__):
+        cls = self.__class__
+        for name in dir(cls):
             if name.startswith('_'):
                 continue
-            property_cls = getattr(self.__class__, name)
-            if not isinstance(property_cls, type):
-                continue
-            if not issubclass(property_cls, PropertyBase):
+            attr_cls = getattr(cls, name)
+            if not isinstance(attr_cls, type):
                 continue
-            if issubclass(property_cls, KnobBase):
-                continue
-            try:
-                value = kwargs.pop(name)
-            except KeyError:
-                pass
-            else:
-                setattr(self, name, value)
-
-        for owner_cls, name in self.knobs():
-            knob_cls = getattr(owner_cls, name)
-            if not knob_cls.initializable:
+            if not issubclass(attr_cls, AttributeBase):
                 continue
 
             try:
@@ -249,13 +265,8 @@ class Configurable(object):
 
         raise TypeError("{0} is not composite".format(self))
 
-    def _get_property(self, name):
-        assert not hasattr(super(Configurable, self), '_get_property')
-
-        try:
-            return self.__dict__[name]
-        except KeyError:
-            raise AttributeError(name)
+    def _get_fallback(self):
+        return None
 
     @abc.abstractmethod
     def _configure(self):
@@ -380,9 +391,13 @@ class ComponentMeta(util.InnerClassMeta, abc.ABCMeta):
     pass
 
 
-class ComponentBase(InnerClass, Configurable):
+class ComponentBase(Configurable):
     __metaclass__ = ComponentMeta
 
+    # shut up pylint
+    __outer_class__ = None
+    __outer_name__ = None
+
     _order = None
 
     @classmethod
@@ -406,11 +421,8 @@ class ComponentBase(InnerClass, Configurable):
         obj.__dict__[self.__outer_name__] = self
         return self
 
-    def _get_property(self, name):
-        try:
-            return super(ComponentBase, self)._get_property(name)
-        except AttributeError:
-            return self.__parent._get_property(name)
+    def _get_fallback(self):
+        return self.__parent
 
     def _handle_exception(self, exc_info):
         try:
@@ -437,27 +449,34 @@ class Composite(Configurable):
     @classmethod
     def knobs(cls):
         name_dict = {}
-        owner_dict = {}
+        owner_dict = collections.OrderedDict()
 
         for owner_cls, name in super(Composite, cls).knobs():
-            knob_cls = getattr(owner_cls, name)
             name_dict[name] = owner_cls
-            owner_dict.setdefault(owner_cls, []).append(knob_cls)
+            owner_dict.setdefault(owner_cls, []).append(name)
 
         for owner_cls, name in cls.components():
             comp_cls = getattr(cls, name)
+
             for owner_cls, name in comp_cls.knobs():
                 if hasattr(cls, name):
                     continue
 
-                knob_cls = getattr(owner_cls, name)
                 try:
                     last_owner_cls = name_dict[name]
                 except KeyError:
                     name_dict[name] = owner_cls
-                    owner_dict.setdefault(owner_cls, []).append(knob_cls)
+                    owner_dict.setdefault(owner_cls, []).append(name)
                 else:
-                    if last_owner_cls is not owner_cls:
+                    knob_cls = getattr(owner_cls, name)
+                    last_knob_cls = getattr(last_owner_cls, name)
+                    if issubclass(knob_cls, last_knob_cls):
+                        name_dict[name] = owner_cls
+                        owner_dict[last_owner_cls].remove(name)
+                        owner_dict.setdefault(owner_cls, [])
+                        if name not in owner_dict[owner_cls]:
+                            owner_dict[owner_cls].append(name)
+                    elif not issubclass(last_knob_cls, knob_cls):
                         raise TypeError("{0}.knobs(): conflicting definitions "
                                         "of '{1}' in {2} and {3}".format(
                                             cls.__name__,
@@ -465,23 +484,35 @@ class Composite(Configurable):
                                             last_owner_cls.__name__,
                                             owner_cls.__name__))
 
-        for owner_cls in sorted(owner_dict, _class_cmp):
-            for knob_cls in owner_dict[owner_cls]:
-                yield knob_cls.__outer_class__, knob_cls.__outer_name__
+        for owner_cls in sorted(owner_dict, key=_class_key):
+            for name in owner_dict[owner_cls]:
+                yield owner_cls, name
 
     @classmethod
     def components(cls):
         assert not hasattr(super(Composite, cls), 'components')
 
-        result = []
-        for name in dir(cls):
-            comp_cls = getattr(cls, name)
-            if (isinstance(comp_cls, type) and
-                    issubclass(comp_cls, ComponentBase)):
-                result.append(comp_cls)
-        result = sorted(result, key=lambda comp_cls: comp_cls._order)
-        for comp_cls in result:
-            yield comp_cls.__outer_class__, comp_cls.__outer_name__
+        seen = set()
+
+        for owner_cls in cls.__mro__:
+            result = []
+
+            for name, comp_cls in owner_cls.__dict__.iteritems():
+                if name in seen:
+                    continue
+                seen.add(name)
+
+                if not isinstance(comp_cls, type):
+                    continue
+                if not issubclass(comp_cls, ComponentBase):
+                    continue
+
+                result.append((comp_cls._order, owner_cls, name))
+
+            result = sorted(result, key=lambda r: r[0])
+
+            for order, owner_cls, name in result:
+                yield owner_cls, name
 
     def _reset(self):
         self.__components = list(self._get_components())
-- 
2.4.3

From 72c185e67b288e1df6e32b0ab93a385dbee35158 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 6 Aug 2015 08:16:22 +0200
Subject: [PATCH 2/3] install: Add common base class for server and replica
 install

https://fedorahosted.org/freeipa/ticket/4517
---
 ipaserver/install/server/common.py         | 460 +++++++++++++++++++++++++++++
 ipaserver/install/server/install.py        | 459 ++--------------------------
 ipaserver/install/server/replicainstall.py | 246 +++------------
 3 files changed, 532 insertions(+), 633 deletions(-)
 create mode 100644 ipaserver/install/server/common.py

diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
new file mode 100644
index 0000000..48cfad7
--- /dev/null
+++ b/ipaserver/install/server/common.py
@@ -0,0 +1,460 @@
+import os
+import sys
+
+from ipapython.dn import DN
+from ipapython.install import common, core
+from ipapython.install.core import Knob
+from ipalib.util import validate_domain_name
+from ipaserver.install import bindinstance
+
+VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c',
+                       'serialnumber', 'l', 'title', 'sn', 'givenname',
+                       'initials', 'generationqualifier', 'dc', 'mail',
+                       'uid', 'postaladdress', 'postalcode', 'postofficebox',
+                       'houseidentifier', 'e', 'street', 'pseudonym',
+                       'incorporationlocality', 'incorporationstate',
+                       'incorporationcountry', 'businesscategory']
+
+
+class BaseServerCA(common.Installable, core.Group, core.Composite):
+    description = "certificate system"
+
+    external_ca = Knob(
+        bool, False,
+        description=("Generate a CSR for the IPA CA certificate to be signed "
+                     "by an external CA"),
+    )
+
+    external_ca_type = Knob(
+        {'generic', 'ms-cs'}, None,
+        description="Type of the external CA",
+    )
+
+    external_cert_files = Knob(
+        (list, str), None,
+        description=("File containing the IPA CA certificate and the external "
+                     "CA certificate chain"),
+        cli_name='external-cert-file',
+        cli_aliases=['external_cert_file', 'external_ca_file'],
+        cli_metavar='FILE',
+    )
+
+    @external_cert_files.validator
+    def external_cert_files(self, value):
+        if any(not os.path.isabs(path) for path in value):
+            raise ValueError("must use an absolute path")
+
+    dirsrv_cert_files = Knob(
+        (list, str), None,
+        description=("File containing the Directory Server SSL certificate "
+                     "and private key"),
+        cli_name='dirsrv-cert-file',
+        cli_aliases=['dirsrv_pkcs12'],
+        cli_metavar='FILE',
+    )
+
+    http_cert_files = Knob(
+        (list, str), None,
+        description=("File containing the Apache Server SSL certificate and "
+                     "private key"),
+        cli_name='http-cert-file',
+        cli_aliases=['http_pkcs12'],
+        cli_metavar='FILE',
+    )
+
+    pkinit_cert_files = Knob(
+        (list, str), None,
+        description=("File containing the Kerberos KDC SSL certificate and "
+                     "private key"),
+        cli_name='pkinit-cert-file',
+        cli_aliases=['pkinit_pkcs12'],
+        cli_metavar='FILE',
+    )
+
+    dirsrv_pin = Knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Directory Server private key",
+        cli_aliases=['dirsrv_pin'],
+        cli_metavar='PIN',
+    )
+
+    http_pin = Knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Apache Server private key",
+        cli_aliases=['http_pin'],
+        cli_metavar='PIN',
+    )
+
+    pkinit_pin = Knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Kerberos KDC private key",
+        cli_aliases=['pkinit_pin'],
+        cli_metavar='PIN',
+    )
+
+    dirsrv_cert_name = Knob(
+        str, None,
+        description="Name of the Directory Server SSL certificate to install",
+        cli_metavar='NAME',
+    )
+
+    http_cert_name = Knob(
+        str, None,
+        description="Name of the Apache Server SSL certificate to install",
+        cli_metavar='NAME',
+    )
+
+    pkinit_cert_name = Knob(
+        str, None,
+        description="Name of the Kerberos KDC SSL certificate to install",
+        cli_metavar='NAME',
+    )
+
+    ca_cert_files = Knob(
+        (list, str), None,
+        description=("File containing CA certificates for the service "
+                     "certificate files"),
+        cli_name='ca-cert-file',
+        cli_aliases=['root-ca-file'],
+        cli_metavar='FILE',
+    )
+
+    subject = Knob(
+        str, None,
+        description="The certificate subject base (default O=<realm-name>)",
+    )
+
+    @subject.validator
+    def subject(self, value):
+        v = unicode(value, 'utf-8')
+        if any(ord(c) < 0x20 for c in v):
+            raise ValueError("must not contain control characters")
+        if '&' in v:
+            raise ValueError("must not contain an ampersand (\"&\")")
+        try:
+            dn = DN(v)
+            for rdn in dn:
+                if rdn.attr.lower() not in VALID_SUBJECT_ATTRS:
+                    raise ValueError("invalid attribute: \"%s\"" % rdn.attr)
+        except ValueError, e:
+            raise ValueError("invalid subject base format: %s" % e)
+
+    ca_signing_algorithm = Knob(
+        {'SHA1withRSA', 'SHA256withRSA', 'SHA512withRSA'}, None,
+        description="Signing algorithm of the IPA CA certificate",
+    )
+
+    skip_schema_check = Knob(
+        bool, False,
+        description="skip check for updated CA DS schema on the remote master",
+    )
+
+
+class BaseServerDNS(common.Installable, core.Group, core.Composite):
+    description = "DNS"
+
+    forwarders = Knob(
+        (list, 'ip'), None,
+        description=("Add a DNS forwarder. This option can be used multiple "
+                     "times"),
+        cli_name='forwarder',
+    )
+
+    no_forwarders = Knob(
+        bool, False,
+        description="Do not add any DNS forwarders, use root servers instead",
+    )
+
+    reverse_zones = Knob(
+        (list, str), [],
+        description=("The reverse DNS zone to use. This option can be used "
+                     "multiple times"),
+        cli_name='reverse-zone',
+        cli_metavar='REVERSE_ZONE',
+    )
+
+    no_reverse = Knob(
+        bool, False,
+        description="Do not create new reverse DNS zone",
+    )
+
+    no_dnssec_validation = Knob(
+        bool, False,
+        description="Disable DNSSEC validation",
+    )
+
+    dnssec_master = Knob(
+        bool, False,
+        description="Setup server to be DNSSEC key master",
+    )
+
+    disable_dnssec_master = Knob(
+        bool, False,
+        description="Disable the DNSSEC master on this server",
+    )
+
+    kasp_db_file = Knob(
+        str, None,
+        description="Copy OpenDNSSEC metadata from the specified file (will "
+                    "not create a new kasp.db file)",
+    )
+
+    force = Knob(
+        bool, False,
+        description="Force install",
+    )
+
+    zonemgr = Knob(
+        str, None,
+        description=("DNS zone manager e-mail address. Defaults to "
+                     "hostmaster@DOMAIN"),
+    )
+
+    @zonemgr.validator
+    def zonemgr(self, value):
+        # validate the value first
+        try:
+            # IDNA support requires unicode
+            encoding = getattr(sys.stdin, 'encoding', None)
+            if encoding is None:
+                encoding = 'utf-8'
+            value = value.decode(encoding)
+            bindinstance.validate_zonemgr_str(value)
+        except ValueError, e:
+            # FIXME we can do this in better way
+            # https://fedorahosted.org/freeipa/ticket/4804
+            # decode to proper stderr encoding
+            stderr_encoding = getattr(sys.stderr, 'encoding', None)
+            if stderr_encoding is None:
+                stderr_encoding = 'utf-8'
+            error = unicode(e).encode(stderr_encoding)
+            raise ValueError(error)
+
+
+class BaseServer(common.Installable, common.Interactive, core.Composite):
+    realm_name = Knob(
+        str, None,
+        description="realm name",
+        cli_name='realm',
+        cli_short_name='r',
+    )
+
+    domain_name = Knob(
+        str, None,
+        description="domain name",
+        cli_name='domain',
+        cli_short_name='n',
+    )
+
+    @domain_name.validator
+    def domain_name(self, value):
+        validate_domain_name(value)
+
+    dm_password = Knob(
+        str, None,
+        sensitive=True,
+        cli_short_name='p',
+    )
+
+    admin_password = Knob(
+        str, None,
+        sensitive=True,
+    )
+
+    mkhomedir = Knob(
+        bool, False,
+        description="create home directories for users on their first login",
+    )
+
+    host_name = Knob(
+        str, None,
+        description="fully qualified name of server",
+        cli_name='hostname',
+    )
+
+    ip_addresses = Knob(
+        (list, 'ip-local'), None,
+        description=("Master Server IP Address. This option can be used "
+                     "multiple times"),
+        cli_name='ip-address',
+        cli_metavar='IP_ADDRESS',
+    )
+
+    no_host_dns = Knob(
+        bool, False,
+        description="Do not use DNS for hostname lookup during installation",
+    )
+
+    setup_ca = Knob(
+        bool, False,
+        description="configure a dogtag CA",
+    )
+
+    setup_kra = Knob(
+        bool, False,
+        description="configure a dogtag KRA",
+    )
+
+    setup_dns = Knob(
+        bool, False,
+        description="configure bind with our zone",
+    )
+
+    no_ntp = Knob(
+        bool, False,
+        description="do not configure ntp",
+        cli_short_name='N',
+    )
+
+    no_pkinit = Knob(
+        bool, False,
+        description="disables pkinit setup steps",
+    )
+
+    no_ui_redirect = Knob(
+        bool, False,
+        description="Do not automatically redirect to the Web UI",
+    )
+
+    ssh_trust_dns = Knob(
+        bool, False,
+        description="configure OpenSSH client to trust DNS SSHFP records",
+    )
+
+    no_ssh = Knob(
+        bool, False,
+        description="do not configure OpenSSH client",
+    )
+
+    no_sshd = Knob(
+        bool, False,
+        description="do not configure OpenSSH server",
+    )
+
+    no_dns_sshfp = Knob(
+        bool, False,
+        description="Do not automatically create DNS SSHFP records",
+    )
+
+    def __init__(self, **kwargs):
+        super(BaseServer, self).__init__(**kwargs)
+
+        #pylint: disable=no-member
+
+        if self.uninstalling:
+            if (self.realm_name or self.admin_password or
+                    self.master_password):
+                raise RuntimeError(
+                    "In uninstall mode, -a, -r and -P options are not allowed")
+        elif not self.interactive:
+            if (not self.realm_name or not self.dm_password or
+                    not self.admin_password):
+                raise RuntimeError(
+                    "In unattended mode you need to provide at least -r, -p "
+                    "and -a options")
+
+        if self.idmax < self.idstart:
+            raise RuntimeError(
+                "idmax (%s) cannot be smaller than idstart (%s)" %
+                (self.idmax, self.idstart))
+
+        # If any of the key file options are selected, all are required.
+        cert_file_req = (self.ca.dirsrv_cert_files, self.ca.http_cert_files)
+        cert_file_opt = (self.ca.pkinit_cert_files,)
+        if any(cert_file_req + cert_file_opt) and not all(cert_file_req):
+            raise RuntimeError(
+                "--dirsrv-cert-file and --http-cert-file are required if any "
+                "key file options are used.")
+
+        if not self.interactive:
+            if self.ca.dirsrv_cert_files and self.ca.dirsrv_pin is None:
+                raise RuntimeError(
+                    "You must specify --dirsrv-pin with --dirsrv-cert-file")
+            if self.ca.http_cert_files and self.ca.http_pin is None:
+                raise RuntimeError(
+                    "You must specify --http-pin with --http-cert-file")
+            if self.ca.pkinit_cert_files and self.ca.pkinit_pin is None:
+                raise RuntimeError(
+                    "You must specify --pkinit-pin with --pkinit-cert-file")
+
+        if self.ca.external_cert_files and self.ca.dirsrv_cert_files:
+            raise RuntimeError(
+                "Service certificate file options cannot be used with the "
+                "external CA options.")
+
+        if self.ca.external_ca_type and not self.ca.external_ca:
+            raise RuntimeError(
+                "You cannot specify --external-ca-type without --external-ca")
+
+        if not self.dns.setup_dns:
+            if self.dns.forwarders:
+                raise RuntimeError(
+                    "You cannot specify a --forwarder option without the "
+                    "--setup-dns option")
+            if self.dns.no_forwarders:
+                raise RuntimeError(
+                    "You cannot specify a --no-forwarders option without the "
+                    "--setup-dns option")
+            if self.dns.reverse_zones:
+                raise RuntimeError(
+                    "You cannot specify a --reverse-zone option without the "
+                    "--setup-dns option")
+            if self.dns.no_reverse:
+                raise RuntimeError(
+                    "You cannot specify a --no-reverse option without the "
+                    "--setup-dns option")
+            if self.dns.no_dnssec_validation:
+                raise RuntimeError(
+                    "You cannot specify a --no-dnssec-validation option "
+                    "without the --setup-dns option")
+        elif self.dns.forwarders and self.dns.no_forwarders:
+            raise RuntimeError(
+                "You cannot specify a --forwarder option together with "
+                "--no-forwarders")
+        elif not self.dns.forwarders and not self.dns.no_forwarders:
+            raise RuntimeError(
+                "You must specify at least one --forwarder option or "
+                "--no-forwarders option")
+        elif self.dns.reverse_zones and self.dns.no_reverse:
+            raise RuntimeError(
+                "You cannot specify a --reverse-zone option together with "
+                "--no-reverse")
+
+        # Automatically disable pkinit w/ dogtag until that is supported
+        self.no_pkinit = True
+
+        self.external_ca = self.ca.external_ca
+        self.external_ca_type = self.ca.external_ca_type
+        self.external_cert_files = self.ca.external_cert_files
+        self.dirsrv_cert_files = self.ca.dirsrv_cert_files
+        self.http_cert_files = self.ca.http_cert_files
+        self.pkinit_cert_files = self.ca.pkinit_cert_files
+        self.dirsrv_pin = self.ca.dirsrv_pin
+        self.http_pin = self.ca.http_pin
+        self.pkinit_pin = self.ca.pkinit_pin
+        self.dirsrv_cert_name = self.ca.dirsrv_cert_name
+        self.http_cert_name = self.ca.http_cert_name
+        self.pkinit_cert_name = self.ca.pkinit_cert_name
+        self.ca_cert_files = self.ca.ca_cert_files
+        self.subject = self.ca.subject
+        self.ca_signing_algorithm = self.ca.ca_signing_algorithm
+        self.skip_schema_check = self.ca.skip_schema_check
+
+        self.forwarders = self.dns.forwarders
+        self.no_forwarders = self.dns.no_forwarders
+        self.reverse_zones = self.dns.reverse_zones
+        self.no_reverse = self.dns.no_reverse
+        self.no_dnssec_validation = self.dns.no_dnssec_validation
+        self.dnssec_master = self.dns.dnssec_master
+        self.disable_dnssec_master = self.dns.disable_dnssec_master
+        self.kasp_db_file = self.dns.kasp_db_file
+        self.force = self.dns.force
+        self.zonemgr = self.dns.zonemgr
+
+        self.unattended = not self.interactive
+
+    ca = core.Component(BaseServerCA)
+    dns = core.Component(BaseServerDNS)
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 015050a..7a6bdae 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -40,15 +40,9 @@ try:
 except ImportError:
     _server_trust_ad_installed = False
 
-SYSRESTORE_DIR_PATH = paths.SYSRESTORE
+from .common import BaseServer
 
-VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c',
-                       'serialnumber', 'l', 'title', 'sn', 'givenname',
-                       'initials', 'generationqualifier', 'dc', 'mail',
-                       'uid', 'postaladdress', 'postalcode', 'postofficebox',
-                       'houseidentifier', 'e', 'street', 'pseudonym',
-                       'incorporationlocality', 'incorporationstate',
-                       'incorporationcountry', 'businesscategory']
+SYSRESTORE_DIR_PATH = paths.SYSRESTORE
 
 
 def validate_dm_password(password):
@@ -1134,279 +1128,14 @@ def uninstall(installer):
     sys.exit(rv)
 
 
-class ServerCA(common.Installable, core.Group, core.Composite):
-    description = "certificate system"
-
-    setup_ca = Knob(
-        bool, False,
-        initializable=False,
-        description="configure a dogtag CA",
-    )
-
-    setup_kra = Knob(
-        bool, False,
-        initializable=False,
-        description="configure a dogtag KRA",
-    )
-
-    external_ca = Knob(
-        bool, False,
-        description=("Generate a CSR for the IPA CA certificate to be signed "
-                     "by an external CA"),
-    )
-
-    external_ca_type = Knob(
-        {'generic', 'ms-cs'}, None,
-        description="Type of the external CA",
-    )
-
-    external_cert_files = Knob(
-        (list, str), None,
-        description=("File containing the IPA CA certificate and the external "
-                     "CA certificate chain (can be specified multiple times)"),
-        cli_name='external-cert-file',
-        cli_aliases=['external_cert_file', 'external_ca_file'],
-        cli_metavar='FILE',
-    )
-
-    @external_cert_files.validator
-    def external_cert_files(self, value):
-        if any(not os.path.isabs(path) for path in value):
-            raise ValueError("must use an absolute path")
-
-    no_pkinit = Knob(
-        bool, False,
-        description="disables pkinit setup steps",
-    )
-
-    dirsrv_cert_files = Knob(
-        (list, str), None,
-        description=("File containing the Directory Server SSL certificate "
-                     "and private key"),
-        cli_name='dirsrv-cert-file',
-        cli_aliases=['dirsrv_pkcs12'],
-        cli_metavar='FILE',
-    )
-
-    http_cert_files = Knob(
-        (list, str), None,
-        description=("File containing the Apache Server SSL certificate and "
-                     "private key"),
-        cli_name='http-cert-file',
-        cli_aliases=['http_pkcs12'],
-        cli_metavar='FILE',
-    )
-
-    pkinit_cert_files = Knob(
-        (list, str), None,
-        description=("File containing the Kerberos KDC SSL certificate and "
-                     "private key"),
-        cli_name='pkinit-cert-file',
-        cli_aliases=['pkinit_pkcs12'],
-        cli_metavar='FILE',
-    )
-
-    dirsrv_pin = Knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Directory Server private key",
-        cli_aliases=['dirsrv_pin'],
-        cli_metavar='PIN',
-    )
-
-    http_pin = Knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Apache Server private key",
-        cli_aliases=['http_pin'],
-        cli_metavar='PIN',
-    )
-
-    pkinit_pin = Knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Kerberos KDC private key",
-        cli_aliases=['pkinit_pin'],
-        cli_metavar='PIN',
-    )
-
-    dirsrv_cert_name = Knob(
-        str, None,
-        description="Name of the Directory Server SSL certificate to install",
-        cli_metavar='NAME',
-    )
-
-    http_cert_name = Knob(
-        str, None,
-        description="Name of the Apache Server SSL certificate to install",
-        cli_metavar='NAME',
-    )
-
-    pkinit_cert_name = Knob(
-        str, None,
-        description="Name of the Kerberos KDC SSL certificate to install",
-        cli_metavar='NAME',
-    )
-
-    ca_cert_files = Knob(
-        (list, str), None,
-        description=("File containing CA certificates for the service "
-                     "certificate files"),
-        cli_name='ca-cert-file',
-        cli_aliases=['root-ca-file'],
-        cli_metavar='FILE',
-    )
-
-    subject = Knob(
-        str, None,
-        description="The certificate subject base (default O=<realm-name>)",
-    )
-
-    @subject.validator
-    def subject(self, value):
-        v = unicode(value, 'utf-8')
-        if any(ord(c) < 0x20 for c in v):
-            raise ValueError("must not contain control characters")
-        if '&' in v:
-            raise ValueError("must not contain an ampersand (\"&\")")
-        try:
-            dn = DN(v)
-            for rdn in dn:
-                if rdn.attr.lower() not in VALID_SUBJECT_ATTRS:
-                    raise ValueError("invalid attribute: \"%s\"" % rdn.attr)
-        except ValueError, e:
-            raise ValueError("invalid subject base format: %s" % e)
-
-    ca_signing_algorithm = Knob(
-        {'SHA1withRSA', 'SHA256withRSA', 'SHA512withRSA'}, None,
-        description="Signing algorithm of the IPA CA certificate",
-    )
-
-
-class ServerDNS(common.Installable, core.Group, core.Composite):
-    description = "DNS"
-
-    setup_dns = Knob(
-        bool, False,
-        description="configure bind with our zone",
-    )
-
-    forwarders = Knob(
-        (list, 'ip'), None,
-        description=("Add a DNS forwarder. This option can be used multiple "
-                     "times"),
-        cli_name='forwarder',
-    )
-
-    no_forwarders = Knob(
-        bool, False,
-        description="Do not add any DNS forwarders, use root servers instead",
-    )
-
-    reverse_zones = Knob(
-        (list, str), [],
-        description=("The reverse DNS zone to use. This option can be used "
-                     "multiple times"),
-        cli_name='reverse-zone',
-    )
-
-    no_reverse = Knob(
-        bool, False,
-        description="Do not create reverse DNS zone",
-    )
-
-    no_dnssec_validation = Knob(
-        bool, False,
-        description="Disable DNSSEC validation",
-    )
-
-    dnssec_master = Knob(
-        bool, False,
-        initializable=False,
-        description="Setup server to be DNSSEC key master",
-    )
-
-    disable_dnssec_master = Knob(
-        bool, False,
-        initializable=False,
-        description="Disable the DNSSEC master on this server",
-    )
-
-    kasp_db_file = Knob(
-        str, None,
-        initializable=False,
-        description="Copy OpenDNSSEC metadata from the specified file (will "
-                    "not create a new kasp.db file)",
-    )
-
-    force = Knob(
-        bool, False,
-        initializable=False,
-        description="Force install",
-    )
-
-    zonemgr = Knob(
-        str, None,
-        description=("DNS zone manager e-mail address. Defaults to "
-                     "hostmaster@DOMAIN"),
-    )
-
-    @zonemgr.validator
-    def zonemgr(self, value):
-        # validate the value first
-        try:
-            # IDNA support requires unicode
-            encoding = getattr(sys.stdin, 'encoding', None)
-            if encoding is None:
-                encoding = 'utf-8'
-            value = value.decode(encoding)
-            bindinstance.validate_zonemgr_str(value)
-        except ValueError, e:
-            # FIXME we can do this in better way
-            # https://fedorahosted.org/freeipa/ticket/4804
-            # decode to proper stderr encoding
-            stderr_encoding = getattr(sys.stderr, 'encoding', None)
-            if stderr_encoding is None:
-                stderr_encoding = 'utf-8'
-            error = unicode(e).encode(stderr_encoding)
-            raise ValueError(error)
-
-    no_host_dns = Knob(
-        bool, False,
-        description="Do not use DNS for hostname lookup during installation",
-    )
-
-    no_dns_sshfp = Knob(
-        bool, False,
-        description="Do not automatically create DNS SSHFP records",
-    )
-
-
-class Server(common.Installable, common.Interactive, core.Composite):
-    realm_name = Knob(
-        str, None,
-        description="realm name",
-        cli_name='realm',
-        cli_short_name='r',
-    )
-
-    domain_name = Knob(
-        str, None,
-        description="domain name",
-        cli_name='domain',
-        cli_short_name='n',
-    )
-
-    @domain_name.validator
-    def domain_name(self, value):
-        validate_domain_name(value)
+class Server(BaseServer):
+    realm_name = Knob(BaseServer.realm_name)
+    domain_name = Knob(BaseServer.domain_name)
 
     dm_password = Knob(
-        str, None,
-        sensitive=True,
+        BaseServer.dm_password,
         description="Directory Manager password",
         cli_name='ds-password',
-        cli_short_name='p',
     )
 
     @dm_password.validator
@@ -1422,8 +1151,7 @@ class Server(common.Installable, common.Interactive, core.Composite):
     )
 
     admin_password = Knob(
-        str, None,
-        sensitive=True,
+        BaseServer.admin_password,
         description="admin user kerberos password",
         cli_short_name='a',
     )
@@ -1432,17 +1160,6 @@ class Server(common.Installable, common.Interactive, core.Composite):
     def admin_password(self, value):
         validate_admin_password(value)
 
-    mkhomedir = Knob(
-        bool, False,
-        description="create home directories for users on their first login",
-    )
-
-    host_name = Knob(
-        str, None,
-        description="fully qualified name of server",
-        cli_name='hostname',
-    )
-
     domainlevel = Knob(
         int, constants.MAX_DOMAIN_LEVEL,
         description="IPA domain level",
@@ -1461,17 +1178,20 @@ class Server(common.Installable, common.Interactive, core.Composite):
                 "Domain Level cannot be higher than {0}".format(
                     constants.MAX_DOMAIN_LEVEL))
 
+    mkhomedir = Knob(BaseServer.mkhomedir)
+    host_name = Knob(BaseServer.host_name)
+
     ip_addresses = Knob(
-        (list, 'ip-local'), None,
+        BaseServer.ip_addresses,
         description=("Master Server IP Address. This option can be used "
                      "multiple times"),
-        cli_name='ip-address',
     )
 
-    no_ntp = Knob(
-        bool, False,
-        description="do not configure ntp",
-    )
+    no_host_dns = Knob(BaseServer.no_host_dns)
+    setup_ca = None
+    setup_kra = None
+    setup_dns = Knob(BaseServer.setup_dns)
+    no_ntp = Knob(BaseServer.no_ntp)
 
     idstart = Knob(
         int, random.randint(1, 10000) * 200000,
@@ -1494,25 +1214,14 @@ class Server(common.Installable, common.Interactive, core.Composite):
         cli_name='no_hbac_allow',
     )
 
-    no_ui_redirect = Knob(
-        bool, False,
-        description="Do not automatically redirect to the Web UI",
-    )
-
-    ssh_trust_dns = Knob(
-        bool, False,
-        description="configure OpenSSH client to trust DNS SSHFP records",
-    )
-
-    no_ssh = Knob(
-        bool, False,
-        description="do not configure OpenSSH client",
-    )
+    # ca
+    skip_schema_check = None
 
-    no_sshd = Knob(
-        bool, False,
-        description="do not configure OpenSSH server",
-    )
+    # dns
+    dnssec_master = None
+    disable_dnssec_master = None
+    kasp_db_file = None
+    force = None
 
     def __init__(self, **kwargs):
         super(Server, self).__init__(**kwargs)
@@ -1530,125 +1239,6 @@ class Server(common.Installable, common.Interactive, core.Composite):
         self._external_ca_file = None
         self._ca_cert = None
 
-        #pylint: disable=no-member
-
-        if not self.dns.setup_dns:
-            if self.dns.forwarders:
-                raise RuntimeError(
-                    "You cannot specify a --forwarder option without the "
-                    "--setup-dns option")
-            if self.dns.no_forwarders:
-                raise RuntimeError(
-                    "You cannot specify a --no-forwarders option without the "
-                    "--setup-dns option")
-            if self.dns.reverse_zones:
-                raise RuntimeError(
-                    "You cannot specify a --reverse-zone option without the "
-                    "--setup-dns option")
-            if self.dns.no_reverse:
-                raise RuntimeError(
-                    "You cannot specify a --no-reverse option without the "
-                    "--setup-dns option")
-            if self.dns.no_dnssec_validation:
-                raise RuntimeError(
-                    "You cannot specify a --no-dnssec-validation option "
-                    "without the --setup-dns option")
-        elif self.dns.forwarders and self.dns.no_forwarders:
-            raise RuntimeError(
-                "You cannot specify a --forwarder option together with "
-                "--no-forwarders")
-        elif self.dns.reverse_zones and self.dns.no_reverse:
-            raise RuntimeError(
-                "You cannot specify a --reverse-zone option together with "
-                "--no-reverse")
-
-        if self.uninstalling:
-            if (self.realm_name or self.admin_password or
-                    self.master_password):
-                raise RuntimeError(
-                    "In uninstall mode, -a, -r and -P options are not allowed")
-        elif not self.interactive:
-            if (not self.realm_name or not self.dm_password or
-                    not self.admin_password):
-                raise RuntimeError(
-                    "In unattended mode you need to provide at least -r, -p "
-                    "and -a options")
-            if self.dns.setup_dns:
-                if not self.dns.forwarders and not self.dns.no_forwarders:
-                    raise RuntimeError(
-                        "You must specify at least one --forwarder option or "
-                        "--no-forwarders option")
-
-        # If any of the key file options are selected, all are required.
-        cert_file_req = (self.ca.dirsrv_cert_files, self.ca.http_cert_files)
-        cert_file_opt = (self.ca.pkinit_cert_files,)
-        if any(cert_file_req + cert_file_opt) and not all(cert_file_req):
-            raise RuntimeError(
-                "--dirsrv-cert-file and --http-cert-file are required if any "
-                "key file options are used.")
-
-        if not self.interactive:
-            if self.ca.dirsrv_cert_files and self.ca.dirsrv_pin is None:
-                raise RuntimeError(
-                    "You must specify --dirsrv-pin with --dirsrv-cert-file")
-            if self.ca.http_cert_files and self.ca.http_pin is None:
-                raise RuntimeError(
-                    "You must specify --http-pin with --http-cert-file")
-            if self.ca.pkinit_cert_files and self.ca.pkinit_pin is None:
-                raise RuntimeError(
-                    "You must specify --pkinit-pin with --pkinit-cert-file")
-
-        if self.ca.external_cert_files and self.ca.dirsrv_cert_files:
-            raise RuntimeError(
-                "Service certificate file options cannot be used with the "
-                "external CA options.")
-
-        if self.ca.external_ca_type and not self.ca.external_ca:
-            raise RuntimeError(
-                "You cannot specify --external-ca-type without --external-ca")
-
-        if self.idmax < self.idstart:
-            raise RuntimeError(
-                "idmax (%s) cannot be smaller than idstart (%s)" %
-                (self.idmax, self.idstart))
-
-        # Automatically disable pkinit w/ dogtag until that is supported
-        self.ca.no_pkinit = True
-
-        self.setup_ca = self.ca.setup_ca
-        self.setup_kra = self.ca.setup_kra
-        self.external_ca = self.ca.external_ca
-        self.external_ca_type = self.ca.external_ca_type
-        self.external_cert_files = self.ca.external_cert_files
-        self.no_pkinit = self.ca.no_pkinit
-        self.dirsrv_cert_files = self.ca.dirsrv_cert_files
-        self.http_cert_files = self.ca.http_cert_files
-        self.pkinit_cert_files = self.ca.pkinit_cert_files
-        self.dirsrv_pin = self.ca.dirsrv_pin
-        self.http_pin = self.ca.http_pin
-        self.pkinit_pin = self.ca.pkinit_pin
-        self.dirsrv_cert_name = self.ca.dirsrv_cert_name
-        self.http_cert_name = self.ca.http_cert_name
-        self.pkinit_cert_name = self.ca.pkinit_cert_name
-        self.ca_cert_files = self.ca.ca_cert_files
-        self.subject = self.ca.subject
-        self.ca_signing_algorithm = self.ca.ca_signing_algorithm
-        self.setup_dns = self.dns.setup_dns
-        self.forwarders = self.dns.forwarders
-        self.no_forwarders = self.dns.no_forwarders
-        self.reverse_zones = self.dns.reverse_zones
-        self.no_reverse = self.dns.no_reverse
-        self.no_dnssec_validation = self.dns.no_dnssec_validation
-        self.dnssec_master = self.dns.dnssec_master
-        self.disable_dnssec_master = self.dns.disable_dnssec_master
-        self.kasp_db_file = self.dns.kasp_db_file
-        self.force = self.dns.force
-        self.zonemgr = self.dns.zonemgr
-        self.no_host_dns = self.dns.no_host_dns
-        self.no_dns_sshfp = self.dns.no_dns_sshfp
-
-        self.unattended = not self.interactive
-
     @step()
     def main(self):
         install_check(self)
@@ -1660,6 +1250,3 @@ class Server(common.Installable, common.Interactive, core.Composite):
         uninstall_check(self)
         yield
         uninstall(self)
-
-    ca = core.Component(ServerCA)
-    dns = core.Component(ServerDNS)
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index a0ae534..67d2a03 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -31,6 +31,8 @@ from ipaserver.install.installutils import create_replica_config
 from ipaserver.install.replication import (
     ReplicationManager, replica_conn_check)
 
+from .common import BaseServer
+
 DIRMAN_DN = DN(('cn', 'directory manager'))
 
 
@@ -640,175 +642,84 @@ def install(installer):
     remove_replica_info_dir(installer)
 
 
-class ReplicaCA(common.Installable, core.Group, core.Composite):
-    description = "certificate system"
-
-    no_pkinit = Knob(
-        bool, False,
-        description="disables pkinit setup steps",
-    )
-
-    skip_schema_check = Knob(
-        bool, False,
-        description="skip check for updated CA DS schema on the remote master",
-    )
-
-
-class ReplicaDNS(common.Installable, core.Group, core.Composite):
-    description = "DNS"
-
-    setup_dns = Knob(
-        bool, False,
-        description="configure bind with our zone",
-    )
-
-    forwarders = Knob(
-        (list, 'ip'), None,
-        description=("Add a DNS forwarder. This option can be used multiple "
-                     "times"),
-        cli_name='forwarder',
-    )
-
-    no_forwarders = Knob(
-        bool, False,
-        description="Do not add any DNS forwarders, use root servers instead",
-    )
-
-    reverse_zones = Knob(
-        (list, str), [],
-        description=("The reverse DNS zone to use. This option can be used "
-                     "multiple times"),
-        cli_name='reverse-zone',
-    )
-
-    no_reverse = Knob(
-        bool, False,
-        description="Do not create new reverse DNS zone",
-    )
-
-    no_dnssec_validation = Knob(
-        bool, False,
-        description="Disable DNSSEC validation",
-    )
-
-    dnssec_master = Knob(
-        bool, False,
-        initializable=False,
-        description="Setup server to be DNSSEC key master",
-    )
-
-    disable_dnssec_master = Knob(
-        bool, False,
-        initializable=False,
-        description="Disable the DNSSEC master on this server",
-    )
-
-    force = Knob(
-        bool, False,
-        initializable=False,
-        description="Force install",
-    )
-
-    kasp_db_file = Knob(
-        str, None,
-        initializable=False,
-        description="Copy OpenDNSSEC metadata from the specified file (will "
-                    "not create a new kasp.db file)",
-    )
-
-    no_host_dns = Knob(
-        bool, False,
-        description="Do not use DNS for hostname lookup during installation",
-    )
-
-    no_dns_sshfp = Knob(
-        bool, False,
-        description="do not automatically create DNS SSHFP records",
-    )
-
-
-class Replica(common.Installable, common.Interactive, core.Composite):
+class Replica(BaseServer):
     replica_file = Knob(
         str, None,
         description="a file generated by ipa-replica-prepare",
     )
 
-    setup_ca = Knob(
-        bool, False,
-        initializable=False,
-        description="configure a dogtag CA",
-    )
+    realm_name = None
+    domain_name = None
 
-    setup_kra = Knob(
-        bool, False,
-        initializable=False,
-        description="configure a dogtag KRA",
-    )
+    setup_ca = Knob(BaseServer.setup_ca)
+    setup_kra = Knob(BaseServer.setup_kra)
+    setup_dns = Knob(BaseServer.setup_dns)
 
     ip_addresses = Knob(
-        (list, 'ip-local'), None,
+        BaseServer.ip_addresses,
         description=("Replica server IP Address. This option can be used "
                      "multiple times"),
-        cli_name='ip-address',
     )
 
-    password = Knob(
-        str, None,
-        sensitive=True,
+    dm_password = Knob(
+        BaseServer.dm_password,
         description="Directory Manager (existing master) password",
-        cli_short_name='p',
+        cli_name='password',
+        cli_metavar='PASSWORD',
     )
 
     admin_password = Knob(
-        str, None,
-        sensitive=True,
+        BaseServer.admin_password,
         description="Admin user Kerberos password used for connection check",
         cli_short_name='w',
     )
 
-    mkhomedir = Knob(
-        bool, False,
-        description="create home directories for users on their first login",
-    )
-
-    no_ntp = Knob(
-        bool, False,
-        description="do not configure ntp",
-    )
-
-    no_ui_redirect = Knob(
-        bool, False,
-        description="Do not automatically redirect to the Web UI",
-    )
-
-    ssh_trust_dns = Knob(
-        bool, False,
-        description="configure OpenSSH client to trust DNS SSHFP records",
-    )
-
-    no_ssh = Knob(
-        bool, False,
-        description="do not configure OpenSSH client",
-    )
-
-    no_sshd = Knob(
-        bool, False,
-        description="do not configure OpenSSH server",
-    )
+    mkhomedir = Knob(BaseServer.mkhomedir)
+    host_name = None
+    no_host_dns = Knob(BaseServer.no_host_dns)
+    no_ntp = Knob(BaseServer.no_ntp)
+    no_pkinit = Knob(BaseServer.no_pkinit)
+    no_ui_redirect = Knob(BaseServer.no_ui_redirect)
+    ssh_trust_dns = Knob(BaseServer.ssh_trust_dns)
+    no_ssh = Knob(BaseServer.no_ssh)
+    no_sshd = Knob(BaseServer.no_sshd)
+    no_dns_sshfp = Knob(BaseServer.no_dns_sshfp)
 
     skip_conncheck = Knob(
         bool, False,
         description="skip connection check to remote master",
     )
 
+    # ca
+    external_ca = None
+    external_ca_type = None
+    external_cert_files = None
+    dirsrv_cert_files = None
+    http_cert_files = None
+    pkinit_cert_files = None
+    dirsrv_pin = None
+    http_pin = None
+    pkinit_pin = None
+    dirsrv_cert_name = None
+    http_cert_name = None
+    pkinit_cert_name = None
+    ca_cert_files = None
+    subject = None
+    ca_signing_algorithm = None
+
+    # dns
+    dnssec_master = None
+    disable_dnssec_master = None
+    kasp_db_file = None
+    force = None
+    zonemgr = None
+
     def __init__(self, **kwargs):
         super(Replica, self).__init__(**kwargs)
 
         self._top_dir = None
         self._config = None
 
-        #pylint: disable=no-member
-
         if self.replica_file is None:
             raise RuntimeError(
                 "you must provide a file generated by ipa-replica-prepare")
@@ -816,69 +727,10 @@ class Replica(common.Installable, common.Interactive, core.Composite):
             raise RuntimeError(
                 "Replica file %s does not exist" % self.replica_file)
 
-        if not self.dns.setup_dns:
-            if self.dns.forwarders:
-                raise RuntimeError(
-                    "You cannot specify a --forwarder option without the "
-                    "--setup-dns option")
-            if self.dns.no_forwarders:
-                raise RuntimeError(
-                    "You cannot specify a --no-forwarders option without the "
-                    "--setup-dns option")
-            if self.dns.reverse_zones:
-                raise RuntimeError(
-                    "You cannot specify a --reverse-zone option without the "
-                    "--setup-dns option")
-            if self.dns.no_reverse:
-                raise RuntimeError(
-                    "You cannot specify a --no-reverse option without the "
-                    "--setup-dns option")
-            if self.dns.no_dnssec_validation:
-                raise RuntimeError(
-                    "You cannot specify a --no-dnssec-validation option "
-                    "without the --setup-dns option")
-        elif self.dns.forwarders and self.dns.no_forwarders:
-            raise RuntimeError(
-                "You cannot specify a --forwarder option together with "
-                "--no-forwarders")
-        elif not self.dns.forwarders and not self.dns.no_forwarders:
-            raise RuntimeError(
-                "You must specify at least one --forwarder option or "
-                "--no-forwarders option")
-        elif self.dns.reverse_zones and self.dns.no_reverse:
-            raise RuntimeError(
-                "You cannot specify a --reverse-zone option together with "
-                "--no-reverse")
-
-        # Automatically disable pkinit w/ dogtag until that is supported
-        self.ca.no_pkinit = True
-
-        self.external_ca = False
-        self.external_cert_files = None
-        self.no_pkinit = self.ca.no_pkinit
-        self.skip_schema_check = self.ca.skip_schema_check
-
-        self.setup_dns = self.dns.setup_dns
-        self.forwarders = self.dns.forwarders
-        self.no_forwarders = self.dns.no_forwarders
-        self.reverse_zones = self.dns.reverse_zones
-        self.no_reverse = self.dns.no_reverse
-        self.no_dnssec_validation = self.dns.no_dnssec_validation
-        self.dnssec_master = self.dns.dnssec_master
-        self.disable_dnssec_master = self.dns.disable_dnssec_master
-        self.kasp_db_file = self.dns.kasp_db_file
-        self.force = self.dns.force
-        self.zonemgr = None
-        self.no_host_dns = self.dns.no_host_dns
-        self.no_dns_sshfp = self.dns.no_dns_sshfp
-
-        self.unattended = not self.interactive
+        self.password = self.dm_password
 
     @step()
     def main(self):
         install_check(self)
         yield
         install(self)
-
-    ca = core.Component(ReplicaCA)
-    dns = core.Component(ReplicaDNS)
-- 
2.4.3

From 621286083a01b07d42d36e778347ce9104648d47 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 6 Aug 2015 08:16:45 +0200
Subject: [PATCH 3/3] install: Move unattended option to the general help
 section

https://fedorahosted.org/freeipa/ticket/4517
---
 ipapython/install/cli.py | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
index 5ee445d..63a2ee6 100644
--- a/ipapython/install/cli.py
+++ b/ipapython/install/cli.py
@@ -78,12 +78,22 @@ class ConfigureTool(admintool.AdminTool):
 
     @classmethod
     def add_options(cls, parser):
+        transformed_cls = cls._transform(cls.configurable_class)
+
+        if issubclass(transformed_cls, common.Interactive):
+            parser.add_option(
+                '-U', '--unattended',
+                dest='unattended',
+                default=False,
+                action='store_true',
+                help="unattended (un)installation never prompts the user",
+            )
+
         basic_group = optparse.OptionGroup(parser, "basic options")
 
         groups = collections.OrderedDict()
         groups[None] = basic_group
 
-        transformed_cls = cls._transform(cls.configurable_class)
         for owner_cls, name in transformed_cls.knobs():
             knob_cls = getattr(owner_cls, name)
             if cls.positional_arguments and name in cls.positional_arguments:
@@ -134,15 +144,6 @@ class ConfigureTool(admintool.AdminTool):
                     **kwargs
                 )
 
-        if issubclass(transformed_cls, common.Interactive):
-            basic_group.add_option(
-                '-U', '--unattended',
-                dest='unattended',
-                default=False,
-                action='store_true',
-                help="unattended (un)installation never prompts the user",
-            )
-
         for group, opt_group in groups.iteritems():
             parser.add_option_group(opt_group)
 
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to