Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import test: improve docs, add more tests #73600

Merged
merged 11 commits into from
Mar 2, 2021
67 changes: 65 additions & 2 deletions docs/docsite/rst/dev_guide/testing/sanity/import.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
import
======

All Python imports in ``lib/ansible/modules/`` and ``lib/ansible/module_utils/`` which are not from the Python standard library
must be imported in a try/except ImportError block.
Ansible allows unchecked imports of some libraries from specific directories, listed at the bottom of this section. Import all other Python libraries in a try/except ImportError block to support sanity tests such as ``validate-modules`` and to allow Ansible to give better error messages to the user. To import a library in a try/except ImportError block:

1. In modules:

.. code-block:: python

# Instead of 'import another_library', do:

import traceback

try:
import another_library
except ImportError:
HAS_ANOTHER_LIBRARY = False
ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_ANOTHER_LIBRARY = True


# Later in module code:

module = AnsibleModule(...)

if not HAS_ANOTHER_LIBRARY:
# Needs: from ansible.module_utils.basic import missing_required_lib
module.fail_json(
msg=missing_required_lib('another_library'),
exception=ANOTHER_LIBRARY_IMPORT_ERROR)

2. In plugins:

.. code-block:: python

# Instead of 'import another_library', do:

from ansible.module_utils.six import raise_from

try:
import another_library
except ImportError as imp_exc:
ANOTHER_LIBRARY_IMPORT_ERROR = imp_exc
else:
ANOTHER_LIBRARY_IMPORT_ERROR = None


# Later in plugin code, for example in __init__ of the plugin:

if ANOTHER_LIBRARY_IMPORT_ERROR:
raise_from(
AnsibleError('another_library must be installed to use this plugin'),
ANOTHER_LIBRARY_IMPORT_ERROR)
# If you target only newer Python 3 versions, you can also use the
# 'raise ... from ...' syntax.

Ansible allows the following unchecked imports from these specific directories:

* ansible-core:

* For ``lib/ansible/modules/`` and ``lib/ansible/module_utils/``, unchecked imports are only allowed from the Python standard library;
* For ``lib/ansible/plugins/``, unchecked imports are only allowed from the Python standard library, from dependencies of ansible-core, and from ansible-core itself;

* collections:

* For ``plugins/modules/`` and ``plugins/module_utils/``, unchecked imports are only allowed from the Python standard library;
* For other directories in ``plugins/`` (see `the community collection requirements <https://github.com/ansible-collections/overview/blob/main/collection_requirements.rst#modules-plugins>`_ for a list), unchecked imports are only allowed from the Python standard library, from dependencies of ansible-core, and from ansible-core itself.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = '''
name: bad
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
short_description: Bad lookup
description: A bad lookup.
author:
- Ansible Core Team
'''

EXAMPLES = '''
- debug:
msg: "{{ lookup('ns.col.bad') }}"
'''

RETURN = ''' # '''

from ansible.plugins.lookup import LookupBase
from ansible import constants

import lxml


class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
self.set_options(var_options=variables, direct=kwargs)

return terms
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = '''
name: world
short_description: World lookup
description: A world lookup.
author:
- Ansible Core Team
'''

EXAMPLES = '''
- debug:
msg: "{{ lookup('ns.col.world') }}"
'''

RETURN = ''' # '''

from ansible.plugins.lookup import LookupBase
from ansible import constants


class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
self.set_options(var_options=variables, direct=kwargs)

return terms
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

# This is not an allowed import, but since this file is in a plugins/ subdirectory that is not checked,
# the import sanity test will not complain.
import lxml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins/modules/bad.py import
plugins/modules/bad.py pylint:ansible-bad-module-import
plugins/lookup/bad.py import
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ cd "${WORK_DIR}/ansible_collections/ns/col"

# rename the sanity ignore file to match the current ansible version and update import ignores with the python version
ansible_version="$(python -c 'import ansible.release; print(".".join(ansible.release.__version__.split(".")[:2]))')"
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" > "tests/sanity/ignore-${ansible_version}.txt"
if [ "${ANSIBLE_TEST_PYTHON_VERSION}" == "2.6" ]; then
# Non-module/module_utils plugins are not checked on this remote-only Python versions
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" | grep -v 'plugins/[^m].* import' > "tests/sanity/ignore-${ansible_version}.txt"
else
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" > "tests/sanity/ignore-${ansible_version}.txt"
fi
cat "tests/sanity/ignore-${ansible_version}.txt"

# common args for all tests
common=(--venv --color --truncate 0 "${@}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ cd "${WORK_DIR}/ansible_collections/ns/col"

# rename the sanity ignore file to match the current ansible version and update import ignores with the python version
ansible_version="$(python -c 'import ansible.release; print(".".join(ansible.release.__version__.split(".")[:2]))')"
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" > "tests/sanity/ignore-${ansible_version}.txt"
if [ "${ANSIBLE_TEST_PYTHON_VERSION}" == "2.6" ]; then
# Non-module/module_utils plugins are not checked on this remote-only Python versions
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" | grep -v 'plugins/[^m].* import' > "tests/sanity/ignore-${ansible_version}.txt"
else
sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" > "tests/sanity/ignore-${ansible_version}.txt"
fi
cat "tests/sanity/ignore-${ansible_version}.txt"

# common args for all tests
# each test will be run in a separate venv to verify that requirements have been properly specified
Expand Down