URL: https://github.com/freeipa/freeipa/pull/252 Author: tiran Title: #252: Use namespace-aware meta importer for ipaplatform Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/252/head:pr252 git checkout pr252
From 01e05c67f7fccf2f04215e6c245da98344a9ed62 Mon Sep 17 00:00:00 2001 From: Christian Heimes <chei...@redhat.com> Date: Fri, 28 Oct 2016 09:23:48 +0200 Subject: [PATCH] Use namespace-aware meta importer for ipaplatform Instead of symlinks and build-time configuration the ipaplatform module is now able to auto-detect platforms on import time. The meta importer uses the platform 'ID' from /etc/os-releases. It falls back to 'ID_LIKE' on platforms like CentOS, which has ID=centos and ID_LIKE="rhel fedora". The meta importer is able to handle namespace packages and the ipaplatform package has been turned into a namespace package in order to support external platform specifications. https://fedorahosted.org/freeipa/ticket/6474 Signed-off-by: Christian Heimes <chei...@redhat.com> --- .gitignore | 5 --- configure.ac | 38 ----------------- freeipa.spec.in | 1 + ignore_import_errors.py | 7 +++- ipaplatform/__init__.py | 24 +++++++++++ ipaplatform/_importhook.py | 96 +++++++++++++++++++++++++++++++++++++++++++ ipaplatform/base/constants.py | 2 + ipaplatform/base/paths.py | 2 +- ipaplatform/base/services.py | 8 +++- ipaplatform/base/tasks.py | 2 + ipaplatform/constants.py | 10 +++++ ipaplatform/paths.py | 10 +++++ ipaplatform/services.py | 11 +++++ ipaplatform/setup.py | 1 + ipaplatform/tasks.py | 10 +++++ 15 files changed, 180 insertions(+), 47 deletions(-) create mode 100644 ipaplatform/__init__.py create mode 100644 ipaplatform/_importhook.py create mode 100644 ipaplatform/constants.py create mode 100644 ipaplatform/paths.py create mode 100644 ipaplatform/services.py create mode 100644 ipaplatform/tasks.py diff --git a/.gitignore b/.gitignore index 2bacc85..428049b 100644 --- a/.gitignore +++ b/.gitignore @@ -83,8 +83,3 @@ freeipa2-dev-doc /ipapython/version.py -/ipaplatform/__init__.py -/ipaplatform/constants.py -/ipaplatform/paths.py -/ipaplatform/services.py -/ipaplatform/tasks.py diff --git a/configure.ac b/configure.ac index b042455..a121956 100644 --- a/configure.ac +++ b/configure.ac @@ -319,35 +319,6 @@ if test "x$MSGATTRIB" = "xno"; then fi dnl --------------------------------------------------------------------------- -dnl IPA platform -dnl --------------------------------------------------------------------------- -AC_ARG_WITH([ipaplatform], - [AC_HELP_STRING([--with-ipaplatform], - [IPA platform module to use])], - [IPAPLATFORM=${withval}], - [IPAPLATFORM=""]) -AC_MSG_CHECKING([supported IPA platform]) - -if test "x${IPAPLATFORM}" == "x"; then - if test -r "/etc/os-release"; then - IPAPLATFORM=$(. /etc/os-release; echo "$ID") - else - AC_MSG_ERROR([unable to read /etc/os-release]) - fi - if test "x${IPAPLATFORM}" == "x"; then - AC_MSG_ERROR([unable to find ID variable in /etc/os-release]) - fi -fi - -if test ! -d "${srcdir}/ipaplatform/${IPAPLATFORM}"; then - AC_MSG_ERROR([IPA platform ${IPAPLATFORM} is not supported]) -fi - -AC_SUBST([IPAPLATFORM]) -AC_MSG_RESULT([${IPAPLATFORM}]) - - -dnl --------------------------------------------------------------------------- dnl Version information from VERSION.m4 and command line dnl --------------------------------------------------------------------------- dnl Are we in source tree? @@ -468,15 +439,6 @@ AC_SUBST(CFLAGS) AC_SUBST(CPPFLAGS) AC_SUBST(LDFLAGS) - -# Files -AC_CONFIG_LINKS([ipaplatform/__init__.py:ipaplatform/$IPAPLATFORM/__init__.py - ipaplatform/constants.py:ipaplatform/$IPAPLATFORM/constants.py - ipaplatform/paths.py:ipaplatform/$IPAPLATFORM/paths.py - ipaplatform/services.py:ipaplatform/$IPAPLATFORM/services.py - ipaplatform/tasks.py:ipaplatform/$IPAPLATFORM/tasks.py - ]) - AC_CONFIG_FILES([ Makefile asn1/Makefile diff --git a/freeipa.spec.in b/freeipa.spec.in index ee5e450..7d0bec8 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1298,6 +1298,7 @@ fi %{python_sitelib}/ipapython-*.egg-info %{python_sitelib}/ipalib-*.egg-info %{python_sitelib}/ipaplatform-*.egg-info +%{python_sitelib}/ipaplatform-*-nspkg.pth %files common -f %{gettext_domain}.lang diff --git a/ignore_import_errors.py b/ignore_import_errors.py index 4ee6ee9..7f3ee50 100644 --- a/ignore_import_errors.py +++ b/ignore_import_errors.py @@ -6,13 +6,18 @@ ImportError ignoring import hook. """ -from __future__ import print_function +from __future__ import absolute_import, print_function import imp import inspect import os.path import sys +# Load ipaplatform's meta importer before IgnoreImporter is registered as +# meta importer. +import ipaplatform.paths # pylint: disable=unused-import + + DIRNAME = os.path.dirname(os.path.abspath(__file__)) diff --git a/ipaplatform/__init__.py b/ipaplatform/__init__.py new file mode 100644 index 0000000..4ce1057 --- /dev/null +++ b/ipaplatform/__init__.py @@ -0,0 +1,24 @@ +# Copyright (C) 2016 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +"""ipaplatform namespace package + +In the presence of a namespace package, any code in this module will be +ignore. +""" +__import__('pkg_resources').declare_namespace(__name__) + +NAME = None # initialized by IpaMetaImporter diff --git a/ipaplatform/_importhook.py b/ipaplatform/_importhook.py new file mode 100644 index 0000000..29d5fe2 --- /dev/null +++ b/ipaplatform/_importhook.py @@ -0,0 +1,96 @@ +# Copyright (C) 2016 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import importlib +import sys + +import ipaplatform + + +class IpaMetaImporter(object): + """Meta import hook and platform detector. + + The meta import hook uses /etc/os-release to auto-detects the best + matching ipaplatform provider. It is compatible with external namespace + packages, too. + """ + modules = { + 'ipaplatform.constants', + 'ipaplatform.paths', + 'ipaplatform.services', + 'ipaplatform.tasks' + } + + def __init__(self): + self.platform_ids = self._read_osrelease() + self.platform = self._get_platform() + # fix modules that have been loaded + for module in self.modules: + if module in sys.modules: + self.load_module(module) + + def _read_osrelease(self, filename='/etc/os-release'): + platforms = [] + with open(filename) as f: + for line in f: + key, value = line.rstrip('\n').split('=', 1) + if value.startswith(('"', "'")): + value = value[1:-1] + if key == 'ID': + platforms.insert(0, value) + # fallback to base distro, centos has ID_LIKE="rhel fedora" + if key == 'ID_LIKE': + platforms.extend(value.split(' ')) + return platforms + + def _get_platform(self): + for platform in self.platform_ids: + try: + importlib.import_module('ipaplatform.{}'.format(platform)) + except ImportError: + pass + else: + return platform + raise ImportError('No ipaplatform available for "{}"'.format( + ', '.join(self.platform_ids))) + + def find_module(self, fullname, path=None): + """Meta importer hook""" + if fullname in self.modules: + return self + return None + + def load_module(self, fullname): + """Meta importer hook""" + suffix = fullname.split('.', 1)[1] + alias = 'ipaplatform.{}.{}'.format(self.platform, suffix) + platform_mod = importlib.import_module(alias) + base_mod = sys.modules.get(fullname) + if base_mod is not None: + # module has been imported before, update its __dict__ + base_mod.__dict__.clear() + base_mod.__dict__.update(platform_mod.__dict__) + else: + sys.modules[fullname] = platform_mod + return platform_mod + + +metaimporter = IpaMetaImporter() +sys.meta_path.insert(0, metaimporter) + +if ipaplatform.NAME is None: + ipaplatform.NAME = metaimporter.platform diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py index 3e1c4c6..44704c4 100644 --- a/ipaplatform/base/constants.py +++ b/ipaplatform/base/constants.py @@ -26,3 +26,5 @@ class BaseConstantsNamespace(object): # nfsd init variable used to enable kerberized NFS SECURE_NFS_VAR = "SECURE_NFS" SSSD_USER = "sssd" + +constants = BaseConstantsNamespace() diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index bbf6b53..bf924f8 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -368,4 +368,4 @@ def USER_CACHE_PATH(self): ) ) -path_namespace = BasePathNamespace +paths = BasePathNamespace() diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py index 750d979..8d51da3 100644 --- a/ipaplatform/base/services.py +++ b/ipaplatform/base/services.py @@ -483,8 +483,12 @@ def remove(self): # Objects below are expected to be exported by platform module -service = None -knownservices = None +def base_service_class_factory(name): + raise NotImplementedError + + +service = base_service_class_factory +knownservices = KnownServices({}) # System may support more time&date services. FreeIPA supports ntpd only, other # services will be disabled during IPA installation diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py index 1e687b6..533834b 100644 --- a/ipaplatform/base/tasks.py +++ b/ipaplatform/base/tasks.py @@ -249,3 +249,5 @@ def configure_httpd_service_ipa_conf(self): def remove_httpd_service_ipa_conf(self): """Remove configuration of httpd service of IPA""" raise NotImplementedError() + +tasks = BaseTaskNamespace() diff --git a/ipaplatform/constants.py b/ipaplatform/constants.py new file mode 100644 index 0000000..42a940d --- /dev/null +++ b/ipaplatform/constants.py @@ -0,0 +1,10 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.constants. +""" +# flake8: noqa +# pylint: disable=unused-import + +from .base.constants import constants +from . import _importhook diff --git a/ipaplatform/paths.py b/ipaplatform/paths.py new file mode 100644 index 0000000..90da40a --- /dev/null +++ b/ipaplatform/paths.py @@ -0,0 +1,10 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# +"""This module will be shadowed by IpaMetaImporter +""" +# flake8: noqa +# pylint: disable=unused-import + +from .base.paths import paths +from . import _importhook diff --git a/ipaplatform/services.py b/ipaplatform/services.py new file mode 100644 index 0000000..9e27db2 --- /dev/null +++ b/ipaplatform/services.py @@ -0,0 +1,11 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.services. +""" +# flake8: noqa +# pylint: disable=unused-import + +from .base.services import wellknownservices, wellknownports +from .base.services import service, knownservices, timedate_services +from . import _importhook diff --git a/ipaplatform/setup.py b/ipaplatform/setup.py index 97311de..402320d 100644 --- a/ipaplatform/setup.py +++ b/ipaplatform/setup.py @@ -32,6 +32,7 @@ name="ipaplatform", doc=__doc__, package_dir={'ipaplatform': ''}, + namespace_packages=['ipaplatform'], packages=[ "ipaplatform", "ipaplatform.base", diff --git a/ipaplatform/tasks.py b/ipaplatform/tasks.py new file mode 100644 index 0000000..3283a77 --- /dev/null +++ b/ipaplatform/tasks.py @@ -0,0 +1,10 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.tasks. +""" +# flake8: noqa +# pylint: disable=unused-import + +from .base.tasks import tasks +from . import _importhook
-- 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