URL: https://github.com/freeipa/freeipa/pull/1148 Author: tiran Title: #1148: Use namespace-aware meta importer for ipaplatform Action: opened
PR body: """ 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> """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/1148/head:pr1148 git checkout pr1148
From 595b134c8938e13d2e8f5fcf5905dfab6dd9b45d Mon Sep 17 00:00:00 2001 From: Christian Heimes <chei...@redhat.com> Date: Wed, 11 Oct 2017 12:09:30 +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 | 6 +- Makefile.am | 6 +- configure.ac | 8 -- freeipa.spec.in | 1 + ignore_import_errors.py | 7 +- ipalib/config.py | 11 +-- ipalib/setup.py | 1 + ipalib/util.py | 8 +- ipaplatform/Makefile.am | 11 +++ ipaplatform/__init__.py | 24 +++++ ipaplatform/_importhook.py | 125 +++++++++++++++++++++++++++ ipaplatform/base/constants.py | 2 + ipaplatform/base/paths.py | 3 +- ipaplatform/base/services.py | 8 +- ipaplatform/base/tasks.py | 5 ++ ipaplatform/constants.py | 8 ++ ipaplatform/fallback.py.in | 1 + ipaplatform/paths.py | 8 ++ ipaplatform/services.py | 8 ++ ipaplatform/setup.py | 1 + ipaplatform/tasks.py | 8 ++ ipapython/certdb.py | 28 ++---- ipapython/config.py | 13 +-- ipapython/ipautil.py | 2 +- ipapython/setup.py | 1 + ipasetup.py.in | 13 ++- ipatests/test_ipaplatform/__init__.py | 0 ipatests/test_ipaplatform/test_importhook.py | 36 ++++++++ pylint_plugins.py | 42 +++++++++ pylintrc | 6 +- pypi/Makefile.am | 1 - pypi/ipaplatform/Makefile.am | 3 - pypi/ipaplatform/README.txt | 2 - pypi/ipaplatform/ipaplatform/__init__.py | 5 -- pypi/ipaplatform/setup.cfg | 6 -- pypi/ipaplatform/setup.py | 26 ------ pypi/test_placeholder.py | 4 +- 37 files changed, 334 insertions(+), 114 deletions(-) create mode 100644 ipaplatform/__init__.py create mode 100644 ipaplatform/_importhook.py create mode 100644 ipaplatform/constants.py create mode 100644 ipaplatform/fallback.py.in create mode 100644 ipaplatform/paths.py create mode 100644 ipaplatform/services.py create mode 100644 ipaplatform/tasks.py create mode 100644 ipatests/test_ipaplatform/__init__.py create mode 100644 ipatests/test_ipaplatform/test_importhook.py delete mode 100644 pypi/ipaplatform/Makefile.am delete mode 100644 pypi/ipaplatform/README.txt delete mode 100644 pypi/ipaplatform/ipaplatform/__init__.py delete mode 100644 pypi/ipaplatform/setup.cfg delete mode 100755 pypi/ipaplatform/setup.py diff --git a/.gitignore b/.gitignore index 8f4c2aa7a9..2f90ab0069 100644 --- a/.gitignore +++ b/.gitignore @@ -108,11 +108,7 @@ freeipa2-dev-doc /client/ipa-join /client/ipa-rmkeytab +/ipaplatform/fallback.py /ipapython/version.py /ipapython/.DEFAULT_PLUGINS -/ipaplatform/__init__.py -/ipaplatform/constants.py -/ipaplatform/paths.py -/ipaplatform/services.py -/ipaplatform/tasks.py diff --git a/Makefile.am b/Makefile.am index 02e53f550c..2e37c5b9b6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,10 +8,10 @@ if WITH_IPATESTS IPATESTS_SUBDIRS = ipatests endif -IPACLIENT_SUBDIRS = ipaclient ipalib ipapython -IPA_PLACEHOLDERS = freeipa ipa ipaplatform ipaserver ipatests +IPACLIENT_SUBDIRS = ipaclient ipalib ipaplatform ipapython +IPA_PLACEHOLDERS = freeipa ipa ipaserver ipatests SUBDIRS = asn1 util client contrib po pypi \ - $(IPACLIENT_SUBDIRS) ipaplatform $(IPATESTS_SUBDIRS) $(SERVER_SUBDIRS) + $(IPACLIENT_SUBDIRS) $(IPATESTS_SUBDIRS) $(SERVER_SUBDIRS) MOSTLYCLEANFILES = ipasetup.pyc ipasetup.pyo \ ignore_import_errors.pyc ignore_import_errors.pyo \ diff --git a/configure.ac b/configure.ac index f098eb1dac..699fd64cba 100644 --- a/configure.ac +++ b/configure.ac @@ -520,13 +520,6 @@ 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 @@ -594,7 +587,6 @@ AC_CONFIG_FILES([ pypi/Makefile pypi/freeipa/Makefile pypi/ipa/Makefile - pypi/ipaplatform/Makefile pypi/ipaserver/Makefile pypi/ipatests/Makefile po/Makefile.in diff --git a/freeipa.spec.in b/freeipa.spec.in index 8b7f179da4..b8bafacdbf 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1610,6 +1610,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 4ee6ee98bc..7f3ee50d39 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/ipalib/config.py b/ipalib/config.py index 151c4b4a8f..c714c137fc 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -28,6 +28,7 @@ For the per-request thread-local information, see `ipalib.request`. """ +from __future__ import absolute_import import os from os import path @@ -39,6 +40,7 @@ from six.moves.configparser import RawConfigParser, ParsingError # pylint: enable=import-error +from ipaplatform.tasks import tasks from ipapython.dn import DN from ipalib.base import check_name from ipalib.constants import ( @@ -47,12 +49,6 @@ TLS_VERSIONS ) from ipalib import errors -try: - # pylint: disable=ipa-forbidden-import - from ipaplatform.tasks import tasks - # pylint: enable=ipa-forbidden-import -except ImportError: - tasks = None if six.PY3: unicode = str @@ -453,8 +449,7 @@ def _bootstrap(self, **overrides): self.home = os.environ.get('HOME', None) # Set fips_mode only if ipaplatform module was loaded - if tasks is not None: - self.fips_mode = tasks.is_fips_enabled() + self.fips_mode = tasks.is_fips_enabled() # Merge in overrides: self._merge(**overrides) diff --git a/ipalib/setup.py b/ipalib/setup.py index cdbd61c012..722fcebd31 100644 --- a/ipalib/setup.py +++ b/ipalib/setup.py @@ -37,6 +37,7 @@ "ipalib.install", ], install_requires=[ + "ipaplatform", "ipapython", "netaddr", "pyasn1", diff --git a/ipalib/util.py b/ipalib/util.py index 91d6e469a5..3601fd0f9f 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -55,10 +55,7 @@ TLS_VERSIONS, TLS_VERSION_MINIMAL, TLS_HIGH_CIPHERS ) from ipalib.text import _ -# pylint: disable=ipa-forbidden-import -from ipalib.install import sysrestore from ipaplatform.paths import paths -# pylint: enable=ipa-forbidden-import from ipapython.ssh import SSHPublicKey from ipapython.dn import DN, RDN from ipapython.dnsutil import DNSName @@ -1078,8 +1075,9 @@ def check_client_configuration(): """ Check if IPA client is configured on the system. """ - fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) - if not fstore.has_files() and not os.path.exists(paths.IPA_DEFAULT_CONF): + if (not os.path.isfile(paths.IPA_DEFAULT_CONF) or + not os.path.isdir(paths.IPA_CLIENT_SYSRESTORE) or + not os.listdir(paths.IPA_CLIENT_SYSRESTORE)): raise ScriptError('IPA client is not configured on this system') diff --git a/ipaplatform/Makefile.am b/ipaplatform/Makefile.am index 8be72b25da..61b5f53516 100644 --- a/ipaplatform/Makefile.am +++ b/ipaplatform/Makefile.am @@ -1 +1,12 @@ include $(top_srcdir)/Makefile.python.am + +EXTRA_DIST = fallback.py.in + +all-local: fallback.py +dist-hook: fallback.py +install-exec-local: fallback.py + +fallback.py: fallback.py.in $(top_builddir)/$(CONFIG_STATUS) + $(AM_V_GEN)sed \ + -e 's|@IPAPLATFORM[@]|$(IPAPLATFORM)|g' \ + $< > $@ diff --git a/ipaplatform/__init__.py b/ipaplatform/__init__.py new file mode 100644 index 0000000000..4ce1057239 --- /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 0000000000..23c034891b --- /dev/null +++ b/ipaplatform/_importhook.py @@ -0,0 +1,125 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +"""Meta import hook for ipaplatform. + +Known Linux distros with /etc/os-release +---------------------------------------- + +- alpine +- centos (like rhel, fedora) +- debian +- fedora +- rhel +- ubuntu (like debian) +""" + +import importlib +import sys +import warnings + +import ipaplatform +try: + from ipaplatform.fallback import FALLBACK +except ImportError: + FALLBACK = None + + +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' + } + bsd_family = ('freebsd', 'openbsd', 'netbsd', 'dragonfly', 'gnukfreebsd') + + def __init__(self): + self.platform_ids = self._get_platform_ids() + self.platform = self._get_platform(self.platform_ids) + + def _get_platform_ids(self): + if sys.platform.startswith('linux'): + # Linux, get distribution from /etc/os-release + try: + platforms = self._read_osrelease() + except OSError as e: + warnings.warn("Failed to read /etc/os-release: {}".format(e)) + platforms = [] + elif sys.platform == 'win32': + # Windows 32 or 64bit platform + platforms = ['win32'] + elif sys.platform == 'darwin': + # macOS + platforms = ['macos'] + elif sys.platform.startswith(self.bsd_family): + # BSD family, look for e.g. ['freebsd10', 'freebsd'] + platforms = [sys.platform, sys.platform.rstrip('0123456789')] + else: + raise ValueError(sys.platform) + # Allow official packages to provide a fallback + if FALLBACK is not None and FALLBACK not in platforms: + platforms.append(FALLBACK) + return platforms + + 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( + v.strip() for v in value.split(' ') if v.strip() + ) + return platforms + + def _get_platform(self, platform_ids): + for platform in platform_ids: + try: + importlib.import_module('ipaplatform.{}'.format(platform)) + except ImportError: + pass + else: + return platform + raise ImportError('No ipaplatform available for "{}"'.format( + ', '.join(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__.update(platform_mod.__dict__) + for key in list(base_mod.__dict__): + if not hasattr(platform_mod, key): + delattr(base_mod, key) + else: + sys.modules[fullname] = platform_mod + return platform_mod + + +metaimporter = IpaMetaImporter() +sys.meta_path.insert(0, metaimporter) + +fixup_module = metaimporter.load_module +ipaplatform.NAME = metaimporter.platform diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py index 6592c63d97..f97f299704 100644 --- a/ipaplatform/base/constants.py +++ b/ipaplatform/base/constants.py @@ -37,3 +37,5 @@ class BaseConstantsNamespace(object): 'httpd_dbus_sssd': 'on', } SSSD_USER = "sssd" + +constants = BaseConstantsNamespace() diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index 2d96dc2efa..c27adfe711 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -356,5 +356,6 @@ class BasePathNamespace(object): GSSPROXY_CONF = '/etc/gssproxy/10-ipa.conf' KRB5CC_HTTPD = '/tmp/krb5cc-httpd' IF_INET6 = '/proc/net/if_inet6' + AUTHCONFIG = None -path_namespace = BasePathNamespace +paths = BasePathNamespace() diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py index fca6298fc0..712052112f 100644 --- a/ipaplatform/base/services.py +++ b/ipaplatform/base/services.py @@ -505,8 +505,12 @@ def remove(self): # Objects below are expected to be exported by platform module -service = None -knownservices = None +def base_service_class_factory(name, api=None): + 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 dc3cacc237..bea49ca61d 100644 --- a/ipaplatform/base/tasks.py +++ b/ipaplatform/base/tasks.py @@ -204,6 +204,9 @@ def configure_httpd_service_ipa_conf(self): """Configure httpd service to work with IPA""" raise NotImplementedError() + def configure_http_gssproxy_conf(self, ipauser): + raise NotImplementedError() + def remove_httpd_service_ipa_conf(self): """Remove configuration of httpd service of IPA""" raise NotImplementedError() @@ -219,3 +222,5 @@ def add_user_to_group(self, user, group): logger.debug('Done adding user to group') except ipautil.CalledProcessError as e: logger.debug('Failed to add user to group: %s', e) + +tasks = BaseTaskNamespace() diff --git a/ipaplatform/constants.py b/ipaplatform/constants.py new file mode 100644 index 0000000000..cc43cfb1df --- /dev/null +++ b/ipaplatform/constants.py @@ -0,0 +1,8 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.constants. +""" +import ipaplatform._importhook + +ipaplatform._importhook.fixup_module('ipaplatform.constants') diff --git a/ipaplatform/fallback.py.in b/ipaplatform/fallback.py.in new file mode 100644 index 0000000000..d0436ba0d7 --- /dev/null +++ b/ipaplatform/fallback.py.in @@ -0,0 +1 @@ +FALLBACK = '@IPAPLATFORM@' diff --git a/ipaplatform/paths.py b/ipaplatform/paths.py new file mode 100644 index 0000000000..2fcb477d42 --- /dev/null +++ b/ipaplatform/paths.py @@ -0,0 +1,8 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.paths. +""" +import ipaplatform._importhook + +ipaplatform._importhook.fixup_module('ipaplatform.paths') diff --git a/ipaplatform/services.py b/ipaplatform/services.py new file mode 100644 index 0000000000..0d40f64430 --- /dev/null +++ b/ipaplatform/services.py @@ -0,0 +1,8 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.services. +""" +import ipaplatform._importhook + +ipaplatform._importhook.fixup_module('ipaplatform.services') diff --git a/ipaplatform/setup.py b/ipaplatform/setup.py index 501e2bc568..1098ab6f10 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 0000000000..23c7859094 --- /dev/null +++ b/ipaplatform/tasks.py @@ -0,0 +1,8 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +"""IpaMetaImporter replaces this module with ipaplatform.$NAME.tasks. +""" +import ipaplatform._importhook + +ipaplatform._importhook.fixup_module('ipaplatform.tasks') diff --git a/ipapython/certdb.py b/ipapython/certdb.py index 92da7829ac..14e6adf69f 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # +from __future__ import absolute_import import collections import logging @@ -30,24 +31,12 @@ import cryptography.x509 +from ipaplatform.paths import paths from ipapython.dn import DN from ipapython.kerberos import Principal from ipapython import ipautil from ipalib import x509 # pylint: disable=ipa-forbidden-import -try: - # pylint: disable=import-error,ipa-forbidden-import - from ipaplatform.paths import paths - # pylint: enable=import-error,ipa-forbidden-import -except ImportError: - CERTUTIL = '/usr/bin/certutil' - PK12UTIL = '/usr/bin/pk12util' - OPENSSL = '/usr/bin/openssl' -else: - CERTUTIL = paths.CERTUTIL - PK12UTIL = paths.PK12UTIL - OPENSSL = paths.OPENSSL - logger = logging.getLogger(__name__) @@ -188,7 +177,8 @@ def verify_kdc_cert_validity(kdc_cert, ca_certs, realm): try: ipautil.run( - [OPENSSL, 'verify', '-CAfile', ca_file.name, kdc_file.name], + [paths.OPENSSL, 'verify', '-CAfile', ca_file.name, + kdc_file.name], capture_output=True) except ipautil.CalledProcessError as e: raise ValueError(e.output) @@ -244,7 +234,7 @@ def __exit__(self, type, value, tb): self.close() def run_certutil(self, args, stdin=None, **kwargs): - new_args = [CERTUTIL, "-d", self.secdir] + new_args = [paths.CERTUTIL, "-d", self.secdir] new_args = new_args + args new_args.extend(['-f', self.pwd_file]) return ipautil.run(new_args, stdin, **kwargs) @@ -367,7 +357,7 @@ def get_trust_chain(self, nickname): return root_nicknames def export_pkcs12(self, nickname, pkcs12_filename, pkcs12_passwd=None): - args = [PK12UTIL, "-d", self.secdir, + args = [paths.PK12UTIL, "-d", self.secdir, "-o", pkcs12_filename, "-n", nickname, "-k", self.pwd_file] @@ -391,7 +381,7 @@ def export_pkcs12(self, nickname, pkcs12_filename, pkcs12_passwd=None): pkcs12_password_file.close() def import_pkcs12(self, pkcs12_filename, pkcs12_passwd=None): - args = [PK12UTIL, "-d", self.secdir, + args = [paths.PK12UTIL, "-d", self.secdir, "-i", pkcs12_filename, "-k", self.pwd_file, '-v'] pkcs12_password_file = None @@ -501,7 +491,7 @@ def import_files(self, files, import_keys=False, key_password=None, (key_file, filename)) args = [ - OPENSSL, 'pkcs8', + paths.OPENSSL, 'pkcs8', '-topk8', '-passout', 'file:' + self.pwd_file, ] @@ -588,7 +578,7 @@ def import_files(self, files, import_keys=False, key_password=None, out_password = ipautil.ipa_generate_password() out_pwdfile = ipautil.write_tmp_file(out_password) args = [ - OPENSSL, 'pkcs12', + paths.OPENSSL, 'pkcs12', '-export', '-in', in_file.name, '-out', out_file.name, diff --git a/ipapython/config.py b/ipapython/config.py index 8393e0d5d5..7e2b2324c9 100644 --- a/ipapython/config.py +++ b/ipapython/config.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # +from __future__ import absolute_import # pylint: disable=deprecated-module from optparse import ( @@ -32,17 +33,9 @@ from six.moves.urllib.parse import urlsplit # pylint: enable=import-error +from ipaplatform.paths import paths from ipapython.dn import DN -try: - # pylint: disable=ipa-forbidden-import - from ipaplatform.paths import paths - # pylint: enable=ipa-forbidden-import -except ImportError: - IPA_DEFAULT_CONF = '/etc/ipa/default.conf' -else: - IPA_DEFAULT_CONF = paths.IPA_DEFAULT_CONF - class IPAConfigError(Exception): def __init__(self, msg=''): @@ -181,7 +174,7 @@ def get_domain(self): def __parse_config(discover_server = True): p = SafeConfigParser() - p.read(IPA_DEFAULT_CONF) + p.read(paths.IPA_DEFAULT_CONF) try: if not config.default_realm: diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index c5c5e9e213..462010f227 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -29,7 +29,7 @@ import os import sys import copy -import stat +import stat # pylint: disable=bad-python3-import import shutil import socket import re diff --git a/ipapython/setup.py b/ipapython/setup.py index 4f71530391..b982577d91 100755 --- a/ipapython/setup.py +++ b/ipapython/setup.py @@ -42,6 +42,7 @@ "dnspython", "gssapi", # "ipalib", # circular dependency + "ipaplatform", "netaddr", "netifaces", "six", diff --git a/ipasetup.py.in b/ipasetup.py.in index 2862ae234c..e8e80a4a85 100644 --- a/ipasetup.py.in +++ b/ipasetup.py.in @@ -25,22 +25,27 @@ class build_py(setuptools_build_py): """ def initialize_options(self): setuptools_build_py.initialize_options(self) - self.skip_package = None + self.skip_modules = () def finalize_options(self): setuptools_build_py.finalize_options(self) omit = os.environ.get('IPA_OMIT_INSTALL', '0') if omit == '1': distname = self.distribution.metadata.name - self.skip_package = '{}.install'.format(distname) + self.skip_modules = ( + # *.install.* subpackages + '{}.install'.format(distname), + # platform fallback override module + 'ipaplatform.fallback', + ) log.warn("bdist_wheel: Ignore package: %s", - self.skip_package) + ', '.join(self.skip_modules)) def build_module(self, module, module_file, package): if isinstance(package, str): package = package.split('.') name = '.'.join(list(package) + [module]) - if self.skip_package and name.startswith(self.skip_package): + if self.skip_modules and name.startswith(self.skip_modules): # remove file in case it has been copied to build/lib before outfile = self.get_module_outfile(self.build_lib, package, module) try: diff --git a/ipatests/test_ipaplatform/__init__.py b/ipatests/test_ipaplatform/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ipatests/test_ipaplatform/test_importhook.py b/ipatests/test_ipaplatform/test_importhook.py new file mode 100644 index 0000000000..a824b0a6a2 --- /dev/null +++ b/ipatests/test_ipaplatform/test_importhook.py @@ -0,0 +1,36 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# +import sys + +import pytest + +import ipaplatform +import ipaplatform.constants +import ipaplatform.paths +import ipaplatform.services +import ipaplatform.tasks +from ipaplatform._importhook import metaimporter +from ipaplatform.fallback import FALLBACK + + +@pytest.mark.skipif(not sys.platform.startswith('linux'), + reason='test requires linux') +def test_os_release(): + platforms = metaimporter._get_platform_ids() + assert FALLBACK in platforms + +@pytest.mark.parametrize('mod,name', [ + (ipaplatform.constants, 'ipaplatform.constants'), + (ipaplatform.paths, 'ipaplatform.paths'), + (ipaplatform.services, 'ipaplatform.services'), + (ipaplatform.tasks, 'ipaplatform.tasks'), +]) +def test_importhook(mod, name): + assert name in metaimporter.modules + prefix, suffix = name.split('.') + assert prefix == 'ipaplatform' + override = '.'.join((prefix, metaimporter.platform, suffix)) + assert mod.__name__ == override + # dicts are equal, modules may not be identical + assert mod.__dict__ == sys.modules[override].__dict__ diff --git a/pylint_plugins.py b/pylint_plugins.py index e5c999861e..0c8678be0e 100644 --- a/pylint_plugins.py +++ b/pylint_plugins.py @@ -269,6 +269,48 @@ def pytest_config_transform(): register_module_extender(MANAGER, 'pytest', pytest_config_transform) +def ipaplatform_constants_transform(): + return AstroidBuilder(MANAGER).string_build(textwrap.dedent(''' + from ipaplatform.base.constants import constants + __all__ = ('constants',) + ''')) + + +def ipaplatform_paths_transform(): + return AstroidBuilder(MANAGER).string_build(textwrap.dedent(''' + from ipaplatform.base.paths import paths + __all__ = ('paths',) + ''')) + + +def ipaplatform_services_transform(): + return AstroidBuilder(MANAGER).string_build(textwrap.dedent(''' + from ipaplatform.base.services import knownservices + from ipaplatform.base.services import timedate_services + from ipaplatform.base.services import service + from ipaplatform.base.services import wellknownservices + from ipaplatform.base.services import wellknownports + __all__ = ('knownservices', 'timedate_services', 'service', + 'wellknownservices', 'wellknownports') + ''')) + + +def ipaplatform_tasks_transform(): + return AstroidBuilder(MANAGER).string_build(textwrap.dedent(''' + from ipaplatform.base.tasks import tasks + __all__ = ('tasks',) + ''')) + +register_module_extender(MANAGER, 'ipaplatform.constants', + ipaplatform_constants_transform) +register_module_extender(MANAGER, 'ipaplatform.paths', + ipaplatform_paths_transform) +register_module_extender(MANAGER, 'ipaplatform.services', + ipaplatform_services_transform) +register_module_extender(MANAGER, 'ipaplatform.tasks', + ipaplatform_tasks_transform) + + class IPAChecker(BaseChecker): __implements__ = IAstroidChecker diff --git a/pylintrc b/pylintrc index 462b96cb00..8cd7c870bf 100644 --- a/pylintrc +++ b/pylintrc @@ -116,9 +116,9 @@ dummy-variables-rgx=_.+ [IPA] forbidden-imports= client/:ipaserver, - ipaclient/:ipaclient.install:ipalib.install:ipaplatform:ipaserver, + ipaclient/:ipaclient.install:ipalib.install:ipaserver, ipaclient/install/:ipaserver, - ipalib/:ipaclient.install:ipalib.install:ipaplatform:ipaserver, + ipalib/:ipaclient.install:ipalib.install:ipaserver, ipalib/install/:ipaserver, ipaplatform/:ipaclient:ipalib:ipaserver, - ipapython/:ipaclient:ipalib:ipaplatform:ipaserver + ipapython/:ipaclient:ipalib:ipaserver diff --git a/pypi/Makefile.am b/pypi/Makefile.am index 5d8be9c1f3..bcbe1ea41f 100644 --- a/pypi/Makefile.am +++ b/pypi/Makefile.am @@ -7,7 +7,6 @@ NULL = SUBDIRS = \ freeipa \ ipa \ - ipaplatform \ ipaserver \ ipatests \ $(NULL) diff --git a/pypi/ipaplatform/Makefile.am b/pypi/ipaplatform/Makefile.am deleted file mode 100644 index 15d86ce0c4..0000000000 --- a/pypi/ipaplatform/Makefile.am +++ /dev/null @@ -1,3 +0,0 @@ -include $(top_srcdir)/Makefile.python.am - -pkginstall = false diff --git a/pypi/ipaplatform/README.txt b/pypi/ipaplatform/README.txt deleted file mode 100644 index 15064b0b0a..0000000000 --- a/pypi/ipaplatform/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -This is a dummy package for FreeIPA's ipaplatform. - diff --git a/pypi/ipaplatform/ipaplatform/__init__.py b/pypi/ipaplatform/ipaplatform/__init__.py deleted file mode 100644 index 3b12c8c74e..0000000000 --- a/pypi/ipaplatform/ipaplatform/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -# Copyright (C) 2017 FreeIPA Contributors see COPYING for license -# - -raise ImportError("ipaplatform is not yet supported as PyPI package.") diff --git a/pypi/ipaplatform/setup.cfg b/pypi/ipaplatform/setup.cfg deleted file mode 100644 index 62f65c719e..0000000000 --- a/pypi/ipaplatform/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[bdist_wheel] -universal = 1 - -[aliases] -packages = clean --all egg_info bdist_wheel -release = packages register upload diff --git a/pypi/ipaplatform/setup.py b/pypi/ipaplatform/setup.py deleted file mode 100755 index f0fca2c708..0000000000 --- a/pypi/ipaplatform/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (C) 2017 FreeIPA Contributors see COPYING for license -# -"""Dummy package for FreeIPA - -ipaplatform is not yet available as PyPI package. -""" - -from os.path import abspath, dirname -import sys - -if __name__ == '__main__': - # include ../../ for ipasetup.py - sys.path.append(dirname(dirname(dirname(abspath(__file__))))) - from ipasetup import ipasetup # noqa: E402 - - ipasetup( - name='ipaplatform', - doc = __doc__, - packages=[ - "ipaplatform", - ], - install_requires=[ - "ipaclient", - ] - ) diff --git a/pypi/test_placeholder.py b/pypi/test_placeholder.py index d17b23af43..74002f577f 100644 --- a/pypi/test_placeholder.py +++ b/pypi/test_placeholder.py @@ -9,13 +9,14 @@ @pytest.mark.parametrize("modname", [ # placeholder packages raise ImportError - 'ipaplatform', 'ipaserver', 'ipatests', # PyPI packages do not have install subpackage 'ipaclient.install', 'ipalib.install', 'ipapython.install', + # fallback module should not be shipped in wheels + 'ipaplatform.fallback', ]) def test_fail_import(modname): try: @@ -29,6 +30,7 @@ def test_fail_import(modname): @pytest.mark.parametrize("modname", [ 'ipaclient', 'ipalib', + 'ipaplatform', 'ipapython', ]) def test_import(modname):
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org