URL: https://github.com/freeipa/freeipa/pull/550
Author: HonzaCholasta
 Title: #550: install: fix help
Action: opened

PR body:
"""
This PR fixes the known issue in the installer refactoring PR #232 and 
concludes https://pagure.io/freeipa/issue/6392.

**server install: remove duplicate -w option**

Remove duplicate -w alias of --admin-password in ipa-server-install and
ipa-replica-install.

**install: add missing space in realm_name description**

**server install: remove duplicate knob definitions**

Remove duplicate definitions of knobs already defined in client install.

**client install: split off SSSD options into a separate class**

Split off SSSD knob definitions from the ClientInstallInterface class into
a new SSSDInstallInterface class.

**install CLI: remove magic option groups**

Do not automatically create the "basic options" and "uninstall options"
option groups in the CLI code.

**install: re-introduce option groups**

Re-introduce option groups in ipa-client-install, ipa-server-install and
ipa-replica-install.

https://pagure.io/freeipa/issue/6392
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/550/head:pr550
git checkout pr550
From c0ceebdd94129c423bc60f91194e2bf61bd98eff Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 7 Mar 2017 13:19:51 +0000
Subject: [PATCH 1/6] server install: remove duplicate -w option

Remove duplicate -w alias of --admin-password in ipa-server-install and
ipa-replica-install.

https://pagure.io/freeipa/issue/6392
---
 ipaserver/install/server/__init__.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
index 65dfa21..ab14bae 100644
--- a/ipaserver/install/server/__init__.py
+++ b/ipaserver/install/server/__init__.py
@@ -118,6 +118,10 @@ class ServerInstallInterface(client.ClientInstallInterface,
     )
     principal = replica_install_only(principal)
 
+    admin_password = extend_knob(
+        client.ClientInstallInterface.admin_password,
+    )
+
     master_password = knob(
         str, None,
         sensitive=True,

From 571b6c2d4ce3b544aa9917c789eabeef217914ba Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 7 Mar 2017 13:20:38 +0000
Subject: [PATCH 2/6] install: add missing space in realm_name description

https://pagure.io/freeipa/issue/6392
---
 ipalib/install/service.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipalib/install/service.py b/ipalib/install/service.py
index 73b8fd8..84539ad 100644
--- a/ipalib/install/service.py
+++ b/ipalib/install/service.py
@@ -122,7 +122,7 @@ def domain_name(self, value):
 
     realm_name = knob(
         str, None,
-        description="Kerberos realm name of the IPA deployment (typically"
+        description="Kerberos realm name of the IPA deployment (typically "
                     "an upper-cased name of the primary DNS domain)",
         cli_names='--realm',
     )

From c7f1dd2a4f25111b6ee87a3b2dde9cd018f7bc96 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 7 Mar 2017 14:06:11 +0000
Subject: [PATCH 3/6] server install: remove duplicate knob definitions

Remove duplicate definitions of knobs already defined in client install.

https://pagure.io/freeipa/issue/6392
---
 ipaserver/install/server/__init__.py | 24 ------------------------
 1 file changed, 24 deletions(-)

diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
index ab14bae..12e0c87 100644
--- a/ipaserver/install/server/__init__.py
+++ b/ipaserver/install/server/__init__.py
@@ -221,30 +221,6 @@ def idmax(self):
     )
     no_ui_redirect = enroll_only(no_ui_redirect)
 
-    ssh_trust_dns = knob(
-        None,
-        description="configure OpenSSH client to trust DNS SSHFP records",
-    )
-    ssh_trust_dns = enroll_only(ssh_trust_dns)
-
-    no_ssh = knob(
-        None,
-        description="do not configure OpenSSH client",
-    )
-    no_ssh = enroll_only(no_ssh)
-
-    no_sshd = knob(
-        None,
-        description="do not configure OpenSSH server",
-    )
-    no_sshd = enroll_only(no_sshd)
-
-    no_dns_sshfp = knob(
-        None,
-        description="Do not automatically create DNS SSHFP records",
-    )
-    no_dns_sshfp = enroll_only(no_dns_sshfp)
-
     dirsrv_config_file = knob(
         str, None,
         description="The path to LDIF file that will be used to modify "

From af0dbb511afbc4c77221d14ae1a2a929c388e7ce Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Mar 2017 08:01:41 +0000
Subject: [PATCH 4/6] client install: split off SSSD options into a separate
 class

Split off SSSD knob definitions from the ClientInstallInterface class into
a new SSSDInstallInterface class.

https://pagure.io/freeipa/issue/6392
---
 ipaclient/install/client.py | 44 +++-----------------------------------
 ipaclient/install/sssd.py   | 52 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 41 deletions(-)
 create mode 100644 ipaclient/install/sssd.py

diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
index 774eaaf..b251223 100644
--- a/ipaclient/install/client.py
+++ b/ipaclient/install/client.py
@@ -63,7 +63,7 @@
 )
 from ipapython.ssh import SSHPublicKey
 
-from . import automount, ipadiscovery, ntpconf
+from . import automount, ipadiscovery, ntpconf, sssd
 from .ipachangeconf import IPAChangeConf
 
 NoneType = type(None)
@@ -3356,7 +3356,8 @@ def init(installer):
 
 
 class ClientInstallInterface(hostname_.HostNameInstallInterface,
-                             service.ServiceAdminInstallInterface):
+                             service.ServiceAdminInstallInterface,
+                             sssd.SSSDInstallInterface):
     """
     Interface of the client installer
 
