| Server IP : 85.214.239.14 / Your IP : 216.73.216.210 Web Server : Apache/2.4.65 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Mon Sep 30 15:36:27 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 8.2.29 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /usr/lib/python3/dist-packages/ansible_test/_util/controller/sanity/pylint/plugins/ |
Upload File : |
"""A plugin for pylint to identify imports and functions which should not be used."""
from __future__ import annotations
import os
import typing as t
import astroid
from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker
ANSIBLE_TEST_MODULES_PATH = os.environ['ANSIBLE_TEST_MODULES_PATH']
ANSIBLE_TEST_MODULE_UTILS_PATH = os.environ['ANSIBLE_TEST_MODULE_UTILS_PATH']
class UnwantedEntry:
"""Defines an unwanted import."""
def __init__(
self,
alternative, # type: str
modules_only=False, # type: bool
names=None, # type: t.Optional[t.Tuple[str, ...]]
ignore_paths=None, # type: t.Optional[t.Tuple[str, ...]]
ansible_test_only=False, # type: bool
): # type: (...) -> None
self.alternative = alternative
self.modules_only = modules_only
self.names = set(names) if names else set()
self.ignore_paths = ignore_paths
self.ansible_test_only = ansible_test_only
def applies_to(self, path, name=None): # type: (str, t.Optional[str]) -> bool
"""Return True if this entry applies to the given path, otherwise return False."""
if self.names:
if not name:
return False
if name not in self.names:
return False
if self.ignore_paths and any(path.endswith(ignore_path) for ignore_path in self.ignore_paths):
return False
if self.ansible_test_only and '/test/lib/ansible_test/_internal/' not in path:
return False
if self.modules_only:
return is_module_path(path)
return True
def is_module_path(path): # type: (str) -> bool
"""Return True if the given path is a module or module_utils path, otherwise return False."""
return path.startswith(ANSIBLE_TEST_MODULES_PATH) or path.startswith(ANSIBLE_TEST_MODULE_UTILS_PATH)
class AnsibleUnwantedChecker(BaseChecker):
"""Checker for unwanted imports and functions."""
__implements__ = (IAstroidChecker,)
name = 'unwanted'
BAD_IMPORT = 'ansible-bad-import'
BAD_IMPORT_FROM = 'ansible-bad-import-from'
BAD_FUNCTION = 'ansible-bad-function'
BAD_MODULE_IMPORT = 'ansible-bad-module-import'
msgs = dict(
E5101=('Import %s instead of %s',
BAD_IMPORT,
'Identifies imports which should not be used.'),
E5102=('Import %s from %s instead of %s',
BAD_IMPORT_FROM,
'Identifies imports which should not be used.'),
E5103=('Call %s instead of %s',
BAD_FUNCTION,
'Identifies functions which should not be used.'),
E5104=('Import external package or ansible.module_utils not %s',
BAD_MODULE_IMPORT,
'Identifies imports which should not be used.'),
)
unwanted_imports = dict(
# Additional imports that we may want to start checking:
# boto=UnwantedEntry('boto3', modules_only=True),
# requests=UnwantedEntry('ansible.module_utils.urls', modules_only=True),
# urllib=UnwantedEntry('ansible.module_utils.urls', modules_only=True),
# see https://docs.python.org/2/library/urllib2.html
urllib2=UnwantedEntry('ansible.module_utils.urls',
ignore_paths=(
'/lib/ansible/module_utils/urls.py',
)),
# see https://docs.python.org/3/library/collections.abc.html
collections=UnwantedEntry('ansible.module_utils.common._collections_compat',
ignore_paths=(
'/lib/ansible/module_utils/common/_collections_compat.py',
),
names=(
'MappingView',
'ItemsView',
'KeysView',
'ValuesView',
'Mapping', 'MutableMapping',
'Sequence', 'MutableSequence',
'Set', 'MutableSet',
'Container',
'Hashable',
'Sized',
'Callable',
'Iterable',
'Iterator',
)),
)
unwanted_functions = {
# see https://docs.python.org/3/library/tempfile.html#tempfile.mktemp
'tempfile.mktemp': UnwantedEntry('tempfile.mkstemp'),
# os.chmod resolves as posix.chmod
'posix.chmod': UnwantedEntry('verified_chmod',
ansible_test_only=True),
'sys.exit': UnwantedEntry('exit_json or fail_json',
ignore_paths=(
'/lib/ansible/module_utils/basic.py',
'/lib/ansible/modules/async_wrapper.py',
),
modules_only=True),
'builtins.print': UnwantedEntry('module.log or module.debug',
ignore_paths=(
'/lib/ansible/module_utils/basic.py',
),
modules_only=True),
}
def visit_import(self, node): # type: (astroid.node_classes.Import) -> None
"""Visit an import node."""
for name in node.names:
self._check_import(node, name[0])
def visit_importfrom(self, node): # type: (astroid.node_classes.ImportFrom) -> None
"""Visit an import from node."""
self._check_importfrom(node, node.modname, node.names)
def visit_attribute(self, node): # type: (astroid.node_classes.Attribute) -> None
"""Visit an attribute node."""
last_child = node.last_child()
# this is faster than using type inference and will catch the most common cases
if not isinstance(last_child, astroid.node_classes.Name):
return
module = last_child.name
entry = self.unwanted_imports.get(module)
if entry and entry.names:
if entry.applies_to(self.linter.current_file, node.attrname):
self.add_message(self.BAD_IMPORT_FROM, args=(node.attrname, entry.alternative, module), node=node)
def visit_call(self, node): # type: (astroid.node_classes.Call) -> None
"""Visit a call node."""
try:
for i in node.func.inferred():
func = None
if isinstance(i, astroid.scoped_nodes.FunctionDef) and isinstance(i.parent, astroid.scoped_nodes.Module):
func = '%s.%s' % (i.parent.name, i.name)
if not func:
continue
entry = self.unwanted_functions.get(func)
if entry and entry.applies_to(self.linter.current_file):
self.add_message(self.BAD_FUNCTION, args=(entry.alternative, func), node=node)
except astroid.exceptions.InferenceError:
pass
def _check_import(self, node, modname): # type: (astroid.node_classes.Import, str) -> None
"""Check the imports on the specified import node."""
self._check_module_import(node, modname)
entry = self.unwanted_imports.get(modname)
if not entry:
return
if entry.applies_to(self.linter.current_file):
self.add_message(self.BAD_IMPORT, args=(entry.alternative, modname), node=node)
def _check_importfrom(self, node, modname, names): # type: (astroid.node_classes.ImportFrom, str, t.List[str]) -> None
"""Check the imports on the specified import from node."""
self._check_module_import(node, modname)
entry = self.unwanted_imports.get(modname)
if not entry:
return
for name in names:
if entry.applies_to(self.linter.current_file, name[0]):
self.add_message(self.BAD_IMPORT_FROM, args=(name[0], entry.alternative, modname), node=node)
def _check_module_import(self, node, modname): # type: (t.Union[astroid.node_classes.Import, astroid.node_classes.ImportFrom], str) -> None
"""Check the module import on the given import or import from node."""
if not is_module_path(self.linter.current_file):
return
if modname == 'ansible.module_utils' or modname.startswith('ansible.module_utils.'):
return
if modname == 'ansible' or modname.startswith('ansible.'):
self.add_message(self.BAD_MODULE_IMPORT, args=(modname,), node=node)
def register(linter):
"""required method to auto register this checker """
linter.register_checker(AnsibleUnwantedChecker(linter))