Hi,

the attached patch contains a new PoC installer for httpd.

Design goals:

1) Make code related to any particular configuration change co-located, be it install/uninstall/upgrade.

2) Get rid of code duplicates.

3) Use the same code path for install and upgrade.

4) Provide metadata for parameters from which option parsers etc. can be generated.

5) Make installers plugable. This is not really apparent from the patch, since it only implements installer for a single component, but I plan to make the whole thing extensible by plugins.

Honza

--
Jan Cholasta
>From da0a429ac2f2ae3cb68a948cba92d9582c49214f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 23 Mar 2015 07:37:54 +0000
Subject: [PATCH] install: New installer PoC

---
 install/tools/ipa-dns-install     |   2 +-
 install/tools/ipa-replica-install |  56 ++-
 install/tools/ipa-server-install  |  56 ++-
 install/tools/ipa-upgradeconfig   |  95 +---
 ipaserver/install/httpinstance.py | 944 ++++++++++++++++++++++++++++++--------
 5 files changed, 846 insertions(+), 307 deletions(-)

diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index 4527447..4923401 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -217,7 +217,7 @@ def main():
 
     # Restart http instance to make sure that python-dns has the right resolver
     # https://bugzilla.redhat.com/show_bug.cgi?id=800368
-    http = httpinstance.HTTPInstance(fstore)
+    http = httpinstance.HTTPInstance()
     service.print_msg("Restarting the web server")
     http.restart()
 
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 8693129..b7320bb 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -230,21 +230,14 @@ def install_ca_cert(ldap, base_dn, realm, cafile):
         print "error copying files: " + str(e)
         sys.exit(1)
 
-def install_http(config, auto_redirect):
+def install_http(config, http):
     # if we have a pkcs12 file, create the cert db from
     # that. Otherwise the ds setup will create the CA
     # cert
-    pkcs12_info = make_pkcs12_info(config.dir, "httpcert.p12", "http_pin.txt")
-
     memcache = memcacheinstance.MemcacheInstance()
     memcache.create_instance('MEMCACHE', config.host_name, config.dirman_password, ipautil.realm_to_suffix(config.realm_name))
 
-    http = httpinstance.HTTPInstance()
-    http.create_instance(
-        config.realm_name, config.host_name, config.domain_name,
-        config.dirman_password, False, pkcs12_info,
-        auto_redirect=auto_redirect, ca_file = config.dir + "/ca.crt",
-        ca_is_configured=ipautil.file_exists(config.dir + "/cacert.p12"))
+    http.execute()
 
     # Now copy the autoconfiguration files
     try:
@@ -258,9 +251,6 @@ def install_http(config, auto_redirect):
         print "error copying files: " + str(e)
         sys.exit(1)
 
-    http.setup_firefox_extension(config.realm_name, config.domain_name)
-
-    return http
 
 def install_bind(config, options):
     api.Backend.ldap2.connect(bind_dn=DIRMAN_DN,
@@ -462,10 +452,6 @@ def main():
             options.no_dnssec_validation = True
             print "WARNING: DNSSEC validation will be disabled"
 
-    # Check to see if httpd is already configured to listen on 443
-    if httpinstance.httpd_443_configured():
-        sys.exit("Aborting installation")
-
     check_dirsrv()
 
     if options.setup_ca:
@@ -499,6 +485,39 @@ def main():
     REPLICA_INFO_TOP_DIR = config.top_dir
     config.setup_ca = options.setup_ca
 
+    http_pkcs12_info = make_pkcs12_info(config.dir, "httpcert.p12", "http_pin.txt")
+
+    overrides = dict(
+        confdir='/dev/null',
+        realm=config.realm_name,
+        host=config.host_name,
+        domain=config.domain_name,
+        ldap_uri=('ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' %
+                  dsinstance.realm_to_serverid(config.realm_name)),
+        basedn=str(ipautil.realm_to_suffix(config.realm_name)),
+    )
+    if ipautil.file_exists(config.dir + "/cacert.p12"):
+        overrides.update(
+            enable_ra=True,
+            ra_plugin='dogtag',
+            dogtag_version=dogtag.install_constants.DOGTAG_VERSION,
+        )
+    else:
+        overrides.update(
+            enable_ra=False,
+            ra_plugin='none',
+        )
+    http_api = create_api(mode='dummy')
+    http_api.bootstrap(in_server=True, context='installer', **overrides)
+    http_api.finalize()
+
+    http = httpinstance.HTTPInstaller(api=http_api,
+                                      subject_base=config.subject_base,
+                                      pkcs12_info=http_pkcs12_info,
+                                      ca_file=config.dir + '/ca.crt',
+                                      ui_redirect=options.ui_redirect)
+    http.validate()
+
     if config.setup_ca and not ipautil.file_exists(config.dir + "/cacert.p12"):
         print 'CA cannot be installed in CA-less setup.'
         sys.exit(1)
@@ -564,6 +583,8 @@ def main():
     api.bootstrap(in_server=True, context='installer')
     api.finalize()
 
+    http.api = api
+
     # Create DS user/group if it doesn't exist yet
     dsinstance.create_ds_user()
 
@@ -676,7 +697,7 @@ def main():
         CA.enable_client_auth_to_db(CA.dogtag_constants.CS_CFG_PATH)
 
     krb = install_krb(config, setup_pkinit=options.setup_pkinit)
-    http = install_http(config, auto_redirect=options.ui_redirect)
+    install_http(config, http)
 
     otpd = otpdinstance.OtpdInstance()
     otpd.create_instance('OTPD', config.host_name, config.dirman_password,
@@ -715,6 +736,7 @@ def main():
         install_bind(config, options)
 
     # Restart httpd to pick up the new IPA configuration
+    http = httpinstance.HTTPInstance()
     service.print_msg("Restarting the web server")
     http.restart()
 
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index 56a4377..7421342 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -72,7 +72,7 @@ from ipapython import sysrestore
 from ipapython.ipautil import *
 from ipapython import ipautil
 from ipapython import dogtag
-from ipalib import api, errors, util, x509
+from ipalib import create_api, api, errors, util, x509
 from ipapython.config import IPAOptionParser
 from ipalib.util import validate_domain_name
 from ipalib.constants import CACERT
@@ -605,7 +605,7 @@ def uninstall():
     if dnskeysync.is_configured():
         dnskeysync.uninstall()
 
-    httpinstance.HTTPInstance(fstore).uninstall()
+    httpinstance.HTTPInstaller(api=api, uninstall=True).run()
     krbinstance.KrbInstance(fstore).uninstall()
     dsinstance.DsInstance(fstore=fstore).uninstall()
     if _server_trust_ad_installed:
@@ -911,10 +911,6 @@ def main():
         except ipaclient.ntpconf.NTPConfigurationError:
             pass
 
-    # Check to see if httpd is already configured to listen on 443
-    if httpinstance.httpd_443_configured():
-        sys.exit("Aborting installation")
-
     realm_name = ""
     host_name = ""
     domain_name = ""
@@ -1009,6 +1005,8 @@ def main():
             ca_cert_files=options.ca_cert_files,
             host_name=host_name)
         http_pkcs12_info = (http_pkcs12_file.name, http_pin)
+    else:
+        http_pkcs12_info = None
 
     if options.dirsrv_cert_files:
         if options.dirsrv_pin is None:
@@ -1047,6 +1045,36 @@ def main():
         sys.exit("Apache Server SSL certificate and Directory Server SSL "
                  "certificate are not signed by the same CA certificate")
 
+    overrides = dict(
+        confdir='/dev/null',
+        realm=realm_name,
+        host=host_name,
+        domain=domain_name,
+        ldap_uri=('ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' %
+                  dsinstance.realm_to_serverid(realm_name)),
+        basedn=str(ipautil.realm_to_suffix(realm_name)),
+    )
+    if setup_ca:
+        overrides.update(
+            enable_ra=True,
+            ra_plugin='dogtag',
+            dogtag_version=dogtag.install_constants.DOGTAG_VERSION,
+        )
+    else:
+        overrides.update(
+            enable_ra=False,
+            ra_plugin='none',
+        )
+    http_api = create_api(mode='dummy')
+    http_api.bootstrap(in_server=True, context='installer', **overrides)
+    http_api.finalize()
+
+    http = httpinstance.HTTPInstaller(api=http_api,
+                                      subject_base=options.subject,
+                                      pkcs12_info=http_pkcs12_info,
+                                      ui_redirect=options.ui_redirect)
+    http.validate()
+
     if not options.dm_password:
         dm_password = read_dm_password()
 
@@ -1153,6 +1181,8 @@ def main():
     api.bootstrap(**cfg)
     api.finalize()
 
+    http.api = api
+
     if not options.unattended:
         print ""
         print "The following operations may take some minutes to complete."
@@ -1275,18 +1305,7 @@ def main():
                          ipautil.realm_to_suffix(realm_name))
 
     # Create a HTTP instance