@@ -3367,12 +3368,6 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
     * ipa-replica-install
     """
 
-    fixed_primary = knob(
-        None,
-        description="Configure sssd to use fixed server as primary IPA server",
-    )
-    fixed_primary = enroll_only(fixed_primary)
-
     principal = knob(
         bases=service.ServiceAdminInstallInterface.principal,
         description="principal to use to join the IPA realm",
@@ -3487,32 +3482,6 @@ def kinit_attempts(self, value):
     )
     request_cert = prepare_only(request_cert)
 
-    permit = knob(
-        None,
-        description="disable access rules by default, permit all access.",
-    )
-    permit = enroll_only(permit)
-
-    enable_dns_updates = knob(
-        None,
-        description="Configures the machine to attempt dns updates when the "
-                    "ip address changes.",
-    )
-    enable_dns_updates = enroll_only(enable_dns_updates)
-
-    no_krb5_offline_passwords = knob(
-        None,
-        description="Configure SSSD not to store user password when the "
-                    "server is offline",
-    )
-    no_krb5_offline_passwords = enroll_only(no_krb5_offline_passwords)
-
-    preserve_sssd = knob(
-        None,
-        description="Preserve old SSSD configuration if possible",
-    )
-    preserve_sssd = enroll_only(preserve_sssd)
-
     def __init__(self, **kwargs):
         super(ClientInstallInterface, self).__init__(**kwargs)
 
@@ -3605,13 +3574,6 @@ def prompt_password(self):
                     "example: '/usr/lib/firefox')",
     )
 
-    no_sssd = knob(
-        None,
-        description="Do not configure the client to use SSSD for "
-                    "authentication",
-        cli_names=[None, '-S'],
-    )
-
     def __init__(self, **kwargs):
         super(ClientInstall, self).__init__(**kwargs)
 
diff --git a/ipaclient/install/sssd.py b/ipaclient/install/sssd.py
new file mode 100644
index 0000000..5e163e3
--- /dev/null
+++ b/ipaclient/install/sssd.py
@@ -0,0 +1,52 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+from ipalib.install import service
+from ipalib.install.service import enroll_only
+from ipapython.install.core import group, knob
+
+
+@group
+class SSSDInstallInterface(service.ServiceInstallInterface):
+    description = "SSSD"
+
+    fixed_primary = knob(
+        None,
+        description="Configure sssd to use fixed server as primary IPA server",
+    )
+    fixed_primary = enroll_only(fixed_primary)
+
+    permit = knob(
+        None,
+        description="disable access rules by default, permit all access.",
+    )
+    permit = enroll_only(permit)
+
+    enable_dns_updates = knob(
+        None,
+        description="Configures the machine to attempt dns updates when the "
+                    "ip address changes.",
+    )
+    enable_dns_updates = enroll_only(enable_dns_updates)
+
+    no_krb5_offline_passwords = knob(
+        None,
+        description="Configure SSSD not to store user password when the "
+                    "server is offline",
+    )
+    no_krb5_offline_passwords = enroll_only(no_krb5_offline_passwords)
+
+    preserve_sssd = knob(
+        None,
+        description="Preserve old SSSD configuration if possible",
+    )
+    preserve_sssd = enroll_only(preserve_sssd)
+
+    no_sssd = knob(
+        None,
+        description="Do not configure the client to use SSSD for "
+                    "authentication",
+        cli_names=[None, '-S'],
+    )
+    no_sssd = enroll_only(no_sssd)

From dc4a256f2b44a635beb89ce003ed8704850cb829 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Mar 2017 08:01:50 +0000
Subject: [PATCH 5/6] install CLI: remove magic option groups

Do not automatically create the "basic options" and "uninstall options"
option groups in the CLI code.

https://pagure.io/freeipa/issue/6392
---
 ipapython/install/cli.py | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py
index 741bf9d..e9e15b6 100644
--- a/ipapython/install/cli.py
+++ b/ipapython/install/cli.py
@@ -136,22 +136,21 @@ def add_options(cls, parser, positional=False):
                 help="unattended (un)installation never prompts the user",
             )
 
-        basic_group = optparse.OptionGroup(parser, "basic options")
-
         groups = collections.OrderedDict()
-        groups[None] = basic_group
+        groups[None] = parser
 
         for owner_cls, name in transformed_cls.knobs():
             knob_cls = getattr(owner_cls, name)
             if knob_cls.is_cli_positional() is not positional:
                 continue
 
-            group_cls = owner_cls.group()
+            group_cls = knob_cls.group()
             try:
                 opt_group = groups[group_cls]
             except KeyError:
                 opt_group = groups[group_cls] = optparse.OptionGroup(
-                    parser, "{0} options".format(group_cls.description))
+                        parser, "{0} options".format(group_cls.description))
+                parser.add_option_group(opt_group)
 
             knob_type = knob_cls.type
             if issubclass(knob_type, list):
@@ -237,9 +236,6 @@ def add_options(cls, parser, positional=False):
                     **kwargs
                 )
 
-        for opt_group in groups.values():
-            parser.add_option_group(opt_group)
-
         super(ConfigureTool, cls).add_options(parser,
                                               debug_option=cls.debug_option)
 
@@ -353,8 +349,7 @@ def add_options(cls, parser, positional=False):
         super(InstallTool, cls).add_options(parser, positional)
 
         if cls.uninstall_kwargs is not None:
-            uninstall_group = optparse.OptionGroup(parser, "uninstall options")
-            uninstall_group.add_option(
+            parser.add_option(
                 '--uninstall',
                 dest='uninstall',
                 default=False,
@@ -362,7 +357,6 @@ def add_options(cls, parser, positional=False):
                 help=("uninstall an existing installation. The uninstall can "
                       "be run with --unattended option"),
             )
-            parser.add_option_group(uninstall_group)
 
     @classmethod
     def get_command_class(cls, options, args):

From 05b27bb56d07eb648d0b070477fff85ca7d8696a Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Mar 2017 08:03:13 +0000
Subject: [PATCH 6/6] install: re-introduce option groups

Re-introduce option groups in ipa-client-install, ipa-server-install and
ipa-replica-install.

https://pagure.io/freeipa/issue/6392
---
 ipaclient/install/automount.py           |   6 +-
 ipaclient/install/client.py              |  17 +-
 ipaclient/install/ipa_client_install.py  |   9 +-
 ipalib/install/service.py                |   4 +-
 ipapython/install/core.py                |  54 +++++-
 ipaserver/install/adtrust.py             |   6 +-
 ipaserver/install/ca.py                  |  21 +--
 ipaserver/install/dns.py                 |   4 +-
 ipaserver/install/ipa_replica_install.py |  22 +--
 ipaserver/install/ipa_server_install.py  |  20 +--
 ipaserver/install/kra.py                 |   3 +
 ipaserver/install/server/__init__.py     | 277 ++++++++++++++++---------------
 12 files changed, 250 insertions(+), 193 deletions(-)

diff --git a/ipaclient/install/automount.py b/ipaclient/install/automount.py
index bb72045..23e9cfe 100644
--- a/ipaclient/install/automount.py
+++ b/ipaclient/install/automount.py
@@ -8,9 +8,10 @@
 
 from ipalib.install import service
 from ipalib.install.service import enroll_only
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob
 
 
+@group
 class AutomountInstallInterface(service.ServiceInstallInterface):
     """
     Interface of the automount installer
