From 136220265324111f77e3eec4b162a5df80623d07 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Tue, 13 Sep 2011 00:01:23 +0300 Subject: Introduce platform-specific adaptation for services used by FreeIPA. Refactor FreeIPA code to allow abstracting all calls to external processes and dependencies on modification of system-wide configuration. A platform provider would give its own implementation of those methods and FreeIPA would use it based on what's built in packaging process. https://fedorahosted.org/freeipa/ticket/1605 --- Makefile | 8 ++ freeipa.spec.in | 5 ++ ipapython/Makefile | 2 +- ipapython/ipautil.py | 47 +---------- ipapython/platform/__init__.py | 23 ++++++ ipapython/platform/base.py | 150 +++++++++++++++++++++++++++++++++++ ipapython/platform/redhat.py | 176 +++++++++++++++++++++++++++++++++++++++++ ipapython/services.py.in | 48 +++++++++++ ipapython/setup.py.in | 2 +- ipapython/sysrestore.py | 5 +- ipaserver/install/service.py | 68 +++++----------- 11 files changed, 436 insertions(+), 98 deletions(-) create mode 100644 ipapython/platform/__init__.py create mode 100644 ipapython/platform/base.py create mode 100644 ipapython/platform/redhat.py create mode 100644 ipapython/services.py.in diff --git a/Makefile b/Makefile index 6484dbbc9..4977f82fa 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ PRJ_PREFIX=freeipa RPMBUILD ?= $(PWD)/rpmbuild TARGET ?= master +SUPPORTED_PLATFORM=redhat + # After updating the version in VERSION you should run the version-update # target. @@ -109,6 +111,12 @@ version-update: release-update ipa-client/ipa-client.spec.in > ipa-client/ipa-client.spec sed -e s/__VERSION__/$(IPA_VERSION)/ ipa-client/version.m4.in \ > ipa-client/version.m4 + + if [ "$(SUPPORTED_PLATFORM)" != "" ]; then \ + sed -e s/SUPPORTED_PLATFORM/$(SUPPORTED_PLATFORM)/ ipapython/services.py.in \ + > ipapython/services.py; \ + fi + if [ "$(SKIP_API_VERSION_CHECK)" != "yes" ]; then \ ./makeapi --validate; \ fi diff --git a/freeipa.spec.in b/freeipa.spec.in index fc7141cc1..0f358fb4c 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -531,7 +531,9 @@ fi %defattr(-,root,root,-) %doc COPYING README Contributors.txt %dir %{python_sitelib}/ipapython +%dir %{python_sitelib}/ipapython/platform %{python_sitelib}/ipapython/*.py* +%{python_sitelib}/ipapython/platform/*.py* %dir %{python_sitelib}/ipalib %{python_sitelib}/ipalib/* %{python_sitearch}/default_encoding_utf8.so @@ -543,6 +545,9 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf %changelog +* Tue Sep 13 2011 Alexander Bokovoy - 2.99.0-5 +- Make sure platform adaptation is packaged in -python sub-package + * Fri Sep 9 2011 Martin Kosek - 2.99.0-4 - Add soft dependency for bind and bind-dyndb-ldap required versions diff --git a/ipapython/Makefile b/ipapython/Makefile index c96d5d9c1..a09ffd1bb 100644 --- a/ipapython/Makefile +++ b/ipapython/Makefile @@ -27,7 +27,7 @@ clean: done distclean: clean - rm -f setup.py ipa-python.spec version.py + rm -f setup.py ipa-python.spec version.py services.py @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ done diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 97284b384..72cf400f9 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -1,6 +1,6 @@ # Authors: Simo Sorce # -# Copyright (C) 2007 Red Hat +# Copyright (C) 2007-2011 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify @@ -1053,51 +1053,6 @@ def get_gsserror(e): return (major, minor) -def service_stop(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "stop", instance_name], - capture_output=capture_output) - -def service_start(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "start", instance_name], - capture_output=capture_output) - -def service_restart(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "restart", instance_name], - capture_output=capture_output) - -def service_is_running(service_name, instance_name=""): - ret = True - try: - run(["/sbin/service", service_name, "status", instance_name]) - except CalledProcessError: - ret = False - return ret - -def service_is_installed(service_name): - installed = True - try: - run(["/sbin/service", service_name, "status"]) - except CalledProcessError, e: - if e.returncode == 1: - # service is not installed or there is other serious issue - installed = False - return installed - -def service_is_enabled(service_name): - (stdout, stderr, returncode) = run(["/sbin/chkconfig", service_name], raiseonerr=False) - return (returncode == 0) - -def chkconfig_on(service_name): - run(["/sbin/chkconfig", service_name, "on"]) - -def chkconfig_off(service_name): - run(["/sbin/chkconfig", service_name, "off"]) - -def chkconfig_add(service_name): - run(["/sbin/chkconfig", "--add", service_name]) - -def chkconfig_del(service_name): - run(["/sbin/chkconfig", "--del", service_name]) def host_port_open(host, port, socket_stream=True, socket_timeout=None): families = (socket.AF_INET, socket.AF_INET6) diff --git a/ipapython/platform/__init__.py b/ipapython/platform/__init__.py new file mode 100644 index 000000000..e0a394b02 --- /dev/null +++ b/ipapython/platform/__init__.py @@ -0,0 +1,23 @@ +# Authors: +# Alexander Bokovoy +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Sub-package containing all platform-specific adaptation for ipapython.services. +Should not be used directly. +""" diff --git a/ipapython/platform/base.py b/ipapython/platform/base.py new file mode 100644 index 000000000..f9d409972 --- /dev/null +++ b/ipapython/platform/base.py @@ -0,0 +1,150 @@ +# Authors: Alexander Bokovoy +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ipalib.plugable import MagicDict + +# Canonical names of services as IPA wants to see them. As we need to have *some* naming, +# set them as in Red Hat distributions. Actual implementation should make them available +# through knownservices. and take care of remapping internally, if needed +wellknownservices = ['certmonger', 'dirsrv', 'httpd', 'ipa', 'krb5kdc', 'messagebus', + 'nslcd', 'nscd', 'ntpd', 'portmap', 'rpcbind'] + +class AuthConfig(object): + """ + AuthConfig class implements system-independent interface to configure + system authentication resources. In Red Hat systems this is done with + authconfig(8) utility. + + AuthConfig class is nothing more than a tool to gather configuration options + and execute their processing. These options then converted by an actual implementation + to series of a system calls to appropriate utilities performing real configuration. + + IPA *expects* names of AuthConfig's options to follow authconfig(8) naming scheme! + + Actual implementation should be done in ipapython/platform/.py by inheriting from + platform.AuthConfig and redefining __build_args() and execute() methods. + + from ipapython.platform import platform + class PlatformAuthConfig(platform.AuthConfig): + def __build_args(): + ... + + def execute(): + ... + + authconfig = PlatformAuthConfig + .... + + See ipapython/platform/redhat.py for a sample implementation that uses authconfig(8) as its backend. + + From IPA code perspective, the authentication configuration should be done with use of ipapython.services.authconfig: + + from ipapython import services as ipaservices + auth_config = ipaservices.authconfig() + auth_config.disable("ldap").\ + disable("krb5").\ + disable("sssd").\ + disable("sssdauth").\ + disable("mkhomedir").\ + add_option("update").\ + enable("nis").\ + add_parameter("nisdomain","foobar") + auth_config.execute() + + If you need to re-use existing AuthConfig instance for multiple runs, make sure to + call 'AuthConfig.reset()' between the runs. + """ + + def __init__(self): + self.parameters = {} + + def enable(self, option): + self.parameters[option] = True + return self + + def disable(self, option): + self.parameters[option] = False + return self + + def add_option(self, option): + self.parameters[option] = None + return self + + def add_parameter(self, option, value): + self.parameters[option] = [value] + return self + + def __build_args(self): + # do nothing + return None + + def execute(self): + # do nothing + return None + + def reset(self): + self.parameters = {} + return self + +class PlatformService(object): + """ + PlatformService abstracts out external process running on the system which is possible + to administer (start, stop, check status, etc). + + """ + + def __init__(self, service_name): + self.service_name = service_name + + def start(self, instance_name="", capture_output=True): + return + + def stop(self, instance_name="", capture_output=True): + return + + def restart(self, instance_name="", capture_output=True): + return + + def is_running(self): + return False + + def is_installed(self): + return False + + def is_enabled(self): + return False + + def enable(self): + return + + def disable(self): + return + + def install(self): + return + + def remove(self): + return + +class KnownServices(MagicDict): + """ + KnownServices is an abstract class factory that should give out instances of well-known + platform services. Actual implementation must create these instances as its own attributes + on first access (or instance creation) and cache them. + """ + diff --git a/ipapython/platform/redhat.py b/ipapython/platform/redhat.py new file mode 100644 index 000000000..6d1d42368 --- /dev/null +++ b/ipapython/platform/redhat.py @@ -0,0 +1,176 @@ +# Authors: Simo Sorce +# Alexander Bokovoy +# +# Copyright (C) 2007-2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import tempfile +import re +import os +import stat +import sys +from ipapython import ipautil +from ipapython.platform import base + +# All what we allow exporting directly from this module +# Everything else is made available through these symbols when they directly imported into ipapython.services: +# authconfig -- class reference for platform-specific implementation of authconfig(8) +# service -- class reference for platform-specific implementation of a PlatformService class +# knownservices -- factory instance to access named services IPA cares about, names are ipapython.services.wellknownservices +# backup_and_replace_hostname -- platform-specific way to set hostname and make it persistent over reboots +# restore_context -- platform-sepcific way to restore security context, if applicable +__all__ = ['authconfig', 'service', 'knownservices', 'backup_and_replace_hostname', 'restore_context'] + +class RedHatService(base.PlatformService): + def stop(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "stop", instance_name], capture_output=capture_output) + + def start(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "start", instance_name], capture_output=capture_output) + + def restart(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "restart", instance_name], capture_output=capture_output) + + def is_running(self, instance_name=""): + ret = True + try: + (sout,serr,rcode) = ipautil.run(["/sbin/service", self.service_name, "status", instance_name]) + if sout.find("is stopped") >= 0: + ret = False + except ipautil.CalledProcessError: + ret = False + return ret + + def is_installed(self): + installed = True + try: + ipautil.run(["/sbin/service", self.service_name, "status"]) + except ipautil.CalledProcessError, e: + if e.returncode == 1: + # service is not installed or there is other serious issue + installed = False + return installed + + def is_enabled(self): + (stdout, stderr, returncode) = ipautil.run(["/sbin/chkconfig", self.service_name],raiseonerr=False) + return (returncode == 0) + + def enable(self): + ipautil.run(["/sbin/chkconfig", self.service_name, "on"]) + + def disable(self): + ipautil.run(["/sbin/chkconfig", self.service_name, "off"]) + + def install(self): + ipautil.run(["/sbin/chkconfig", "--add", self.service_name]) + + def remove(self): + ipautil.run(["/sbin/chkconfig", "--del", self.service_name]) + +class RedHatAuthConfig(base.AuthConfig): + """ + AuthConfig class implements system-independent interface to configure + system authentication resources. In Red Hat-produced systems this is done with + authconfig(8) utility. + """ + def __build_args(self): + args = [] + for (option, value) in self.parameters.items(): + if type(value) is bool: + if value: + args.append("--enable%s" % (option)) + else: + args.append("--disable%s" % (option)) + elif type(value) in (tuple, list): + args.append("--%s" % (option)) + args.append("%s" % (value[0])) + elif value is None: + args.append("--%s" % (option)) + else: + args.append("--%s%s" % (option,value)) + return args + + def execute(self): + args = self.__build_args() + ipautil.run(["/usr/sbin/authconfig"]+args) + +class RedHatServices(base.KnownServices): + def __init__(self): + services = dict() + for s in base.wellknownservices: + services[s] = RedHatService(s) + # Call base class constructor. This will lock services to read-only + super(RedHatServices, self).__init__(services) + +authconfig = RedHatAuthConfig +service = RedHatService +knownservices = RedHatServices() + +def restore_context(filepath): + """ + restore security context on the file path + SE Linux equivalent is /sbin/restorecon + """ + ipautil.run(["/sbin/restorecon", filepath]) + + +def backup_and_replace_hostname(fstore, statestore, hostname): + network_filename = "/etc/sysconfig/network" + # Backup original /etc/sysconfig/network + fstore.backup_file(network_filename) + hostname_pattern = re.compile(''' +(^ + \s* + (?P