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 <cheimes@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Christian Heimes 2017-10-11 12:09:30 +02:00
parent f6adf4f3a8
commit a48f6511f6
41 changed files with 422 additions and 141 deletions

6
.gitignore vendored
View File

@ -108,11 +108,7 @@ freeipa2-dev-doc
/client/ipa-join
/client/ipa-rmkeytab
/ipaplatform/override.py
/ipapython/version.py
/ipapython/.DEFAULT_PLUGINS
/ipaplatform/__init__.py
/ipaplatform/constants.py
/ipaplatform/paths.py
/ipaplatform/services.py
/ipaplatform/tasks.py

View File

@ -28,9 +28,9 @@ env:
test_install
test_ipaclient
test_ipalib
test_ipaplatform
test_ipapython
test_ipaserver
test_integration/test_ipalib_util.py
test_xmlrpc/test_[l-z]*.py"
- TASK_TO_RUN="run-tests"
PYTHON=/usr/bin/python3
@ -43,9 +43,9 @@ env:
test_install
test_ipaclient
test_ipalib
test_ipaplatform
test_ipapython
test_ipaserver
test_integration/test_ipalib_util.py
test_xmlrpc/test_[l-uw-z]*.py"
# FIXME: add vault tests once PKI finally fixes vault
install:

View File