-    http = httpinstance.HTTPInstance(fstore)
-    if options.http_cert_files:
-        http.create_instance(
-            realm_name, host_name, domain_name, dm_password,
-            pkcs12_info=http_pkcs12_info, subject_base=options.subject,
-            auto_redirect=options.ui_redirect,
-            ca_is_configured=setup_ca)
-    else:
-        http.create_instance(
-            realm_name, host_name, domain_name, dm_password,
-            subject_base=options.subject, auto_redirect=options.ui_redirect,
-            ca_is_configured=setup_ca)
+    http.execute()
     tasks.restore_context(paths.CACHE_IPA_SESSIONS)
 
     # Export full CA chain
@@ -1333,6 +1352,7 @@ def main():
         bind.create_sample_bind_zone()
 
     # Restart httpd to pick up the new IPA configuration
+    http = httpinstance.HTTPInstance()
     service.print_msg("Restarting the web server")
     http.restart()
 
diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index 8159ce2..a05bc7f 100755
--- a/install/tools/ipa-upgradeconfig
+++ b/install/tools/ipa-upgradeconfig
@@ -132,26 +132,6 @@ def find_hostname():
 
     raise RuntimeError("Unable to determine the fully qualified hostname from %s" % filename)
 
-def find_autoredirect(fqdn):
-    """
-    When upgrading ipa-rewrite.conf we need to see if the automatic redirect
-    was disabled during install time (or afterward). So sift through the
-    configuration file and see if we can determine the status.
-
-    Returns True if autoredirect is enabled, False otherwise
-    """
-    filename = paths.HTTPD_IPA_REWRITE_CONF
-    if os.path.exists(filename):
-        pattern = "^RewriteRule \^/\$ https://%s/ipa/ui \[L,NC,R=301\]" % fqdn
-        p = re.compile(pattern)
-        for line in fileinput.input(filename):
-            if p.search(line):
-                fileinput.close()
-                return True
-        fileinput.close()
-        return False
-    return True
-
 def find_version(filename):
     """Find the version of a configuration file
 
@@ -230,8 +210,6 @@ def upgrade_pki(ca, fstore):
         root_logger.info('CA is not configured')
         return
 
-    http = httpinstance.HTTPInstance(fstore)
-    http.enable_mod_nss_renegotiate()
     if not installutils.get_directive(configured_constants.CS_CFG_PATH,
                                       'proxy.securePort', '=') and \
             os.path.exists(paths.PKI_SETUP_PROXY):
@@ -312,16 +290,6 @@ def cleanup_adtrust(fstore):
             root_logger.debug('Removing %s from backup', backed_up_file)
 
 
-def setup_firefox_extension(fstore):
-    """Set up the Firefox configuration extension, if it's not set up yet
-    """
-    root_logger.info('[Setting up Firefox extension]')
-    http = httpinstance.HTTPInstance(fstore)
-    realm = api.env.realm
-    domain = api.env.domain
-    http.setup_firefox_extension(realm, domain)
-
-
 def upgrade_ipa_profile(ca, domain, fqdn):
     """
     Update the IPA Profile provided by dogtag
@@ -1058,7 +1026,7 @@ def find_subject_base():
                       'certmap.conf will not be updated.')
 
 
-def uninstall_selfsign(ds, http):
+def uninstall_selfsign(ds):
     root_logger.info('[Removing self-signed CA]')
     """Replace self-signed CA by a CA-less install"""
     if api.env.ra_plugin != 'selfsign':
@@ -1075,7 +1043,6 @@ def uninstall_selfsign(ds, http):
         p.write(f)
 
     ds.stop_tracking_certificates()
-    http.stop_tracking_certificates()
 
 
 def mask_named_regular():
@@ -1202,45 +1169,6 @@ def remove_ds_ra_cert(subject_base):
     sysupgrade.set_upgrade_state('ds', 'remove_ra_cert', True)
 
 
