mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-11 08:41:55 -06:00
4fc7f72648
pylint 2.0 is more strict and complains about several aspects of ipalib.api. It turns out that AstroidBuilder.string_build() can be used to easily teach pylint about object attributes and attribute values. Although the assignment wouldn't work with the actual implementation, the string builder assignments shows pylint the names and values of members. It works without additional transformation. See: https://pagure.io/freeipa/issue/7614 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Armando Neto <abiagion@redhat.com>
527 lines
16 KiB
Python
527 lines
16 KiB
Python
#
|
|
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
|
#
|
|
|
|
from __future__ import print_function
|
|
|
|
import copy
|
|
import os.path
|
|
import sys
|
|
import textwrap
|
|
|
|
from astroid import MANAGER, register_module_extender
|
|
from astroid import scoped_nodes
|
|
from pylint.checkers import BaseChecker
|
|
from pylint.checkers.utils import check_messages
|
|
from pylint.interfaces import IAstroidChecker
|
|
from astroid.builder import AstroidBuilder
|
|
|
|
|
|
def register(linter):
|
|
linter.register_checker(IPAChecker(linter))
|
|
|
|
|
|
def _warning_already_exists(cls, member):
|
|
print(
|
|
"WARNING: member '{member}' in '{cls}' already exists".format(
|
|
cls="{}.{}".format(cls.root().name, cls.name), member=member),
|
|
file=sys.stderr
|
|
)
|
|
|
|
|
|
def fake_class(name_or_class_obj, members=()):
|
|
if isinstance(name_or_class_obj, scoped_nodes.ClassDef):
|
|
cl = name_or_class_obj
|
|
else:
|
|
cl = scoped_nodes.ClassDef(name_or_class_obj, None)
|
|
|
|
for m in members:
|
|
if isinstance(m, str):
|
|
if m in cl.locals:
|
|
_warning_already_exists(cl, m)
|
|
else:
|
|
cl.locals[m] = [scoped_nodes.ClassDef(m, None)]
|
|
elif isinstance(m, dict):
|
|
for key, val in m.items():
|
|
assert isinstance(key, str), "key must be string"
|
|
if key in cl.locals:
|
|
_warning_already_exists(cl, key)
|
|
fake_class(cl.locals[key], val)
|
|
else:
|
|
cl.locals[key] = [fake_class(key, val)]
|
|
else:
|
|
# here can be used any astroid type
|
|
if m.name in cl.locals:
|
|
_warning_already_exists(cl, m.name)
|
|
else:
|
|
cl.locals[m.name] = [copy.copy(m)]
|
|
return cl
|
|
|
|
|
|
# 'class': ['generated', 'properties']
|
|
ipa_class_members = {
|
|
# Python standard library & 3rd party classes
|
|
'socket._socketobject': ['sendall'],
|
|
|
|
# IPA classes
|
|
'ipalib.base.NameSpace': [
|
|
'add',
|
|
'mod',
|
|
'del',
|
|
'show',
|
|
'find'
|
|
],
|
|
'ipalib.cli.Collector': ['__options'],
|
|
'ipalib.errors.ACIError': [
|
|
'info',
|
|
],
|
|
'ipalib.errors.ConversionError': [
|
|
'error',
|
|
],
|
|
'ipalib.errors.DatabaseError': [
|
|
'desc',
|
|
],
|
|
'ipalib.errors.NetworkError': [
|
|
'error',
|
|
],
|
|
'ipalib.errors.NotFound': [
|
|
'reason',
|
|
],
|
|
'ipalib.errors.PublicError': [
|
|
'msg',
|
|
'strerror',
|
|
'kw',
|
|
],
|
|
'ipalib.errors.SingleMatchExpected': [
|
|
'found',
|
|
],
|
|
'ipalib.errors.SkipPluginModule': [
|
|
'reason',
|
|
],
|
|
'ipalib.errors.ValidationError': [
|
|
'error',
|
|
],
|
|
'ipalib.errors.SchemaUpToDate': [
|
|
'fingerprint',
|
|
'ttl',
|
|
],
|
|
'ipalib.messages.PublicMessage': [
|
|
'msg',
|
|
'strerror',
|
|
'type',
|
|
'kw',
|
|
],
|
|
'ipalib.parameters.Param': [
|
|
'cli_name',
|
|
'cli_short_name',
|
|
'label',
|
|
'default',
|
|
'doc',
|
|
'required',
|
|
'multivalue',
|
|
'primary_key',
|
|
'normalizer',
|
|
'default_from',
|
|
'autofill',
|
|
'query',
|
|
'attribute',
|
|
'include',
|
|
'exclude',
|
|
'flags',
|
|
'hint',
|
|
'alwaysask',
|
|
'sortorder',
|
|
'option_group',
|
|
'no_convert',
|
|
'deprecated',
|
|
],
|
|
'ipalib.parameters.Bool': [
|
|
'truths',
|
|
'falsehoods'],
|
|
'ipalib.parameters.Data': [
|
|
'minlength',
|
|
'maxlength',
|
|
'length',
|
|
'pattern',
|
|
'pattern_errmsg',
|
|
],
|
|
'ipalib.parameters.Str': ['noextrawhitespace'],
|
|
'ipalib.parameters.Password': ['confirm'],
|
|
'ipalib.parameters.File': ['stdin_if_missing'],
|
|
'ipalib.parameters.Enum': ['values'],
|
|
'ipalib.parameters.Number': [
|
|
'minvalue',
|
|
'maxvalue',
|
|
],
|
|
'ipalib.parameters.Decimal': [
|
|
'precision',
|
|
'exponential',
|
|
'numberclass',
|
|
],
|
|
'ipalib.parameters.DNSNameParam': [
|
|
'only_absolute',
|
|
'only_relative',
|
|
],
|
|
'ipalib.parameters.Principal': [
|
|
'require_service',
|
|
],
|
|
'ipalib.plugable.API': [
|
|
'Advice',
|
|
],
|
|
'ipalib.util.ForwarderValidationError': [
|
|
'msg',
|
|
],
|
|
'ipaserver.plugins.dns.DNSRecord': [
|
|
'validatedns',
|
|
'normalizedns',
|
|
],
|
|
'ipatests.test_integration.base.IntegrationTest': [
|
|
'domain',
|
|
{'master': [
|
|
{'config': [
|
|
{'dirman_password': dir(str)},
|
|
{'admin_password': dir(str)},
|
|
{'admin_name': dir(str)},
|
|
{'dns_forwarder': dir(str)},
|
|
{'test_dir': dir(str)},
|
|
{'ad_admin_name': dir(str)},
|
|
{'ad_admin_password': dir(str)},
|
|
{'domain_level': dir(str)},
|
|
]},
|
|
{'domain': [
|
|
{'realm': dir(str)},
|
|
{'name': dir(str)},
|
|
]},
|
|
'hostname',
|
|
'ip',
|
|
'collect_log',
|
|
{'run_command': [
|
|
{'stdout_text': dir(str)},
|
|
'stderr_text',
|
|
'returncode',
|
|
]},
|
|
{'transport': ['put_file', 'file_exists']},
|
|
'put_file_contents',
|
|
'get_file_contents',
|
|
'ldap_connect',
|
|
]},
|
|
'replicas',
|
|
'clients',
|
|
'ad_domains',
|
|
]
|
|
}
|
|
|
|
|
|
def fix_ipa_classes(cls):
|
|
class_name_with_module = "{}.{}".format(cls.root().name, cls.name)
|
|
if class_name_with_module in ipa_class_members:
|
|
fake_class(cls, ipa_class_members[class_name_with_module])
|
|
|
|
|
|
MANAGER.register_transform(scoped_nodes.ClassDef, fix_ipa_classes)
|
|
|
|
|
|
def pytest_config_transform():
|
|
"""pylint.config attribute
|
|
"""
|
|
return AstroidBuilder(MANAGER).string_build(textwrap.dedent('''
|
|
from _pytest.config import get_config
|
|
config = get_config()
|
|
'''))
|
|
|
|
|
|
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
|
|
|
|
name = 'ipa'
|
|
msgs = {
|
|
'W9901': (
|
|
'Forbidden import %s (can\'t import from %s in %s)',
|
|
'ipa-forbidden-import',
|
|
'Used when an forbidden import is detected.',
|
|
),
|
|
}
|
|
options = (
|
|
(
|
|
'forbidden-imports',
|
|
{
|
|
'default': '',
|
|
'type': 'csv',
|
|
'metavar': '<path>[:<module>[:<module>...]][,<path>...]',
|
|
'help': 'Modules which are forbidden to be imported in the '
|
|
'given paths',
|
|
},
|
|
),
|
|
)
|
|
priority = -1
|
|
|
|
def open(self):
|
|
self._dir = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
self._forbidden_imports = {self._dir: []}
|
|
for forbidden_import in self.config.forbidden_imports:
|
|
forbidden_import = forbidden_import.split(':')
|
|
path = os.path.join(self._dir, forbidden_import[0])
|
|
path = os.path.abspath(path)
|
|
modules = forbidden_import[1:]
|
|
self._forbidden_imports[path] = modules
|
|
|
|
self._forbidden_imports_stack = []
|
|
|
|
def _get_forbidden_import_rule(self, node):
|
|
path = node.path
|
|
if path and isinstance(path, list):
|
|
# In pylint 2.0, path is a list with one element. Namespace
|
|
# packages may contain more than one element, but we can safely
|
|
# ignore them, as they don't contain code.
|
|
path = path[0]
|
|
if path:
|
|
path = os.path.abspath(path)
|
|
while path.startswith(self._dir):
|
|
if path in self._forbidden_imports:
|
|
return path
|
|
path = os.path.dirname(path)
|
|
return self._dir
|
|
|
|
def visit_module(self, node):
|
|
self._forbidden_imports_stack.append(
|
|
self._get_forbidden_import_rule(node))
|
|
|
|
def leave_module(self, node):
|
|
self._forbidden_imports_stack.pop()
|
|
|
|
def _check_forbidden_imports(self, node, names):
|
|
path = self._forbidden_imports_stack[-1]
|
|
relpath = os.path.relpath(path, self._dir)
|
|
modules = self._forbidden_imports[path]
|
|
for module in modules:
|
|
module_prefix = module + '.'
|
|
for name in names:
|
|
if name == module or name.startswith(module_prefix):
|
|
self.add_message('ipa-forbidden-import',
|
|
args=(name, module, relpath), node=node)
|
|
|
|
@check_messages('ipa-forbidden-import')
|
|
def visit_import(self, node):
|
|
names = [n[0] for n in node.names]
|
|
self._check_forbidden_imports(node, names)
|
|
|
|
@check_messages('ipa-forbidden-import')
|
|
def visit_importfrom(self, node):
|
|
names = ['{}.{}'.format(node.modname, n[0]) for n in node.names]
|
|
self._check_forbidden_imports(node, names)
|
|
|
|
|
|
#
|
|
# Teach pylint how api object works
|
|
#
|
|
# ipalib uses some tricks to create api.env members and api objects. pylint
|
|
# is not able to infer member names and types from code. The explict
|
|
# assignments inside the string builder templates are good enough to show
|
|
# pylint, how the api is created. Additional transformations are not
|
|
# required.
|
|
#
|
|
|
|
AstroidBuilder(MANAGER).string_build(textwrap.dedent(
|
|
"""
|
|
from ipalib import api
|
|
from ipalib import cli, plugable, rpc
|
|
from ipalib.base import NameSpace
|
|
from ipaclient.plugins import rpcclient
|
|
try:
|
|
from ipaserver.plugins import dogtag, ldap2, serverroles
|
|
except ImportError:
|
|
HAS_SERVER = False
|
|
else:
|
|
HAS_SERVER = True
|
|
|
|
def wildcard(*args, **kwargs):
|
|
return None
|
|
|
|
# ipalib.api members
|
|
api.Backend = plugable.APINameSpace(api, None)
|
|
api.Command = plugable.APINameSpace(api, None)
|
|
api.Method = plugable.APINameSpace(api, None)
|
|
api.Object = plugable.APINameSpace(api, None)
|
|
api.Updater = plugable.APINameSpace(api, None)
|
|
# ipalib.api.Backend members
|
|
api.Backend.cli = cli.cli(api)
|
|
api.Backend.textui = cli.textui(api)
|
|
api.Backend.jsonclient = rpc.jsonclient(api)
|
|
api.Backend.rpcclient = rpcclient.rpcclient(api)
|
|
api.Backend.xmlclient = rpc.xmlclient(api)
|
|
|
|
if HAS_SERVER:
|
|
api.Backend.kra = dogtag.kra(api)
|
|
api.Backend.ldap2 = ldap2.ldap2(api)
|
|
api.Backend.ra = dogtag.ra(api)
|
|
api.Backend.ra_certprofile = dogtag.ra_certprofile(api)
|
|
api.Backend.ra_lightweight_ca = dogtag.ra_lightweight_ca(api)
|
|
api.Backend.serverroles = serverroles.serverroles(api)
|
|
|
|
# ipalib.base.NameSpace
|
|
NameSpace.find = wildcard
|
|
"""
|
|
))
|
|
|
|
|
|
AstroidBuilder(MANAGER).string_build(textwrap.dedent(
|
|
"""
|
|
from ipalib import api
|
|
from ipapython.dn import DN
|
|
|
|
api.env.api_version = ''
|
|
api.env.bin = '' # object
|
|
api.env.ca_agent_install_port = None
|
|
api.env.ca_agent_port = 0
|
|
api.env.ca_ee_install_port = None
|
|
api.env.ca_ee_port = 0
|
|
api.env.ca_host = ''
|
|
api.env.ca_install_port = None
|
|
api.env.ca_port = 0
|
|
api.env.conf = '' # object
|
|
api.env.conf_default = '' # object
|
|
api.env.confdir = '' # object
|
|
api.env.container_accounts = DN()
|
|
api.env.container_adtrusts = DN()
|
|
api.env.container_applications = DN()
|
|
api.env.container_automember = DN()
|
|
api.env.container_automount = DN()
|
|
api.env.container_ca = DN()
|
|
api.env.container_caacl = DN()
|
|
api.env.container_certmap = DN()
|
|
api.env.container_certmaprules = DN()
|
|
api.env.container_certprofile = DN()
|
|
api.env.container_cifsdomains = DN()
|
|
api.env.container_configs = DN()
|
|
api.env.container_custodia = DN()
|
|
api.env.container_deleteuser = DN()
|
|
api.env.container_dna = DN()
|
|
api.env.container_dna_posix_ids = DN()
|
|
api.env.container_dns = DN()
|
|
api.env.container_dnsservers = DN()
|
|
api.env.container_group = DN()
|
|
api.env.container_hbac = DN()
|
|
api.env.container_hbacservice = DN()
|
|
api.env.container_hbacservicegroup = DN()
|
|
api.env.container_host = DN()
|
|
api.env.container_hostgroup = DN()
|
|
api.env.container_locations = DN()
|
|
api.env.container_masters = DN()
|
|
api.env.container_netgroup = DN()
|
|
api.env.container_otp = DN()
|
|
api.env.container_permission = DN()
|
|
api.env.container_policies = DN()
|
|
api.env.container_policygroups = DN()
|
|
api.env.container_policylinks = DN()
|
|
api.env.container_privilege = DN()
|
|
api.env.container_radiusproxy = DN()
|
|
api.env.container_ranges = DN()
|
|
api.env.container_realm_domains = DN()
|
|
api.env.container_rolegroup = DN()
|
|
api.env.container_roles = DN()
|
|
api.env.container_s4u2proxy = DN()
|
|
api.env.container_selinux = DN()
|
|
api.env.container_service = DN()
|
|
api.env.container_stageuser = DN()
|
|
api.env.container_sudocmd = DN()
|
|
api.env.container_sudocmdgroup = DN()
|
|
api.env.container_sudorule = DN()
|
|
api.env.container_sysaccounts = DN()
|
|
api.env.container_topology = DN()
|
|
api.env.container_trusts = DN()
|
|
api.env.container_user = DN()
|
|
api.env.container_vault = DN()
|
|
api.env.container_views = DN()
|
|
api.env.container_virtual = DN()
|
|
api.env.context = '' # object
|
|
api.env.debug = False
|
|
api.env.delegate = False
|
|
api.env.dogtag_version = 0
|
|
api.env.dot_ipa = '' # object
|
|
api.env.enable_ra = False
|
|
api.env.env_confdir = None
|
|
api.env.fallback = True
|
|
api.env.force_schema_check = False
|
|
api.env.home = '' # object
|
|
api.env.host = ''
|
|
api.env.http_timeout = 0
|
|
api.env.in_server = False # object
|
|
api.env.in_tree = False # object
|
|
api.env.interactive = True
|
|
api.env.ipalib = '' # object
|
|
api.env.kinit_lifetime = None
|
|
api.env.log = '' # object
|
|
api.env.logdir = '' # object
|
|
api.env.mode = ''
|
|
api.env.mount_ipa = ''
|
|
api.env.nss_dir = '' # object
|
|
api.env.plugins_on_demand = False # object
|
|
api.env.prompt_all = False
|
|
api.env.ra_plugin = ''
|
|
api.env.recommended_max_agmts = 0
|
|
api.env.replication_wait_timeout = 0
|
|
api.env.rpc_protocol = ''
|
|
api.env.server = ''
|
|
api.env.script = '' # object
|
|
api.env.site_packages = '' # object
|
|
api.env.skip_version_check = False
|
|
api.env.startup_timeout = 0
|
|
api.env.startup_traceback = False
|
|
api.env.tls_ca_cert = '' # object
|
|
api.env.tls_version_max = ''
|
|
api.env.tls_version_min = ''
|
|
api.env.validate_api = False
|
|
api.env.verbose = 0
|
|
api.env.version = ''
|
|
api.env.wait_for_dns = 0
|
|
api.env.webui_prod = True
|
|
"""
|
|
))
|