@ -8,10 +8,14 @@ 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)
GENERATED_PYTHON_FILES = \
$(top_builddir)/ipaplatform/override.py \
$(top_builddir)/ipapython/version.py
MOSTLYCLEANFILES = ipasetup.pyc ipasetup.pyo \
pylint_plugins.pyc pylint_plugins.pyo
@ -133,7 +137,7 @@ _srpms-body: _rpms-prep
rm -f rm -f $(top_builddir)/.version
.PHONY: lite-server
lite-server: $(top_builddir)/ipapython/version.py
lite-server: $(GENERATED_PYTHON_FILES)
+$(MAKE) -C $(top_builddir)/install/ui
PYTHONPATH=$(top_srcdir) $(PYTHON) -bb \
contrib/lite-server.py $(LITESERVER_ARGS)
@ -179,16 +183,20 @@ else
endif
@echo "All tests passed."
.PHONY: $(top_builddir)/ipaplatform/override.py
$(top_builddir)/ipaplatform/override.py:
(cd $(top_builddir)/ipaplatform && make override.py)
.PHONY: $(top_builddir)/ipapython/version.py
$(top_builddir)/ipapython/version.py:
(cd $(top_builddir)/ipapython && make version.py)
.PHONY: acilint
acilint: $(top_builddir)/ipapython/version.py
acilint: $(GENERATED_PYTHON_FILES)
cd $(srcdir); ./makeaci --validate
.PHONY: apilint
apilint: $(top_builddir)/ipapython/version.py
apilint: $(GENERATED_PYTHON_FILES)
cd $(srcdir); ./makeapi --validate
.PHONY: polint
@ -203,7 +211,7 @@ polint:
.PHONY: pylint
if WITH_PYLINT
pylint: $(top_builddir)/ipapython/version.py ipasetup.py
pylint: $(GENERATED_PYTHON_FILES) ipasetup.py
FILES=`find $(top_srcdir) \
-type d -exec test -e '{}/__init__.py' \; -print -prune -o \
-path './rpmbuild' -prune -o \

View File

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

View File

@ -1637,6 +1637,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
@ -1658,6 +1659,7 @@ fi
%{python3_sitelib}/ipapython-*.egg-info
%{python3_sitelib}/ipalib-*.egg-info
%{python3_sitelib}/ipaplatform-*.egg-info
%{python3_sitelib}/ipaplatform-*-nspkg.pth
%endif # with_python3

View File

@ -28,6 +28,7 @@ of the process.
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.urllib.parse import urlparse, urlunparse
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 @@ from ipalib.constants import (
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
@ -451,10 +447,7 @@ class Env(object):
self.script = path.abspath(sys.argv[0])
self.bin = path.dirname(self.script)
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)

View File

@ -37,6 +37,7 @@ if __name__ == '__main__':
"ipalib.install",
],
install_requires=[
"ipaplatform",
"ipapython",
"netaddr",
"pyasn1",

View File

@ -55,6 +55,7 @@ from ipalib.constants import (
TLS_VERSIONS, TLS_VERSION_MINIMAL, TLS_HIGH_CIPHERS
)
from ipalib.text import _
from ipaplatform.paths import paths
from ipapython.ssh import SSHPublicKey
from ipapython.dn import DN, RDN
from ipapython.dnsutil import DNSName
@ -1101,9 +1102,9 @@ def check_client_configuration():
"""
Check if IPA client is configured on the system.
"""
if (not os.path.isfile(_IPA_DEFAULT_CONF) or
not os.path.isdir(_IPA_CLIENT_SYSRESTORE) or
not os.listdir(_IPA_CLIENT_SYSRESTORE)):
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')

View File

@ -1 +1,12 @@
include $(top_srcdir)/Makefile.python.am
EXTRA_DIST = override.py.in
all-local: override.py
dist-hook: override.py
install-exec-local: override.py
override.py: override.py.in $(top_builddir)/$(CONFIG_STATUS)
$(AM_V_GEN)sed \
-e 's|@IPAPLATFORM[@]|$(IPAPLATFORM)|g' \
$< > $@

11
ipaplatform/__init__.py Normal file
View File

@ -0,0 +1,11 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
"""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

150
ipaplatform/_importhook.py Normal file
View File

@ -0,0 +1,150 @@
#
# 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 io
import re
import sys
import warnings
import ipaplatform
try:
from ipaplatform.override import OVERRIDE
except ImportError:
OVERRIDE = None
_osrelease_line = re.compile(
u"^(?!#)(?P<name>[a-zA-Z0-9_]+)="
u"(?P<quote>[\"\']?)(?P<value>.+)(?P=quote)$"
)
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, override=OVERRIDE):
self.override = override
self.platform_ids = self._get_platform_ids(self.override)
self.platform = self._get_platform(self.platform_ids)
def _get_platform_ids(self, override):
platforms = []
# allow RPM and Debian packages to override platform
if override is not None:
platforms.append(override)
if sys.platform.startswith('linux'):
# Linux, get distribution from /etc/os-release
try:
platforms.extend(self._parse_osrelease())
except Exception as e:
warnings.warn("Failed to read /etc/os-release: {}".format(e))
elif sys.platform == 'win32':
# Windows 32 or 64bit platform
platforms.append('win32')
elif sys.platform == 'darwin':
# macOS
platforms.append('macos')
elif sys.platform.startswith(self.bsd_family):
# BSD family, look for e.g. ['freebsd10', 'freebsd']
platforms.append(sys.platform)
simple = sys.platform.rstrip('0123456789')
if simple != sys.platform:
platforms.append(simple)
if not platforms:
raise ValueError("Unsupported platform: {}".format(sys.platform))
return platforms
def _parse_osrelease(self, filename='/etc/os-release'):
release = {}
with io.open(filename, encoding='utf-8') as f:
for line in f:
mo = _osrelease_line.match(line)
if mo is not None:
release[mo.group('name')] = mo.group('value')
platforms = [
release['ID'],
]
if "ID_LIKE" in release:
platforms.extend(
v.strip() for v in release['ID_LIKE'].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

View File

@ -37,3 +37,6 @@ class BaseConstantsNamespace(object):
'httpd_dbus_sssd': 'on',
}
SSSD_USER = "sssd"
constants = BaseConstantsNamespace()

View File

@ -355,5 +355,7 @@ 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()

View File

@ -505,8 +505,12 @@ class SystemdService(PlatformService):
# 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

View File

@ -204,6 +204,9 @@ class BaseTaskNamespace(object):
"""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,6 @@ class BaseTaskNamespace(object):
logger.debug('Done adding user to group')
except ipautil.CalledProcessError as e:
logger.debug('Failed to add user to group: %s', e)
tasks = BaseTaskNamespace()

8
ipaplatform/constants.py Normal file
View File

@ -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')

View File

@ -0,0 +1 @@
OVERRIDE = '@IPAPLATFORM@'

8
ipaplatform/paths.py Normal file
View File

@ -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')

8
ipaplatform/services.py Normal file
View File

@ -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')

View File

@ -32,6 +32,7 @@ if __name__ == '__main__':
name="ipaplatform",
doc=__doc__,
package_dir={'ipaplatform': ''},
namespace_packages=['ipaplatform'],
packages=[
"ipaplatform",
"ipaplatform.base",

8
ipaplatform/tasks.py Normal file
View File

@ -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')

View File

@ -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 shutil
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 @@ class NSSDatabase(object):
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 @@ class NSSDatabase(object):
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 @@ class NSSDatabase(object):
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 @@ class NSSDatabase(object):
(key_file, filename))
args = [
OPENSSL, 'pkcs8',
paths.OPENSSL, 'pkcs8',
'-topk8',
'-passout', 'file:' + self.pwd_file,
]
@ -588,7 +578,7 @@ class NSSDatabase(object):
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,

View File

@ -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 (
@ -33,18 +34,10 @@ from six.moves.configparser import SafeConfigParser
from six.moves.urllib.parse import urlsplit
# pylint: enable=import-error
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.ipautil import CheckedIPAddress, CheckedIPAddressLoopback
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=''):
@ -188,7 +181,7 @@ config = IPAConfig()
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:

View File

@ -42,6 +42,7 @@ if __name__ == '__main__':
"dnspython",
"gssapi",
# "ipalib", # circular dependency
"ipaplatform",
"netaddr",
"netifaces",
"six",

View File

@ -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 override module
'ipaplatform.override',
)
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:

View File

@ -41,6 +41,7 @@ if __name__ == '__main__':
"ipatests.test_integration",
"ipatests.test_ipaclient",
"ipatests.test_ipalib",
"ipatests.test_ipaplatform",
"ipatests.test_ipapython",
"ipatests.test_ipaserver",
"ipatests.test_ipaserver.test_install",
@ -54,6 +55,7 @@ if __name__ == '__main__':
'ipatests.test_integration': ['scripts/*'],
'ipatests.test_ipaclient': ['data/*/*/*'],
'ipatests.test_ipalib': ['data/*'],
'ipatests.test_ipaplatform': ['data/*'],
"ipatests.test_ipaserver": ['data/*'],
'ipatests.test_xmlrpc': ['data/*'],
},
@ -63,6 +65,7 @@ if __name__ == '__main__':
"gssapi",
"ipaclient",
"ipalib",
"ipaplatform",
"ipapython",
"nose",
"polib",

View File

@ -1,23 +0,0 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
"""
Test the `ipalib.util` module.
This tests is in test_integration beucase we only can import ipaplatform here.
"""
from ipalib import util
from ipaplatform.paths import paths
import pytest
pytestmark = pytest.mark.tier0
def test_hardcoded_paths_are_right():
"""
Test if constants created in ipalib.util are in sync with
paths declared in ipaplatform.paths
"""
assert util._IPA_CLIENT_SYSRESTORE == paths.IPA_CLIENT_SYSRESTORE
assert util._IPA_DEFAULT_CONF == paths.IPA_DEFAULT_CONF

View File

View File

@ -0,0 +1,15 @@
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

View File

@ -0,0 +1,16 @@
NAME=Fedora
VERSION="26 (Workstation Edition)"
ID=fedora
VERSION_ID=26
PRETTY_NAME="Fedora 26 (Workstation Edition)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:26"
HOME_URL="https://fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=26
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=26
PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy
VARIANT="Workstation Edition"
VARIANT_ID=workstation

View File

@ -0,0 +1,11 @@
NAME="Ubuntu"
VERSION="16.04.3 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.3 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

View File

@ -0,0 +1,54 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
import os
import sys
import pytest
import ipaplatform.constants
import ipaplatform.paths
import ipaplatform.services
import ipaplatform.tasks
from ipaplatform._importhook import metaimporter
try:
from ipaplatform.override import OVERRIDE
except ImportError:
OVERRIDE = None
HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, 'data')
@pytest.mark.skipif(OVERRIDE is None,
reason='test requires override')
def test_override():
assert OVERRIDE == metaimporter.platform_ids[0]
assert OVERRIDE == metaimporter.platform
@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__
@pytest.mark.parametrize('filename, expected_platforms', [
(os.path.join(DATA, 'os-release-centos'), ['centos', 'rhel', 'fedora']),
(os.path.join(DATA, 'os-release-fedora'), ['fedora']),
(os.path.join(DATA, 'os-release-ubuntu'), ['ubuntu', 'debian']),
])
def test_parse_os_release(filename, expected_platforms):
parsed = metaimporter._parse_osrelease(filename)
assert parsed == expected_platforms

View File

@ -269,6 +269,49 @@ 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

View File

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

View File

@ -7,7 +7,6 @@ NULL =
SUBDIRS = \
freeipa \
ipa \
ipaplatform \
ipaserver \
ipatests \
$(NULL)

View File

@ -1,3 +0,0 @@
include $(top_srcdir)/Makefile.python.am
pkginstall = false

View File

@ -1,2 +0,0 @@
This is a dummy package for FreeIPA's ipaplatform.

View File

@ -1,5 +0,0 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
raise ImportError("ipaplatform is not yet supported as PyPI package.")

View File

@ -1,6 +0,0 @@
[bdist_wheel]
universal = 1
[aliases]
packages = clean --all egg_info bdist_wheel
release = packages register upload

View File

@ -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",
]
)

View File

@ -9,13 +9,14 @@ import pytest
@pytest.mark.parametrize("modname", [
# placeholder packages raise ImportError
'ipaplatform',
'ipaserver',
'ipatests',
# PyPI packages do not have install subpackage
'ipaclient.install',
'ipalib.install',
'ipapython.install',
# override module should not be shipped in wheels
'ipaplatform.override',
])
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):