-def fix_trust_flags():
-    root_logger.info('[Fixing trust flags in %s]' % paths.HTTPD_ALIAS_DIR)
-
-    if sysupgrade.get_upgrade_state('http', 'fix_trust_flags'):
-        root_logger.info("Trust flags already processed")
-        return
-
-    if not api.Backend.ldap2.isconnected():
-        try:
-            api.Backend.ldap2.connect(autobind=True)
-        except ipalib.errors.PublicError, e:
-            root_logger.error("Cannot connect to LDAP: %s", e)
-            return
-
-    if not api.Command.ca_is_enabled()['result']:
-        root_logger.info("CA is not enabled")
-        return
-
-    db = certs.CertDB(api.env.realm)
-    nickname = certdb.get_ca_nickname(api.env.realm)
-    cert = db.get_cert_from_db(nickname)
-    if cert:
-        db.trust_root_cert(nickname, 'CT,C,C')
-
-    sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True)
-
-
-def update_mod_nss_protocol(http):
-    root_logger.info('[Updating mod_nss protocol versions]')
-
-    if sysupgrade.get_upgrade_state('nss.conf', 'protocol_updated_tls12'):
-        root_logger.info("Protocol versions already updated")
-        return
-
-    http.set_mod_nss_protocol()
-
-    sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True)
-
-
 def main():
     """
     Get some basics about the system. If getting those basics fail then
@@ -1281,13 +1209,9 @@ def main():
 
     check_certs()
 
-    auto_redirect = find_autoredirect(fqdn)
     configured_constants = dogtag.configured_constants()
     sub_dict = dict(
-        REALM=api.env.realm,
         FQDN=fqdn,
-        AUTOREDIR='' if auto_redirect else '#',
-        CRL_PUBLISH_PATH=configured_constants.CRL_PUBLISH_PATH,
         DOGTAG_PORT=configured_constants.AJP_PORT,
         CLONE='#'
     )
@@ -1312,8 +1236,6 @@ def main():
         ds_serverid = dsinstance.realm_to_serverid(api.env.realm)
         ds_dirname = dsinstance.config_dirname(ds_serverid)
 
-        upgrade(sub_dict, paths.HTTPD_IPA_CONF, ipautil.SHARE_DIR + "ipa.conf")
-        upgrade(sub_dict, paths.HTTPD_IPA_REWRITE_CONF, ipautil.SHARE_DIR + "ipa-rewrite.conf")
         if ca.is_configured():
             upgrade(sub_dict, paths.HTTPD_IPA_PKI_PROXY_CONF, ipautil.SHARE_DIR + "ipa-pki-proxy.conf", add=True)
         else:
@@ -1338,15 +1260,9 @@ def main():
             removed_sysconfig_file)
         fstore.restore_file(removed_sysconfig_file)
 
-    http = httpinstance.HTTPInstance(fstore)
-    http.configure_selinux_for_httpd()
-    http.change_mod_nss_port_from_http()
-    http.configure_certmonger_renewal_guard()
-
-    http.stop()
-    update_mod_nss_protocol(http)
-    fix_trust_flags()
-    http.start()
+    http = httpinstance.HTTPInstaller(api=api,
+                                      subject_base=subject_base)
+    http.run()
 
     ds = dsinstance.DsInstance()
     ds.configure_dirsrv_ccache()
@@ -1356,7 +1272,7 @@ def main():
     remove_ds_ra_cert(subject_base)
     ds.start(ds_serverid)
 
-    uninstall_selfsign(ds, http)
+    uninstall_selfsign(ds)
 
     simple_service_list = (
         (memcacheinstance.MemcacheInstance(), 'MEMCACHE'),
@@ -1386,7 +1302,6 @@ def main():
 
     cleanup_kdc(fstore)
     cleanup_adtrust(fstore)
-    setup_firefox_extension(fstore)
     add_ca_dns_records()
 
     # Any of the following functions returns True iff the named.conf file
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 18cf6bb..8e76bb2 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -26,6 +26,10 @@ import re
 import dbus
 import shlex
 import pipes
+import itertools
+import traceback
+import fileinput
+import sys
 
 import service
 import certs
@@ -33,10 +37,11 @@ import installutils
 from ipapython import sysrestore
 from ipapython import ipautil
 from ipapython import dogtag
+from ipapython import certdb
 from ipapython.ipa_log_manager import root_logger
 import ipapython.errors
 from ipaserver.install import sysupgrade
-from ipalib import api
+from ipalib import api, errors
 from ipaplatform.tasks import tasks
 from ipaplatform.paths import paths
 from ipaplatform import services
@@ -73,125 +78,376 @@ def httpd_443_configured():
 
     return False
 
-class WebGuiInstance(service.SimpleServiceInstance):
-    def __init__(self):
-        service.SimpleServiceInstance.__init__(self, "ipa_webgui")
 
-class HTTPInstance(service.Service):
-    def __init__(self, fstore=None, cert_nickname='Server-Cert'):
-        service.Service.__init__(self, "httpd", service_desc="the web interface")
-        if fstore:
-            self.fstore = fstore
+_missing = object()
+
+
+class Knob(object):
+    __counter = itertools.count()
+
+    def __init__(self, type, default=_missing, label=None):
+        self.type = type
+        if default is _missing:
+            self.required = True
+            self.default = None
         else:
-            self.fstore = sysrestore.FileStore(paths.SYSRESTORE)
-
-        self.cert_nickname = cert_nickname
-        self.ca_is_configured = True
-
-    subject_base = ipautil.dn_attribute_property('_subject_base')
-
-    def create_instance(self, realm, fqdn, domain_name, dm_password=None,
-                        autoconfig=True, pkcs12_info=None,
-                        subject_base=None, auto_redirect=True, ca_file=None,
-                        ca_is_configured=None):
-        self.fqdn = fqdn
-        self.realm = realm
-        self.domain = domain_name
-        self.dm_password = dm_password
-        self.suffix = ipautil.realm_to_suffix(self.realm)
-        self.pkcs12_info = pkcs12_info
-        self.principal = "HTTP/%s@%s" % (self.fqdn, self.realm)
-        self.dercert = None
+            self.required = False
+            self.default = default
+        self.label = label
+        self.order = next(self.__counter)
+
+    def __get__(self, obj, obj_type):
+        if obj is None:
+            return self
+        try:
+            return obj.get_knob(self)
+        except KeyError:
+            return self.default
+
+
+class Installer(object):
+    @classmethod
+    def knobs(cls):
+        for name in dir(cls):
+            obj = getattr(cls, name)
+            if isinstance(obj, Knob):
+                yield (cls, name, obj)
+
+    def __init__(self, uninstall=False, **kwargs):
+        missing = set()
+
+        self.__knobs = {}
+        for cls, name, knob in self.knobs():
+            try:
+                value = kwargs.pop(name)
+            except KeyError:
+                if knob.required:
+                    missing.add(name)
+            else:
+                self.__knobs[id(knob)] = value
+
+        if missing:
+            missing = sorted(missing)
+            raise TypeError(
+                "{0}() missing {1} required keyword arguments: {2}".format(
+                    type(self).__name__,
+                    len(missing),
+                    ', '.join(repr(name) for name in missing)))
+        if kwargs:
+            extra = sorted(kwargs.keys())
+            raise TypeError(
+                "{0}() got {1} unexpected keyword arguments: {2}".format(
+                    type(self).__name__,
+                    len(extra),
+                    ', '.join(repr(name) for name in extra)))
+
+        if uninstall:
+            self.__gen_obj = self.unconfigure()
+        else:
+            self.__gen_obj = self.configure()
+        assert hasattr(self.__gen_obj, 'next')
+
+    def get_knob(self, knob):
+        return self.__knobs[id(knob)]
+
+    def run(self):
+        self.validate()
+        self.execute()
+
+    def validate(self):
+        try:
+            self.next()
+        except StopIteration:
+            pass
+
+    def execute(self):
+        try:
+            self.next()
+        except StopIteration:
+            pass
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        return self.next()
+
+    def next(self):
+        return self.__gen_obj.next()
+
+    def send(self, value):
+        return self.__gen_obj.send(value)
+
+    def throw(self, type, value=None, traceback=None):
+        return self.__gen_obj.throw(type, value, traceback)
+
+    def close(self):
+        self.__gen_obj.close()
+
+    def configure(self):
+        raise NotImplementedError
+
+    def unconfigure(self):
+        raise NotImplementedError
+
+
+class CompositeInstaller(Installer):
+    @classmethod
+    def knobs(cls):
+        for comp in cls.components():
+            for item in comp.knobs():
+                yield item
+
+        for item in super(CompositeInstaller, cls).knobs():
+            yield item
+
+    @classmethod
+    def components(cls):
+        raise NotImplementedError
+
+    def __init__(self, uninstall=False, **kwargs):
+        comp_kwargs = {}
+        for cls, name, knob in self.knobs():
+            if cls is type(self):
+                continue
+            try:
+                value = kwargs.pop(name)
+            except KeyError:
+                pass
+            else:
+                comp_kwargs.setdefault(cls, {})[name] = value
+
+        super(CompositeInstaller, self).__init__(uninstall=uninstall, **kwargs)
+
+        self.__gen_objs = [
+            comp(parent=self, uninstall=uninstall, **comp_kwargs.get(comp, {}))
+            for comp in self.components()]
+        assert all(hasattr(gen_obj, 'next') for gen_obj in self.__gen_objs)
+
+    def configure(self):
+        return itertools.izip_longest(*self.__gen_objs)
+
+    def unconfigure(self):
+        return itertools.izip_longest(*reversed(self.__gen_objs))
+
+
+class ComposableInstaller(Installer):
+    def __init__(self, parent, **kwargs):
+        self.parent = parent
+
+        super(ComposableInstaller, self).__init__(**kwargs)
+
+    def get_knob(self, key):
+        try:
+            return super(ComposableInstaller, self).get_knob(key)
+        except KeyError:
+            return self.parent.get_knob(key)
+
+
+class InstallerWithSteps(CompositeInstaller):
+    @classmethod
+    def components(cls):
+        steps = []
+        for name in dir(cls):
+            obj = getattr(cls, name)
+            if hasattr(obj, '_step_order'):
+                steps.append(obj)
+
+        steps = sorted(steps, key=lambda step: step._step_order)
+        for step in steps:
+            yield step
+
+
+class InstallerStep(ComposableInstaller):
+    def configure(self):
+        return
+        yield
+
+    def unconfigure(self):
+        return
+        yield
+
+    @classmethod
+    def configurator(cls, func):
+        def configure(self):
+            gen_obj = func(self.parent)
+            assert hasattr(gen_obj, 'next')
+            return gen_obj
+
+        new_dict = dict(cls.__dict__, configure=configure)
+        new_cls = type(cls.__name__, cls.__bases__, new_dict)
+
+        return new_cls
+
+    @classmethod
+    def unconfigurator(cls, func):
+        def unconfigure(self):
+            gen_obj = func(self.parent)
+            assert hasattr(gen_obj, 'next')
+            return gen_obj
+
+        new_dict = dict(cls.__dict__, unconfigure=unconfigure)
+        new_cls = type(cls.__name__, cls.__bases__, new_dict)
+
+        return new_cls
+
+
+_step_counter = itertools.count()
+
+def step():
+    def decorator(func):
+        cls = type(func.__name__, (InstallerStep,), dict(_step_order=next(_step_counter)))
+        return cls.configurator(func)
+
+    return decorator
+
+
+def find_autoredirect(fqdn):
+    """
+    When upgrading ipa-rewrite.conf we need to see if the automatic redirect
+    was disabled during install time (or afterward). So sift through the
+    configuration file and see if we can determine the status.
+
+    Returns True if autoredirect is enabled, False otherwise
+    """
+    filename = paths.HTTPD_IPA_REWRITE_CONF
+    if os.path.exists(filename):
+        pattern = "^RewriteRule \^/\$ https://%s/ipa/ui \[L,NC,R=301\]" % fqdn
+        p = re.compile(pattern)
+        for line in fileinput.input(filename):
+            if p.search(line):
+                fileinput.close()
+                return True
+        fileinput.close()
+        return False
+    return True
+
+
+def find_version(filename):
+    """Find the version of a configuration file
+
+    If no VERSION entry exists in the file, returns 0.
+    If the file does not exist, returns -1.
+    """
+    if os.path.exists(filename):
+        pattern = "^[\s#]*VERSION\s+([0-9]+)\s+.*"
+        p = re.compile(pattern)
+        for line in fileinput.input(filename):
+            if p.search(line):
+                fileinput.close()
+                return p.search(line).group(1)
+        fileinput.close()
+
+        # no VERSION found
+        return 0
+    else:
+        return -1
+
+
+def backup_file(filename, ext):
+    """Make a backup of filename using ext as the extension. Do not overwrite
+       previous backups."""
+    if not os.path.isabs(filename):
+        raise ValueError("Absolute path required")
+
+    backupfile = filename + ".bak"
+
+    while os.path.exists(backupfile):
+        backupfile = backupfile + "." + str(ext)
+
+    try:
+        shutil.copy2(filename, backupfile)
+    except IOError, e:
+        if e.errno == 2: # No such file or directory
+            pass
+        else:
+            raise e
+
+
+class HTTPInstaller(InstallerWithSteps):
+    ui_redirect = Knob(bool, True,
+                       label="automatically redirect to the Web UI")
+
+    def __init__(self, api, subject_base=None, pkcs12_info=None, ca_file=None,
+                 **kwargs):
+        self.api = api
         self.subject_base = subject_base
-        self.sub_dict = dict(
-            REALM=realm,
-            FQDN=fqdn,
-            DOMAIN=self.domain,
-            AUTOREDIR='' if auto_redirect else '#',
-            CRL_PUBLISH_PATH=dogtag.install_constants.CRL_PUBLISH_PATH,
-        )
+        self.pkcs12_info = pkcs12_info
         self.ca_file = ca_file
-        if ca_is_configured is not None:
-            self.ca_is_configured = ca_is_configured
-
-        # get a connection to the DS
-        self.ldap_connect()
-
-
-        self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
-        self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
-                  self.set_mod_nss_protocol)
-        self.step("setting mod_nss password file", self.__set_mod_nss_passwordfile)
-        self.step("enabling mod_nss renegotiate", self.enable_mod_nss_renegotiate)
-        self.step("adding URL rewriting rules", self.__add_include)
-        self.step("configuring httpd", self.__configure_http)
-        if self.ca_is_configured:
-            self.step("configure certmonger for renewals",
-                      self.configure_certmonger_renewal_guard)
-        self.step("setting up ssl", self.__setup_ssl)
-        self.step("importing CA certificates from LDAP", self.__import_ca_certs)
-        if autoconfig:
-            self.step("setting up browser autoconfig", self.__setup_autoconfig)
-        self.step("publish CA cert", self.__publish_ca_cert)
-        self.step("creating a keytab for httpd", self.__create_http_keytab)
-        self.step("clean up any existing httpd ccache", self.remove_httpd_ccache)
-        self.step("configuring SELinux for httpd", self.configure_selinux_for_httpd)
-        self.step("restarting httpd", self.__start)
-        self.step("configuring httpd to start on boot", self.__enable)
-
-        self.start_creation(runtime=60)
-
-    def __start(self):
-        self.backup_state("running", self.is_running())
-        self.restart()
-
-    def __enable(self):
-        self.backup_state("enabled", self.is_enabled())
-        # We do not let the system start IPA components on its own,
-        # Instead we reply on the IPA init script to start only enabled
-        # components as found in our LDAP configuration tree
-        self.ldap_enable('HTTP', self.fqdn, self.dm_password, self.suffix)
 
-    def configure_selinux_for_httpd(self):
+        self.is_installer = (api.env.context == 'installer')
+        self.fstore = sysrestore.FileStore(paths.SYSRESTORE)
+        self.service = HTTPInstance(api)
+
         try:
-            tasks.set_selinux_booleans(SELINUX_BOOLEAN_SETTINGS,
-                                       self.backup_state)
-        except ipapython.errors.SetseboolError as e:
-            self.print_msg(e.format_service_warning('web interface'))
+            ui_redirect = kwargs.pop('ui_redirect')
+        except KeyError:
+            ui_redirect = find_autoredirect(api.env.host)
 
-    def __create_http_keytab(self):
-        installutils.kadmin_addprinc(self.principal)
-        installutils.create_keytab(paths.IPA_KEYTAB, self.principal)
-        self.move_service(self.principal)
-        self.add_cert_to_service()
+        super(HTTPInstaller, self).__init__(ui_redirect=ui_redirect, **kwargs)
 
-        pent = pwd.getpwnam("apache")
-        os.chown(paths.IPA_KEYTAB, pent.pw_uid, pent.pw_gid)
+    def configure(self):
+        gen_obj = super(HTTPInstaller, self).configure()
+        assert hasattr(gen_obj, 'next')
 
-    def remove_httpd_ccache(self):
-        # Clean up existing ccache
-        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
-        # current env
-        ipautil.run(['kdestroy', '-A'], runas='apache', raiseonerr=False, env={})
+        gen_obj.next()
 
-    def __configure_http(self):
-        target_fname = paths.HTTPD_IPA_CONF
-        http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict)
-        self.fstore.backup_file(paths.HTTPD_IPA_CONF)
-        http_fd = open(target_fname, "w")
-        http_fd.write(http_txt)
-        http_fd.close()
-        os.chmod(target_fname, 0644)
+        yield
 
-        target_fname = paths.HTTPD_IPA_REWRITE_CONF
-        http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict)
-        self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
-        http_fd = open(target_fname, "w")
-        http_fd.write(http_txt)
-        http_fd.close()
-        os.chmod(target_fname, 0644)
+        if self.is_installer:
+            self.service.print_msg('Configuring %s (%s): Estimated time %s' % (
+                self.service.service_desc, self.service.service_name, service.format_seconds(60)))
+
+            self.fstore.backup_file(paths.HTTPD_NSS_CONF)
+
+        try:
+            gen_obj.next()
+        except StopIteration:
+            raise
+        except BaseException as e:
+            if self.is_installer:
+                if not (isinstance(e, SystemExit) and e.code == 0):
+                    # show the traceback, so it's not lost if cleanup method fails
+                    root_logger.debug("%s" % traceback.format_exc())
+                    self.service.print_msg('  [error] %s: %s' % (type(e).__name__, e))
+
+            raise
+        else:
+            if self.is_installer:
+                self.service.print_msg("Done configuring %s (%s)." % (
+                    self.service.service_desc, self.service.service_name))
+
+    def unconfigure(self):
+        gen_obj = super(HTTPInstaller, self).unconfigure()
+        assert hasattr(gen_obj, 'next')
+
+        if self.is_installer:
+            self.service.print_msg("Unconfiguring web server")
+
+        gen_obj.next()
+
+        yield
+
+        gen_obj.next()
+
+        for f in [paths.HTTPD_SSL_CONF, paths.HTTPD_NSS_CONF]:
+            try:
+                self.fstore.restore_file(f)
+            except ValueError, error:
+                root_logger.debug(error)
+                pass
+
+        # Remove the configuration files we create
+        installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)
+
+        running = self.service.restore_state("running")
+        if running:
+            self.service.restart()
+
+    @step()
+    def set_mod_nss_port(self):
+        old_value = '8443'
 
-    def change_mod_nss_port_from_http(self):
         # mod_ssl enforces SSLEngine on for vhost on 443 even though
         # the listener is mod_nss. This then crashes the httpd as mod_nss
         # listened port obviously does not match mod_ssl requirements.
@@ -201,34 +457,179 @@ class HTTPInstance(service.Service):
         # sets SSLEngine off when mod_ssl is installed.
         #
         # Remove the workaround.
-        if sysupgrade.get_upgrade_state('nss.conf', 'listen_port_updated'):
-            installutils.set_directive(paths.HTTPD_NSS_CONF, 'Listen', '443', quotes=False)
-            sysupgrade.set_upgrade_state('nss.conf', 'listen_port_updated', False)
+        if not self.is_installer:
+            state = sysupgrade.get_upgrade_state('nss.conf',
+                                                 'listen_port_updated')
+            if state is False:
+                return
+
+            if state:
+                old_value = '443 http'
+
+        if self.is_installer and httpd_443_configured():
+            raise RuntimeError("Aborting installation")
 
-    def __set_mod_nss_port(self):
-        self.fstore.backup_file(paths.HTTPD_NSS_CONF)
-        if installutils.update_file(paths.HTTPD_NSS_CONF, '8443', '443') != 0:
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [1/16] setting mod_nss port to 443")
+
+        if installutils.update_file(paths.HTTPD_NSS_CONF, old_value, '443') != 0:
             print "Updating port in %s failed." % paths.HTTPD_NSS_CONF
 
-    def __set_mod_nss_nickname(self, nickname):
-        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSNickname', nickname)
+        sysupgrade.set_upgrade_state('nss.conf', 'listen_port_updated', False)
 
+    @step()
     def set_mod_nss_protocol(self):
+        if not self.is_installer:
+            root_logger.info('[Updating mod_nss protocol versions]')
+
+            state = sysupgrade.get_upgrade_state('nss.conf',
+                                                 'protocol_updated_tls12')
+            if state:
+                root_logger.info("Protocol versions already updated")
+                return
+
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [2/16] setting mod_nss protocol list to TLSv1.0 - TLSv1.2")
+
         installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSProtocol', 'TLSv1.0,TLSv1.1,TLSv1.2', False)
 
+        sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True)
+
+    @step()
+    def set_mod_nss_passwordfile(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [3/16] setting mod_nss password file")
+
+        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog', 'file:/etc/httpd/conf/password.conf')
+
+    @step()
     def enable_mod_nss_renegotiate(self):
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [4/16] enabling mod_nss renegotiate")
+
         installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRenegotiation', 'on', False)
         installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRequireSafeNegotiation', 'on', False)
 
-    def __set_mod_nss_passwordfile(self):
-        installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog', 'file:/etc/httpd/conf/password.conf')
+    @step()
+    def add_include(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [5/16] adding URL rewriting rules")
 
-    def __add_include(self):
-        """This should run after __set_mod_nss_port so is already backed up"""
         if installutils.update_file(paths.HTTPD_NSS_CONF, '</VirtualHost>', 'Include conf.d/ipa-rewrite.conf\n</VirtualHost>') != 0:
             print "Adding Include conf.d/ipa-rewrite to %s failed." % paths.HTTPD_NSS_CONF
 
+    @step()
+    def configure_http(self):
+        filename = ipautil.SHARE_DIR + 'ipa.conf'
+        ipa_template_version = int(find_version(filename))
+        if ipa_template_version < 0:
+            raise RuntimeError("%s not found" % filename)
+
+        filename = ipautil.SHARE_DIR + 'ipa-rewrite.conf'
+        rewrite_template_version = int(find_version(filename))
+        if rewrite_template_version < 0:
+            raise RuntimeError("%s not found" % filename)
+
+        ipa_conf_version = int(find_version(paths.HTTPD_IPA_CONF))
+        rewrite_conf_version = int(find_version(paths.HTTPD_IPA_REWRITE_CONF))
+
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [6/16] configuring httpd")
+
+        sub_dict = dict(
+            REALM=self.api.env.realm,
+            FQDN=self.api.env.host,
+            DOMAIN=self.api.env.domain,
+            AUTOREDIR='' if self.ui_redirect else '#',
+            CRL_PUBLISH_PATH=dogtag.configured_constants().CRL_PUBLISH_PATH,
+        )
+
+        if ipa_conf_version < ipa_template_version:
+            if ipa_conf_version == 0:
+                if not self.is_installer:
+                    root_logger.warning(
+                        "%s is now managed by IPA. It will be overwritten. A "
+                        "backup of the original will be made.",
+                        paths.HTTPD_IPA_CONF)
+                self.fstore.backup_file(paths.HTTPD_IPA_CONF)
+
+            backup_file(paths.HTTPD_IPA_CONF, ipa_template_version)
+
+            target_fname = paths.HTTPD_IPA_CONF
+            http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", sub_dict)
+            http_fd = open(target_fname, "w")
+            http_fd.write(http_txt)
+            http_fd.close()
+            os.chmod(target_fname, 0644)
+
+            if not self.is_installer:
+                root_logger.info("Upgraded %s to version %d",
+                                 paths.HTTPD_IPA_CONF,
+                                 ipa_template_version)
+
+        if rewrite_conf_version < rewrite_template_version:
+            if rewrite_conf_version == 0:
+                if not self.is_installer:
+                    root_logger.warning(
+                        "%s is now managed by IPA. It will be overwritten. A "
+                        "backup of the original will be made.",
+                        paths.HTTPD_IPA_REWRITE_CONF)
+                self.fstore.backup_file(paths.HTTPD_IPA_REWRITE_CONF)
+
+            backup_file(paths.HTTPD_IPA_REWRITE_CONF, rewrite_template_version)
+
+            target_fname = paths.HTTPD_IPA_REWRITE_CONF
+            http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", sub_dict)
+            http_fd = open(target_fname, "w")
+            http_fd.write(http_txt)
+            http_fd.close()
+            os.chmod(target_fname, 0644)
+
+            if not self.is_installer:
+                root_logger.info("Upgraded %s to version %d",
+                                 paths.HTTPD_IPA_REWRITE_CONF,
+                                 rewrite_template_version)
+
+    @configure_http.unconfigurator
+    def configure_http(self):
+        yield
+
+        # Remove the configuration files we create
+        if self.fstore.has_file(paths.HTTPD_IPA_REWRITE_CONF):
+            self.fstore.restore_file(paths.HTTPD_IPA_REWRITE_CONF)
+        else:
+            installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
+        if self.fstore.has_file(paths.HTTPD_IPA_CONF):
+            self.fstore.restore_file(paths.HTTPD_IPA_CONF)
+        else:
+            installutils.remove_file(paths.HTTPD_IPA_CONF)
+
+    @step()
     def configure_certmonger_renewal_guard(self):
+        if not self.api.env.enable_ra:
+            return
+
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [7/16] configure certmonger for renewals")
+
         certmonger = services.knownservices.certmonger
         certmonger_stopped = not certmonger.is_running()
 
@@ -249,7 +650,7 @@ class HTTPInstance(service.Service):
                 if helper:
                     args = shlex.split(helper)
                     if args[0] != paths.IPA_SERVER_GUARD:
-                        self.backup_state('certmonger_ipa_helper', helper)
+                        self.service.backup_state('certmonger_ipa_helper', helper)
                         args = [paths.IPA_SERVER_GUARD] + args
                         helper = ' '.join(pipes.quote(a) for a in args)
                         ca_iface.Set('org.fedorahosted.certmonger.ca',
@@ -258,14 +659,43 @@ class HTTPInstance(service.Service):
             if certmonger_stopped:
                 certmonger.stop()
 
-    def __setup_ssl(self):
-        fqdn = self.fqdn
+    @configure_certmonger_renewal_guard.unconfigurator
+    def configure_certmonger_renewal_guard(self):
+        if not self.api.env.enable_ra:
+            return
+
+        yield
+
+        helper = self.service.restore_state('certmonger_ipa_helper')
+        if helper:
+            bus = dbus.SystemBus()
+            obj = bus.get_object('org.fedorahosted.certmonger',
+                                 '/org/fedorahosted/certmonger')
+            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
+            path = iface.find_ca_by_nickname('IPA')
+            if path:
+                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
+                ca_iface = dbus.Interface(ca_obj,
+                                          'org.freedesktop.DBus.Properties')
+                ca_iface.Set('org.fedorahosted.certmonger.ca',
+                             'external-helper', helper)
+
+    @step()
+    def setup_ssl(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [8/16] setting up ssl")
 
-        ca_db = certs.CertDB(self.realm, host_name=fqdn, subject_base=self.subject_base)
+        fqdn = self.api.env.host
 
-        db = certs.CertDB(self.realm, subject_base=self.subject_base)
+        ca_db = certs.CertDB(self.api.env.realm, host_name=fqdn, subject_base=self.subject_base)
+
+        db = certs.CertDB(self.api.env.realm, subject_base=self.subject_base)
         if self.pkcs12_info:
-            if self.ca_is_configured:
+            if self.api.env.enable_ra:
                 trust_flags = 'CT,C,C'
             else:
                 trust_flags = None
@@ -280,18 +710,18 @@ class HTTPInstance(service.Service):
 
             # We only handle one server cert
             nickname = server_certs[0][0]
-            self.dercert = db.get_cert_from_db(nickname, pem=False)
+            self.service.dercert = db.get_cert_from_db(nickname, pem=False)
 
-            if self.ca_is_configured:
-                db.track_server_cert(nickname, self.principal, db.passwd_fname, 'restart_httpd')
+            if self.api.env.enable_ra:
+                db.track_server_cert(nickname, self.service.principal, db.passwd_fname, 'restart_httpd')
 
-            self.__set_mod_nss_nickname(nickname)
+            installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSNickname', nickname)
         else:
 
             db.create_password_conf()
-            self.dercert = db.create_server_cert(self.cert_nickname, self.fqdn,
+            self.service.dercert = db.create_server_cert('Server-Cert', self.api.env.host,
                                                  ca_db)
-            db.track_server_cert(self.cert_nickname, self.principal,
+            db.track_server_cert('Server-Cert', self.service.principal,
                                  db.passwd_fname, 'restart_httpd')
             db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
 
@@ -311,19 +741,96 @@ class HTTPInstance(service.Service):
         tasks.restore_context(certs.NSS_DIR + "/cert8.db")
         tasks.restore_context(certs.NSS_DIR + "/key3.db")
 
-    def __import_ca_certs(self):
-        db = certs.CertDB(self.realm, subject_base=self.subject_base)
-        self.import_ca_certs(db, self.ca_is_configured)
+    @setup_ssl.unconfigurator
+    def setup_ssl(self):
+        if not self.api.env.enable_ra:
+            return
+
+        yield
+
+        self.service.stop_tracking_certificates()
+
+    @step()
+    def fix_trust_flags(self):
+        if self.is_installer:
+            return
+
+        yield
+
+        root_logger.info('[Fixing trust flags in %s]' % paths.HTTPD_ALIAS_DIR)
+
+        if sysupgrade.get_upgrade_state('http', 'fix_trust_flags'):
+            root_logger.info("Trust flags already processed")
+            return
+
+        if not self.api.Backend.ldap2.isconnected():
+            try:
+                self.api.Backend.ldap2.connect(autobind=True)
+            except errors.PublicError, e:
+                root_logger.error("Cannot connect to LDAP: %s", e)
+                return
+
+        if not self.api.Command.ca_is_enabled()['result']:
+            root_logger.info("CA is not enabled")
+            return
+
+        db = certs.CertDB(self.api.env.realm)
+        nickname = certdb.get_ca_nickname(self.api.env.realm)
+        cert = db.get_cert_from_db(nickname)
+        if cert:
+            db.trust_root_cert(nickname, 'CT,C,C')
+
+        sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True)
+
+    @step()
+    def uninstall_selfsign(self):
+        if self.is_installer:
+            return
+
+        if self.api.env.ra_plugin != 'selfsign':
+            return
+
+        yield
+
+        self.service.stop_tracking_certificates()
+
+    @step()
+    def import_ca_certs(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [9/16] importing CA certificates from LDAP")
+
+        db = certs.CertDB(self.api.env.realm, subject_base=self.subject_base)
+        self.service.import_ca_certs(db, self.api.env.enable_ra)
+
+    @step()
+    def setup_autoconfig(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [10/16] setting up browser autoconfig")
+
+        sub_dict = dict(
+            REALM=self.api.env.realm,
+            FQDN=self.api.env.host,
+            DOMAIN=self.api.env.domain,
+            AUTOREDIR='' if self.ui_redirect else '#',
+            CRL_PUBLISH_PATH=dogtag.configured_constants().CRL_PUBLISH_PATH,
+        )
 
-    def __setup_autoconfig(self):
         target_fname = paths.PREFERENCES_HTML
         ipautil.copy_template_file(
             ipautil.SHARE_DIR + "preferences.html.template",
-            target_fname, self.sub_dict)
+            target_fname, sub_dict)
         os.chmod(target_fname, 0644)
 
         # The signing cert is generated in __setup_ssl
-        db = certs.CertDB(self.realm, subject_base=self.subject_base)
+        db = certs.CertDB(self.api.env.realm, subject_base=self.subject_base)
         with open(db.passwd_fname) as pwdfile:
             pwd = pwdfile.read()
 
@@ -342,15 +849,18 @@ class HTTPInstance(service.Service):
             root_logger.warning('Object-signing certificate was not found; '
                 'therefore, configure.jar was not created.')
 
-        self.setup_firefox_extension(self.realm, self.domain)
-
-    def setup_firefox_extension(self, realm, domain):
+    @step()
+    def setup_firefox_extension(self):
         """Set up the signed browser configuration extension
         """
+        if not self.is_installer:
+            root_logger.info('[Setting up Firefox extension]')
+
+        yield
 
         target_fname = paths.KRB_JS
-        sub_dict = dict(REALM=realm, DOMAIN=domain)
-        db = certs.CertDB(realm)
+        sub_dict = dict(REALM=self.api.env.realm, DOMAIN=self.api.env.domain)
+        db = certs.CertDB(self.api.env.realm)
         with open(db.passwd_fname) as pwdfile:
             pwd = pwdfile.read()
 
@@ -377,61 +887,133 @@ class HTTPInstance(service.Service):
         shutil.rmtree(tmpdir)
         os.chmod(target_fname, 0644)
 
-    def __publish_ca_cert(self):
-        ca_db = certs.CertDB(self.realm)
+    @step()
+    def publish_ca_cert(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [11/16] publish CA cert")
+
+        ca_db = certs.CertDB(self.api.env.realm)
         ca_db.publish_ca_cert(paths.CA_CRT)
 
-    def uninstall(self):
-        if self.is_configured():
-            self.print_msg("Unconfiguring web server")
+    @step()
+    def create_http_keytab(self):
+        if not self.is_installer:
+            return
 
-        running = self.restore_state("running")
-        enabled = self.restore_state("enabled")
+        yield
 
+        self.service.print_msg("  [12/16] creating a keytab for httpd")
 
-        self.stop_tracking_certificates()
+        installutils.kadmin_addprinc(self.service.principal)
+        installutils.create_keytab(paths.IPA_KEYTAB, self.service.principal)
+        self.service.move_service(self.service.principal)
+        self.service.add_cert_to_service()
 
-        helper = self.restore_state('certmonger_ipa_helper')
-        if helper:
-            bus = dbus.SystemBus()
-            obj = bus.get_object('org.fedorahosted.certmonger',
-                                 '/org/fedorahosted/certmonger')
-            iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
-            path = iface.find_ca_by_nickname('IPA')
-            if path:
-                ca_obj = bus.get_object('org.fedorahosted.certmonger', path)
-                ca_iface = dbus.Interface(ca_obj,
-                                          'org.freedesktop.DBus.Properties')
-                ca_iface.Set('org.fedorahosted.certmonger.ca',
-                             'external-helper', helper)
+        pent = pwd.getpwnam("apache")
+        os.chown(paths.IPA_KEYTAB, pent.pw_uid, pent.pw_gid)
 
-        for f in [paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF, paths.HTTPD_NSS_CONF]:
-            try:
-                self.fstore.restore_file(f)
-            except ValueError, error:
-                root_logger.debug(error)
-                pass
+    @step()
+    def remove_httpd_ccache(self):
+        if not self.is_installer:
+            return
 
-        # Remove the configuration files we create
-        installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
-        installutils.remove_file(paths.HTTPD_IPA_CONF)
-        installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)
+        yield
+
+        self.service.print_msg("  [13/16] clean up any existing httpd ccache")
+
+        self.service.remove_httpd_ccache()
+
+    @step()
+    def configure_selinux_for_httpd(self):
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [14/16] configuring SELinux for httpd")
+
+        try:
+            tasks.set_selinux_booleans(SELINUX_BOOLEAN_SETTINGS,
+                                       self.service.backup_state)
+        except ipapython.errors.SetseboolError as e:
+            self.service.print_msg(e.format_service_warning('web interface'))
+
+    @configure_selinux_for_httpd.unconfigurator
+    def configure_selinux_for_httpd(self):
+        yield
 
         # Restore SELinux boolean states
-        boolean_states = {name: self.restore_state(name)
+        boolean_states = {name: self.service.restore_state(name)
                           for name in SELINUX_BOOLEAN_SETTINGS}
         try:
             tasks.set_selinux_booleans(boolean_states)
         except ipapython.errors.SetseboolError as e:
-            self.print_msg('WARNING: ' + str(e))
+            self.service.print_msg('WARNING: ' + str(e))
 
-        if running:
-            self.restart()
+    @step()
+    def start(self):
+        yield
+
+        if self.is_installer:
+            self.service.print_msg("  [15/16] restarting httpd")
+
+            self.service.backup_state("running", self.service.is_running())
+
+        self.service.restart()
+
+    @start.unconfigurator
+    def start(self):
+        running = self.service.restore_state("running")
+
+        yield
+
+        if not running is None:
+            self.service.stop()
+
+    @step()
+    def enable(self):
+        if not self.is_installer:
+            return
+
+        yield
+
+        self.service.print_msg("  [16/16] configuring httpd to start on boot")
+
+        self.service.backup_state("enabled", self.service.is_enabled())
+
+        # We do not let the system start IPA components on its own,
+        # Instead we reply on the IPA init script to start only enabled
+        # components as found in our LDAP configuration tree
+        self.service.ldap_enable('HTTP', self.api.env.host, None, self.api.env.basedn)
+
+    @enable.unconfigurator
+    def enable(self):
+        enabled = self.service.restore_state("enabled")
+
+        yield
 
         # disabled by default, by ldap_enable()
         if enabled:
-            self.enable()
+            self.service.enable()
+
+
+class HTTPInstance(service.Service):
+    def __init__(self, api=api):
+        service.Service.__init__(self, "httpd", service_desc="the web interface")
+
+        self.fqdn = api.env.host
+        self.realm = api.env.realm
+        self.suffix = api.env.basedn
+        self.principal = "HTTP/%s@%s" % (self.fqdn, self.realm)
+
+    def remove_httpd_ccache(self):
+        # Clean up existing ccache
+        # Make sure that empty env is passed to avoid passing KRB5CCNAME from
+        # current env
+        ipautil.run(['kdestroy', '-A'], runas='apache', raiseonerr=False, env={})
 
     def stop_tracking_certificates(self):
-        db = certs.CertDB(api.env.realm)
-        db.untrack_server_cert(self.cert_nickname)
+        db = certs.CertDB(self.realm)
+        db.untrack_server_cert('Server-Cert')
-- 
2.1.0

-- 
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