@@ -19,9 +20,10 @@ class AutomountInstallInterface(service.ServiceInstallInterface):
     * ipa-client-install
     * ipa-client-automount
     """
+    description = "Automount"
 
     automount_location = knob(
-        str, 'default',
+        str, None,
         description="Automount location",
     )
     automount_location = enroll_only(automount_location)
diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
index b251223..1f5ba16 100644
--- a/ipaclient/install/client.py
+++ b/ipaclient/install/client.py
@@ -50,7 +50,7 @@
 from ipapython.admintool import ScriptError
 from ipapython.dn import DN
 from ipapython.install import typing
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob, extend_knob
 from ipapython.install.common import step
 from ipapython.ipa_log_manager import log_mgr, root_logger
 from ipapython.ipautil import (
@@ -3355,6 +3355,7 @@ def init(installer):
     installer.sssd = not installer.no_sssd
 
 
+@group
 class ClientInstallInterface(hostname_.HostNameInstallInterface,
                              service.ServiceAdminInstallInterface,
                              sssd.SSSDInstallInterface):
@@ -3367,9 +3368,10 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
     * ipa-replica-prepare
     * ipa-replica-install
     """
+    description = "Client"
 
-    principal = knob(
-        bases=service.ServiceAdminInstallInterface.principal,
+    principal = extend_knob(
+        service.ServiceAdminInstallInterface.principal,
         description="principal to use to join the IPA realm",
     )
     principal = enroll_only(principal)
@@ -3518,8 +3520,8 @@ class ClientInstall(ClientInstallInterface,
     replica_file = None
     dm_password = None
 
-    ca_cert_files = knob(
-        bases=ClientInstallInterface.ca_cert_files,
+    ca_cert_files = extend_knob(
+        ClientInstallInterface.ca_cert_files,
     )
 
     @ca_cert_files.validator
@@ -3544,11 +3546,6 @@ def ca_cert_files(self, value):
     def prompt_password(self):
         return self.interactive
 
-    automount_location = knob(
-        bases=automount.AutomountInstallInterface.automount_location,
-        default=None,
-    )
-
     no_ac = knob(
         None,
         description="do not modify the nsswitch.conf and PAM configuration",
diff --git a/ipaclient/install/ipa_client_install.py b/ipaclient/install/ipa_client_install.py
index 4ac7cf5..da661e6 100644
--- a/ipaclient/install/ipa_client_install.py
+++ b/ipaclient/install/ipa_client_install.py
@@ -5,16 +5,17 @@
 from ipaclient.install import client
 from ipaplatform.paths import paths
 from ipapython.install import cli
-from ipapython.install.core import knob
+from ipapython.install.core import knob, extend_knob
 
 
 class StandaloneClientInstall(client.ClientInstall):
     no_host_dns = False
     no_wait_for_dns = False
 
-    principal = knob(
-        bases=client.ClientInstall.principal,
-        cli_names=list(client.ClientInstall.principal.cli_names) + ['-p'],
+    principal = client.ClientInstall.principal
+    principal = extend_knob(
+        principal,
+        cli_names=list(principal.cli_names) + ['-p'],
     )
 
     password = knob(
diff --git a/ipalib/install/service.py b/ipalib/install/service.py
index 84539ad..7d1045a 100644
--- a/ipalib/install/service.py
+++ b/ipalib/install/service.py
@@ -8,7 +8,7 @@
 
 from ipalib.util import validate_domain_name
 from ipapython.install import common, core, typing
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob
 
 
 def prepare_only(obj):
@@ -94,12 +94,14 @@ def installs_replica(cls):
     return _does(cls, 'replica_install')
 
 
+@group
 class ServiceInstallInterface(common.Installable,
                               common.Interactive,
                               core.Composite):
     """
     Interface common to all service installers
     """
+    description = "Basic"
 
     domain_name = knob(
         str, None,
diff --git a/ipapython/install/core.py b/ipapython/install/core.py
index 8289b16..7d22fd1 100644
--- a/ipapython/install/core.py
+++ b/ipapython/install/core.py
@@ -123,6 +123,10 @@ def validate(self, value):
         pass
 
     @classmethod
+    def group(cls):
+        return cls.__outer_class__.group()
+
+    @classmethod
     def is_cli_positional(cls):
         return all(n is not None and not n.startswith('-')
                    for n in cls.cli_names)
@@ -146,15 +150,19 @@ def validate(self, value):
         return cls
 
 
-def knob(type_=_missing, default=_missing, bases=_missing, sensitive=_missing,
-         deprecated=_missing, description=_missing, cli_names=_missing,
-         cli_deprecated_names=_missing, cli_metavar=_missing):
-    if type_ is None:
-        type_ = NoneType
+_type = type
+
+
+def _knob(type=_missing, default=_missing, bases=_missing, _order=_missing,
+          sensitive=_missing, deprecated=_missing, description=_missing,
+          group=_missing, cli_names=_missing, cli_deprecated_names=_missing,
+          cli_metavar=_missing):
+    if type is None:
+        type = NoneType
 
     if bases is _missing:
         bases = (KnobBase,)
-    elif isinstance(bases, type):
+    elif isinstance(bases, _type):
         bases = (bases,)
 
     if cli_names is None or isinstance(cli_names, str):
@@ -168,17 +176,20 @@ def knob(type_=_missing, default=_missing, bases=_missing, sensitive=_missing,
         cli_deprecated_names = tuple(cli_deprecated_names)
 
     class_dict = {}
-    class_dict['_order'] = next(_counter)
-    if type_ is not _missing:
-        class_dict['type'] = type_
+    if type is not _missing:
+        class_dict['type'] = type
     if default is not _missing:
         class_dict['default'] = default
+    if _order is not _missing:
+        class_dict['_order'] = _order
     if sensitive is not _missing:
         class_dict['sensitive'] = sensitive
     if deprecated is not _missing:
         class_dict['deprecated'] = deprecated
     if description is not _missing:
         class_dict['description'] = description
+    if group is not _missing:
+        class_dict['group'] = group
     if cli_names is not _missing:
         class_dict['cli_names'] = cli_names
     if cli_deprecated_names is not _missing:
@@ -189,6 +200,31 @@ def knob(type_=_missing, default=_missing, bases=_missing, sensitive=_missing,
     return util.InnerClassMeta('Knob', bases, class_dict)
 
 
+def knob(type, default=_missing, **kwargs):
+    return _knob(
+        type, default,
+        _order=next(_counter),
+        **kwargs
+    )
+
+
+def extend_knob(base, default=_missing, bases=_missing, group=_missing,
+                **kwargs):
+    if bases is _missing:
+        bases = (base,)
+
+    if group is _missing:
+        group = staticmethod(base.group)
+
+    return _knob(
+        _missing, default,
+        bases=bases,
+        _order=_missing,
+        group=group,
+        **kwargs
+    )
+
+
 class Configurable(six.with_metaclass(abc.ABCMeta, object)):
     """
     Base class of all configurables.
diff --git a/ipaserver/install/adtrust.py b/ipaserver/install/adtrust.py
index b81c27c..b0037e0 100644
--- a/ipaserver/install/adtrust.py
+++ b/ipaserver/install/adtrust.py
@@ -15,11 +15,12 @@
 from ipalib.constants import DOMAIN_LEVEL_0
 from ipalib import errors
 from ipalib.install.service import ServiceAdminInstallInterface
+from ipalib.install.service import replica_install_only
 from ipaplatform.paths import paths
 from ipapython.admintool import ScriptError
 from ipapython import ipaldap, ipautil
 from ipapython.dn import DN
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob
 from ipapython.ipa_log_manager import root_logger
 from ipaserver.install import adtrustinstance
 from ipaserver.install import service
@@ -430,6 +431,7 @@ def install(standalone, options, fstore, api):
         add_new_adtrust_agents(api, options)
 
 
+@group
 class ADTrustInstallInterface(ServiceAdminInstallInterface):
     """
     Interface for the AD trust installer
@@ -439,6 +441,7 @@ class ADTrustInstallInterface(ServiceAdminInstallInterface):
     * ipa-replica-install
     * ipa-adtrust-install
     """
+    description = "AD trust"
 
     # the following knobs are provided on top of those specified for
     # admin credentials
@@ -451,6 +454,7 @@ class ADTrustInstallInterface(ServiceAdminInstallInterface):
         description="Add IPA masters to a list of hosts allowed to "
                     "serve information about users from trusted forests"
     )
+    add_agents = replica_install_only(add_agents)
     enable_compat = knob(
         None,
         description="Enable support for trusted domains for old clients"
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index 649c152..db3b744 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -18,7 +18,7 @@
 from ipalib.install.service import enroll_only, master_install_only, replica_install_only
 from ipaserver.install import sysupgrade
 from ipapython.install import typing
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob, extend_knob
 from ipaserver.install import (cainstance,
                                custodiainstance,
                                dsinstance,
@@ -367,6 +367,7 @@ class CASigningAlgorithm(enum.Enum):
     SHA_512_WITH_RSA = 'SHA512withRSA'
 
 
+@group
 class CAInstallInterface(dogtag.DogtagInstallInterface,
                          conncheck.ConnCheckInterface):
     """
@@ -378,22 +379,22 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
     * ipa-replica-install
     * ipa-ca-install
     """
+    description = "Certificate system"
 
-    principal = knob(
-        bases=conncheck.ConnCheckInterface.principal,
+    principal = conncheck.ConnCheckInterface.principal
+    principal = extend_knob(
+        principal,
         description="User allowed to manage replicas",
-        cli_names=(
-            list(conncheck.ConnCheckInterface.principal.cli_names) + ['-P']),
+        cli_names=list(principal.cli_names) + ['-P'],
     )
     principal = enroll_only(principal)
     principal = replica_install_only(principal)
 
-    admin_password = knob(
-        bases=conncheck.ConnCheckInterface.admin_password,
+    admin_password = conncheck.ConnCheckInterface.admin_password
+    admin_password = extend_knob(
+        admin_password,
         description="Admin user Kerberos password used for connection check",
-        cli_names=(
-            list(conncheck.ConnCheckInterface.admin_password.cli_names) +
-            ['-w']),
+        cli_names=list(admin_password.cli_names) + ['-w'],
     )
     admin_password = enroll_only(admin_password)
 
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index f718309..0dddf2a 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -32,7 +32,7 @@
 from ipapython.dn import DN
 from ipapython.dnsutil import check_zone_overlap
 from ipapython.install import typing
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob
 from ipapython.ipa_log_manager import root_logger
 from ipapython.admintool import ScriptError
 from ipapython.ipautil import user_input
@@ -414,6 +414,7 @@ class DNSForwardPolicy(enum.Enum):
     FIRST = 'first'
 
 
+@group
 class DNSInstallInterface(hostname.HostNameInstallInterface):
     """
     Interface of the DNS installer
@@ -424,6 +425,7 @@ class DNSInstallInterface(hostname.HostNameInstallInterface):
     * ipa-replica-install
     * ipa-dns-install
     """
+    description = "DNS"
 
     allow_zone_overlap = knob(
         None,
diff --git a/ipaserver/install/ipa_replica_install.py b/ipaserver/install/ipa_replica_install.py
index 39c7456..9d38bec 100644
--- a/ipaserver/install/ipa_replica_install.py
+++ b/ipaserver/install/ipa_replica_install.py
@@ -3,7 +3,7 @@
 #
 
 from ipapython.install import cli
-from ipapython.install.core import knob
+from ipapython.install.core import knob, extend_knob
 from ipaplatform.paths import paths
 from ipaserver.install.server import ServerReplicaInstall
 
@@ -19,9 +19,8 @@ class CompatServerReplicaInstall(ServerReplicaInstall):
     ca_file = None
     zonemgr = None
 
-    replica_file = knob(
-        # pylint: disable=no-member
-        bases=ServerReplicaInstall.replica_file,
+    replica_file = extend_knob(
+        ServerReplicaInstall.replica_file,  # pylint: disable=no-member
         cli_names='replica_file',
     )
 
@@ -52,17 +51,18 @@ def dm_password(self):
     def dm_password(self, value):
         self.__dm_password = value
 
-    ip_addresses = knob(
-        # pylint: disable=no-member
-        bases=ServerReplicaInstall.ip_addresses,
+    ip_addresses = extend_knob(
+        ServerReplicaInstall.ip_addresses,  # pylint: disable=no-member
         description="Replica server IP Address. This option can be used "
                     "multiple times",
     )
 
-    admin_password = knob(
-        # pylint: disable=no-member
-        bases=ServerReplicaInstall.admin_password,
-        cli_names=list(ServerReplicaInstall.admin_password.cli_names) + ['-w'],
+    admin_password = (
+        ServerReplicaInstall.admin_password     # pylint: disable=no-member
+    )
+    admin_password = extend_knob(
+        admin_password,
+        cli_names=list(admin_password.cli_names) + ['-w'],
     )
 
     @admin_password.default_getter
diff --git a/ipaserver/install/ipa_server_install.py b/ipaserver/install/ipa_server_install.py
index e708040..428e184 100644
--- a/ipaserver/install/ipa_server_install.py
+++ b/ipaserver/install/ipa_server_install.py
@@ -3,7 +3,7 @@
 #
 
 from ipapython.install import cli
-from ipapython.install.core import knob
+from ipapython.install.core import extend_knob
 from ipaplatform.paths import paths
 from ipaserver.install.server import ServerMasterInstall
 
@@ -15,22 +15,20 @@ class CompatServerMasterInstall(ServerMasterInstall):
     no_sudo = False
     request_cert = False
 
-    dm_password = knob(
-        # pylint: disable=no-member
-        bases=ServerMasterInstall.dm_password,
+    dm_password = extend_knob(
+        ServerMasterInstall.dm_password,    # pylint: disable=no-member
         cli_names=['--ds-password', '-p'],
     )
 
-    admin_password = knob(
+    admin_password = ServerMasterInstall.admin_password
+    admin_password = extend_knob(
+        admin_password,
         # pylint: disable=no-member
-        bases=ServerMasterInstall.admin_password,
-        cli_names=(list(ServerMasterInstall.admin_password.cli_names) +
-                   ['-a']),
+        cli_names=list(admin_password.cli_names) + ['-a'],
     )
 
-    ip_addresses = knob(
-        # pylint: disable=no-member
-        bases=ServerMasterInstall.ip_addresses,
+    ip_addresses = extend_knob(
+        ServerMasterInstall.ip_addresses,   # pylint: disable=no-member
         description="Master Server IP Address. This option can be used "
                     "multiple times",
     )
diff --git a/ipaserver/install/kra.py b/ipaserver/install/kra.py
index 5a7a6ef..17617ed 100644
--- a/ipaserver/install/kra.py
+++ b/ipaserver/install/kra.py
@@ -15,6 +15,7 @@
 from ipapython import certdb
 from ipapython import ipautil
 from ipapython.dn import DN
+from ipapython.install.core import group
 from ipaserver.install import custodiainstance
 from ipaserver.install import cainstance
 from ipaserver.install import krainstance
@@ -141,6 +142,7 @@ def uninstall(standalone):
         kra.uninstall()
 
 
+@group
 class KRAInstallInterface(dogtag.DogtagInstallInterface):
     """
     Interface of the KRA installer
@@ -151,3 +153,4 @@ class KRAInstallInterface(dogtag.DogtagInstallInterface):
     * ipa-replica-install
     * ipa-kra-install
     """
+    description = "KRA"
diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
index 12e0c87..d43df0d 100644
--- a/ipaserver/install/server/__init__.py
+++ b/ipaserver/install/server/__init__.py
@@ -14,6 +14,7 @@
 
 from ipaclient.install import client
 from ipalib import constants
+from ipalib.install import service
 from ipalib.install.service import (enroll_only,
                                     installs_master,
                                     installs_replica,
@@ -24,7 +25,7 @@
 from ipapython import ipautil
 from ipapython.dnsutil import check_zone_overlap
 from ipapython.install import typing
-from ipapython.install.core import knob
+from ipapython.install.core import group, knob, extend_knob
 from ipapython.install.common import step
 
 from .install import validate_admin_password, validate_dm_password
@@ -41,12 +42,120 @@
 from .. import adtrust, ca, conncheck, dns, kra
 
 
-class ServerInstallInterface(client.ClientInstallInterface,
+@group
+class ServerUninstallInterface(service.ServiceInstallInterface):
+    description = "Uninstall"
+
+    ignore_topology_disconnect = knob(
+        None,
+        description="do not check whether server uninstall disconnects the "
+                    "topology (domain level 1+)",
+    )
+    ignore_topology_disconnect = master_install_only(ignore_topology_disconnect)
+
+    ignore_last_of_role = knob(
+        None,
+        description="do not check whether server uninstall removes last "
+                    "CA/DNS server or DNSSec master (domain level 1+)",
+    )
+    ignore_last_of_role = master_install_only(ignore_last_of_role)
+
+
+@group
+class ServerCertificateInstallInterface(service.ServiceInstallInterface):
+    description = "SSL certificate"
+
+    dirsrv_cert_files = knob(
+        # pylint: disable=invalid-sequence-index
+        typing.List[str], None,
+        description=("File containing the Directory Server SSL certificate "
+                     "and private key"),
+        cli_names='--dirsrv-cert-file',
+        cli_deprecated_names='--dirsrv_pkcs12',
+        cli_metavar='FILE',
+    )
+    dirsrv_cert_files = prepare_only(dirsrv_cert_files)
+
+    http_cert_files = knob(
+        # pylint: disable=invalid-sequence-index
+        typing.List[str], None,
+        description=("File containing the Apache Server SSL certificate and "
+                     "private key"),
+        cli_names='--http-cert-file',
+        cli_deprecated_names='--http_pkcs12',
+        cli_metavar='FILE',
+    )
+    http_cert_files = prepare_only(http_cert_files)
+
+    pkinit_cert_files = knob(
+        # pylint: disable=invalid-sequence-index
+        typing.List[str], None,
+        description=("File containing the Kerberos KDC SSL certificate and "
+                     "private key"),
+        cli_names='--pkinit-cert-file',
+        cli_deprecated_names='--pkinit_pkcs12',
+        cli_metavar='FILE',
+    )
+    pkinit_cert_files = prepare_only(pkinit_cert_files)
+
+    dirsrv_pin = knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Directory Server private key",
+        cli_deprecated_names='--dirsrv_pin',
+        cli_metavar='PIN',
+    )
+    dirsrv_pin = prepare_only(dirsrv_pin)
+
+    http_pin = knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Apache Server private key",
+        cli_deprecated_names='--http_pin',
+        cli_metavar='PIN',
+    )
+    http_pin = prepare_only(http_pin)
+
+    pkinit_pin = knob(
+        str, None,
+        sensitive=True,
+        description="The password to unlock the Kerberos KDC private key",
+        cli_deprecated_names='--pkinit_pin',
+        cli_metavar='PIN',
+    )
+    pkinit_pin = prepare_only(pkinit_pin)
+
+    dirsrv_cert_name = knob(
+        str, None,
+        description="Name of the Directory Server SSL certificate to install",
+        cli_metavar='NAME',
+    )
+    dirsrv_cert_name = prepare_only(dirsrv_cert_name)
+
+    http_cert_name = knob(
+        str, None,
+        description="Name of the Apache Server SSL certificate to install",
+        cli_metavar='NAME',
+    )
+    http_cert_name = prepare_only(http_cert_name)
+
+    pkinit_cert_name = knob(
+        str, None,
+        description="Name of the Kerberos KDC SSL certificate to install",
+        cli_metavar='NAME',
+    )
+    pkinit_cert_name = prepare_only(pkinit_cert_name)
+
+
+@group
+class ServerInstallInterface(ServerCertificateInstallInterface,
+                             client.ClientInstallInterface,
                              ca.CAInstallInterface,
                              kra.KRAInstallInterface,
                              dns.DNSInstallInterface,
                              adtrust.ADTrustInstallInterface,
-                             conncheck.ConnCheckInterface):
+                             conncheck.ConnCheckInterface,
+                             ServerUninstallInterface):
     """
     Interface of server installers
 
@@ -55,6 +164,7 @@ class ServerInstallInterface(client.ClientInstallInterface,
     * ipa-replica-prepare
     * ipa-replica-install
     """
+    description = "Server"
 
     force_join = False
     kinit_attempts = 1
@@ -65,56 +175,57 @@ class ServerInstallInterface(client.ClientInstallInterface,
     enable_dns_updates = False
     no_krb5_offline_passwords = False
     preserve_sssd = False
+    no_sssd = False
 
-    domain_name = knob(
-        bases=client.ClientInstallInterface.domain_name,
+    domain_name = client.ClientInstallInterface.domain_name
+    domain_name = extend_knob(
+        domain_name,
         # pylint: disable=no-member
-        cli_names=(list(client.ClientInstallInterface.domain_name.cli_names) +
-                   ['-n']),
+        cli_names=list(domain_name.cli_names) + ['-n'],
     )
 
-    servers = knob(
-        bases=client.ClientInstallInterface.servers,
+    servers = extend_knob(
+        client.ClientInstallInterface.servers,
         description="fully qualified name of IPA server to enroll to",
     )
     servers = enroll_only(servers)
 
-    realm_name = knob(
-        bases=client.ClientInstallInterface.realm_name,
-        cli_names=(list(client.ClientInstallInterface.realm_name.cli_names) +
-                   ['-r']),
+    realm_name = client.ClientInstallInterface.realm_name
+    realm_name = extend_knob(
+        realm_name,
+        cli_names=list(realm_name.cli_names) + ['-r'],
     )
 
-    host_name = knob(
-        bases=client.ClientInstallInterface.host_name,
+    host_name = extend_knob(
+        client.ClientInstallInterface.host_name,
         description="fully qualified name of this host",
     )
 
-    ca_cert_files = knob(
-        bases=client.ClientInstallInterface.ca_cert_files,
+    ca_cert_files = extend_knob(
+        client.ClientInstallInterface.ca_cert_files,
         description="File containing CA certificates for the service "
                     "certificate files",
         cli_deprecated_names='--root-ca-file',
     )
     ca_cert_files = prepare_only(ca_cert_files)
 
-    dm_password = knob(
-        bases=client.ClientInstallInterface.dm_password,
+    dm_password = extend_knob(
+        client.ClientInstallInterface.dm_password,
         description="Directory Manager password",
     )
 
-    ip_addresses = knob(
-        bases=client.ClientInstallInterface.ip_addresses,
+    ip_addresses = extend_knob(
+        client.ClientInstallInterface.ip_addresses,
         description="Server IP Address. This option can be used multiple "
                     "times",
     )
 
-    principal = knob(
-        bases=client.ClientInstallInterface.principal,
+    principal = client.ClientInstallInterface.principal
+    principal = extend_knob(
+        principal,
         description="User Principal allowed to promote replicas and join IPA "
                     "realm",
-        cli_names=(list(client.ClientInstallInterface.principal.cli_names) +
-                   ['-P']),
+        cli_names=list(principal.cli_names) + ['-P'],
     )
     principal = replica_install_only(principal)
 
@@ -195,20 +306,6 @@ def idmax(self):
     )
     no_hbac_allow = master_install_only(no_hbac_allow)
 
-    ignore_topology_disconnect = knob(
-        None,
-        description="do not check whether server uninstall disconnects the "
-                    "topology (domain level 1+)",
-    )
-    ignore_topology_disconnect = master_install_only(ignore_topology_disconnect)
-
-    ignore_last_of_role = knob(
-        None,
-        description="do not check whether server uninstall removes last "
-                    "CA/DNS server or DNSSec master (domain level 1+)",
-    )
-    ignore_last_of_role = master_install_only(ignore_last_of_role)
-
     no_pkinit = knob(
         None,
         description="disables pkinit setup steps",
@@ -235,92 +332,6 @@ def dirsrv_config_file(self, value):
         if not os.path.exists(value):
             raise ValueError("File %s does not exist." % value)
 
-    dirsrv_cert_files = knob(
-        # pylint: disable=invalid-sequence-index
-        typing.List[str], None,
-        description=("File containing the Directory Server SSL certificate "
-                     "and private key"),
-        cli_names='--dirsrv-cert-file',
-        cli_deprecated_names='--dirsrv_pkcs12',
-        cli_metavar='FILE',
-    )
-    dirsrv_cert_files = prepare_only(dirsrv_cert_files)
-
-    http_cert_files = knob(
-        # pylint: disable=invalid-sequence-index
-        typing.List[str], None,
-        description=("File containing the Apache Server SSL certificate and "
-                     "private key"),
-        cli_names='--http-cert-file',
-        cli_deprecated_names='--http_pkcs12',
-        cli_metavar='FILE',
-    )
-    http_cert_files = prepare_only(http_cert_files)
-
-    pkinit_cert_files = knob(
-        # pylint: disable=invalid-sequence-index
-        typing.List[str], None,
-        description=("File containing the Kerberos KDC SSL certificate and "
-                     "private key"),
-        cli_names='--pkinit-cert-file',
-        cli_deprecated_names='--pkinit_pkcs12',
-        cli_metavar='FILE',
-    )
-    pkinit_cert_files = prepare_only(pkinit_cert_files)
-
-    dirsrv_pin = knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Directory Server private key",
-        cli_deprecated_names='--dirsrv_pin',
-        cli_metavar='PIN',
-    )
-    dirsrv_pin = prepare_only(dirsrv_pin)
-
-    http_pin = knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Apache Server private key",
-        cli_deprecated_names='--http_pin',
-        cli_metavar='PIN',
-    )
-    http_pin = prepare_only(http_pin)
-
-    pkinit_pin = knob(
-        str, None,
-        sensitive=True,
-        description="The password to unlock the Kerberos KDC private key",
-        cli_deprecated_names='--pkinit_pin',
-        cli_metavar='PIN',
-    )
-    pkinit_pin = prepare_only(pkinit_pin)
-
-    dirsrv_cert_name = knob(
-        str, None,
-        description="Name of the Directory Server SSL certificate to install",
-        cli_metavar='NAME',
-    )
-    dirsrv_cert_name = prepare_only(dirsrv_cert_name)
-
-    http_cert_name = knob(
-        str, None,
-        description="Name of the Apache Server SSL certificate to install",
-        cli_metavar='NAME',
-    )
-    http_cert_name = prepare_only(http_cert_name)
-
-    pkinit_cert_name = knob(
-        str, None,
-        description="Name of the Kerberos KDC SSL certificate to install",
-        cli_metavar='NAME',
-    )
-    pkinit_cert_name = prepare_only(pkinit_cert_name)
-
-    add_agents = knob(
-        bases=adtrust.ADTrustInstallInterface.add_agents
-    )
-    add_agents = replica_install_only(add_agents)
-
     def __init__(self, **kwargs):
         super(ServerInstallInterface, self).__init__(**kwargs)
 
@@ -515,8 +526,8 @@ class ServerMasterInstall(ServerMasterInstallInterface):
     setup_ca = True
     setup_kra = False
 
-    domain_name = knob(
-        bases=ServerMasterInstallInterface.domain_name,
+    domain_name = extend_knob(
+        ServerMasterInstallInterface.domain_name,
     )
 
     @domain_name.validator
@@ -526,16 +537,16 @@ def domain_name(self, value):
             print("Checking DNS domain %s, please wait ..." % value)
             check_zone_overlap(value, False)
 
-    dm_password = knob(
-        bases=ServerMasterInstallInterface.dm_password,
+    dm_password = extend_knob(
+        ServerMasterInstallInterface.dm_password,
     )
 
     @dm_password.validator
     def dm_password(self, value):
         validate_dm_password(value)
 
-    admin_password = knob(
-        bases=ServerMasterInstallInterface.admin_password,
+    admin_password = extend_knob(
+        ServerMasterInstallInterface.admin_password,
         description="admin user kerberos password",
     )
 
@@ -575,8 +586,8 @@ class ServerReplicaInstall(ServerReplicaInstallInterface):
     subject_base = None
     ca_subject = None
 
-    admin_password = knob(
-        bases=ServerReplicaInstallInterface.admin_password,
+    admin_password = extend_knob(
+        ServerReplicaInstallInterface.admin_password,
         description="Kerberos password for the specified admin principal",
     )
 
-- 
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