diff --git a/.ansible-lint b/.ansible-lint index 8ff1ec7e..8c6a6746 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -15,6 +15,7 @@ kinds: skip_list: - fqcn-builtins - var-naming[no-role-prefix] + - sanity[cannot-ignore] exclude_paths: - tests/roles/ - .github/ diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 4f8a9799..6bf4ccd9 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,3 +1,4 @@ +--- # Default state for all rules default: true diff --git a/.sanity-ansible-ignore-2.13.txt b/.sanity-ansible-ignore-2.13.txt index 608d7700..ebe4703e 100644 --- a/.sanity-ansible-ignore-2.13.txt +++ b/.sanity-ansible-ignore-2.13.txt @@ -1,31 +1,22 @@ +plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license +tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip +tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip plugins/modules/pcs_api_v2.py compile-2.7!skip plugins/modules/pcs_api_v2.py import-2.7!skip -plugins/modules/pcs_api_v2.py import-3.5!skip plugins/modules/pcs_api_v2.py import-3.6!skip plugins/modules/pcs_api_v2.py import-3.7!skip plugins/modules/pcs_api_v2.py import-3.8!skip -plugins/modules/pcs_api_v2.py import-3.9!skip -plugins/modules/pcs_api_v2.py import-3.10!skip -plugins/modules/pcs_api_v2.py validate-modules:import-error -plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.8!skip plugins/modules/pcs_qdevice_certs.py compile-2.7!skip plugins/modules/pcs_qdevice_certs.py import-2.7!skip -plugins/modules/pcs_qdevice_certs.py import-3.5!skip plugins/modules/pcs_qdevice_certs.py import-3.6!skip plugins/modules/pcs_qdevice_certs.py import-3.7!skip plugins/modules/pcs_qdevice_certs.py import-3.8!skip -plugins/modules/pcs_qdevice_certs.py import-3.9!skip -plugins/modules/pcs_qdevice_certs.py import-3.10!skip -plugins/modules/pcs_qdevice_certs.py validate-modules:import-error -plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.9!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.10!skip -tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip -tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip diff --git a/.sanity-ansible-ignore-2.14.txt b/.sanity-ansible-ignore-2.14.txt index 60eb30b5..33afc64e 100644 --- a/.sanity-ansible-ignore-2.14.txt +++ b/.sanity-ansible-ignore-2.14.txt @@ -1,34 +1,28 @@ +plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license +tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip +tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip plugins/modules/pcs_api_v2.py compile-2.7!skip +plugins/modules/pcs_api_v2.py compile-3.5!skip plugins/modules/pcs_api_v2.py import-2.7!skip plugins/modules/pcs_api_v2.py import-3.5!skip plugins/modules/pcs_api_v2.py import-3.6!skip plugins/modules/pcs_api_v2.py import-3.7!skip plugins/modules/pcs_api_v2.py import-3.8!skip -plugins/modules/pcs_api_v2.py import-3.9!skip -plugins/modules/pcs_api_v2.py import-3.10!skip -plugins/modules/pcs_api_v2.py import-3.11!skip -plugins/modules/pcs_api_v2.py validate-modules:import-error -plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.5!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.5!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.8!skip plugins/modules/pcs_qdevice_certs.py compile-2.7!skip +plugins/modules/pcs_qdevice_certs.py compile-3.5!skip plugins/modules/pcs_qdevice_certs.py import-2.7!skip plugins/modules/pcs_qdevice_certs.py import-3.5!skip plugins/modules/pcs_qdevice_certs.py import-3.6!skip plugins/modules/pcs_qdevice_certs.py import-3.7!skip plugins/modules/pcs_qdevice_certs.py import-3.8!skip -plugins/modules/pcs_qdevice_certs.py import-3.9!skip -plugins/modules/pcs_qdevice_certs.py import-3.10!skip -plugins/modules/pcs_qdevice_certs.py import-3.11!skip -plugins/modules/pcs_qdevice_certs.py validate-modules:import-error -plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.9!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.10!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.11!skip -tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip -tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip diff --git a/.sanity-ansible-ignore-2.15.txt b/.sanity-ansible-ignore-2.15.txt index 60eb30b5..33afc64e 100644 --- a/.sanity-ansible-ignore-2.15.txt +++ b/.sanity-ansible-ignore-2.15.txt @@ -1,34 +1,28 @@ +plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license +tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip +tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip plugins/modules/pcs_api_v2.py compile-2.7!skip +plugins/modules/pcs_api_v2.py compile-3.5!skip plugins/modules/pcs_api_v2.py import-2.7!skip plugins/modules/pcs_api_v2.py import-3.5!skip plugins/modules/pcs_api_v2.py import-3.6!skip plugins/modules/pcs_api_v2.py import-3.7!skip plugins/modules/pcs_api_v2.py import-3.8!skip -plugins/modules/pcs_api_v2.py import-3.9!skip -plugins/modules/pcs_api_v2.py import-3.10!skip -plugins/modules/pcs_api_v2.py import-3.11!skip -plugins/modules/pcs_api_v2.py validate-modules:import-error -plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.5!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.5!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.8!skip plugins/modules/pcs_qdevice_certs.py compile-2.7!skip +plugins/modules/pcs_qdevice_certs.py compile-3.5!skip plugins/modules/pcs_qdevice_certs.py import-2.7!skip plugins/modules/pcs_qdevice_certs.py import-3.5!skip plugins/modules/pcs_qdevice_certs.py import-3.6!skip plugins/modules/pcs_qdevice_certs.py import-3.7!skip plugins/modules/pcs_qdevice_certs.py import-3.8!skip -plugins/modules/pcs_qdevice_certs.py import-3.9!skip -plugins/modules/pcs_qdevice_certs.py import-3.10!skip -plugins/modules/pcs_qdevice_certs.py import-3.11!skip -plugins/modules/pcs_qdevice_certs.py validate-modules:import-error -plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.5!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.9!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.10!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.11!skip -tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip -tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip diff --git a/.sanity-ansible-ignore-2.16.txt b/.sanity-ansible-ignore-2.16.txt index 0445e070..ebe4703e 100644 --- a/.sanity-ansible-ignore-2.16.txt +++ b/.sanity-ansible-ignore-2.16.txt @@ -1,33 +1,22 @@ +plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license +tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip +tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip plugins/modules/pcs_api_v2.py compile-2.7!skip plugins/modules/pcs_api_v2.py import-2.7!skip plugins/modules/pcs_api_v2.py import-3.6!skip plugins/modules/pcs_api_v2.py import-3.7!skip plugins/modules/pcs_api_v2.py import-3.8!skip -plugins/modules/pcs_api_v2.py import-3.9!skip -plugins/modules/pcs_api_v2.py import-3.10!skip -plugins/modules/pcs_api_v2.py import-3.11!skip -plugins/modules/pcs_api_v2.py import-3.12!skip -plugins/modules/pcs_api_v2.py validate-modules:import-error -plugins/modules/pcs_api_v2.py validate-modules:missing-gplv3-license +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.6!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.7!skip +plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-3.8!skip plugins/modules/pcs_qdevice_certs.py compile-2.7!skip plugins/modules/pcs_qdevice_certs.py import-2.7!skip plugins/modules/pcs_qdevice_certs.py import-3.6!skip plugins/modules/pcs_qdevice_certs.py import-3.7!skip plugins/modules/pcs_qdevice_certs.py import-3.8!skip -plugins/modules/pcs_qdevice_certs.py import-3.9!skip -plugins/modules/pcs_qdevice_certs.py import-3.10!skip -plugins/modules/pcs_qdevice_certs.py import-3.11!skip -plugins/modules/pcs_qdevice_certs.py import-3.12!skip -plugins/modules/pcs_qdevice_certs.py validate-modules:import-error -plugins/modules/pcs_qdevice_certs.py validate-modules:missing-gplv3-license -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py compile-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-2.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.6!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.7!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.8!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.9!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.10!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.11!skip -plugins/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py import-3.12!skip -tests/ha_cluster/unit/test_pcs_api_v2.py shebang!skip -tests/ha_cluster/unit/test_pcs_qdevice_certs.py shebang!skip diff --git a/library/pcs_api_v2.py b/library/pcs_api_v2.py index c9922aa6..c2d01df4 100644 --- a/library/pcs_api_v2.py +++ b/library/pcs_api_v2.py @@ -36,9 +36,11 @@ cmd_params: description: parameters of the command specified in cmd_name type: dict + default: {} cmd_options: description: generic command options type: dict + default: {} suboptions: request_timeout: description: request timeout @@ -119,8 +121,25 @@ # pylint: disable=no-name-in-module from ansible.module_utils.ha_cluster_lsr import pcs_api_v2_utils as api_utils +import traceback + # pylint: enable=no-name-in-module -from pcs.common.async_tasks.dto import CommandDto, CommandOptionsDto +try: + from pcs.common.async_tasks.dto import CommandDto, CommandOptionsDto +except ImportError: + HAS_PCS = False + PCS_IMPORT_ERROR = traceback.format_exc() + + class CommandOptionsDto(object): + def __init__(self, **kwargs): + pass + + class CommandDto(object): + pass + +else: + HAS_PCS = True + PCS_IMPORT_ERROR = None def run_module() -> None: diff --git a/library/pcs_qdevice_certs.py b/library/pcs_qdevice_certs.py index b2b0517a..e14fabed 100644 --- a/library/pcs_qdevice_certs.py +++ b/library/pcs_qdevice_certs.py @@ -37,6 +37,7 @@ cmd_options: description: pcs API v2 command options type: dict + default: {} suboptions: request_timeout: description: request timeout @@ -112,8 +113,25 @@ # pylint: disable=no-name-in-module from ansible.module_utils.ha_cluster_lsr import pcs_api_v2_utils as api_utils +import traceback + # pylint: enable=no-name-in-module -from pcs.common.async_tasks.dto import CommandDto, CommandOptionsDto +try: + from pcs.common.async_tasks.dto import CommandDto, CommandOptionsDto +except ImportError: + HAS_PCS = False + PCS_IMPORT_ERROR = traceback.format_exc() + + class CommandOptionsDto(object): + def __init__(self, **kwargs): + pass + + class CommandDto(object): + pass + +else: + HAS_PCS = True + PCS_IMPORT_ERROR = None def run_module() -> None: diff --git a/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py b/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py index b4c1d200..d51f61d8 100644 --- a/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py +++ b/module_utils/ha_cluster_lsr/pcs_api_v2_utils.py @@ -13,6 +13,8 @@ # pylint: enable=invalid-name import sys +import traceback + # Add paths to pcs bundled libraries to make Dacite available sys.path.insert(0, "/usr/lib64/pcs/pcs_bundled/packages/") @@ -24,15 +26,45 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url -from dacite import DaciteError -from pcs.common.async_tasks.dto import ( - CommandDto, - CommandOptionsDto, - TaskResultDto, -) -from pcs.common.async_tasks.types import TaskFinishType, TaskKillReason -from pcs.common.interface.dto import from_dict, to_dict -from pcs.common.reports import ReportItemDto, ReportItemSeverity + +try: + from dacite import DaciteError +except ImportError: + HAS_DACITE = False + DACITE_IMPORT_ERROR = traceback.format_exc() +else: + HAS_DACITE = True + DACITE_IMPORT_ERROR = None + +try: + from pcs.common.async_tasks.dto import ( + CommandDto, + CommandOptionsDto, + TaskResultDto, + ) + from pcs.common.async_tasks.types import TaskFinishType, TaskKillReason + from pcs.common.interface.dto import from_dict, to_dict + from pcs.common.reports import ReportItemDto, ReportItemSeverity +except ImportError: + HAS_PCS = False + PCS_IMPORT_ERROR = traceback.format_exc() + + class CommandOptionsDto(object): + def __init__(self, **kwargs): + pass + + class ReportItemDto(object): + pass + + class TaskResultDto(object): + pass + + class CommandDto(object): + pass + +else: + HAS_PCS = True + PCS_IMPORT_ERROR = None PCSD_SOCKET = "/var/run/pcsd.socket" API_ENDPOINT = "http://doesntmatter/api/v2/task/run" diff --git a/tasks/shell_pcs/sbd.yml b/tasks/shell_pcs/sbd.yml index ee7a4273..89b0eba7 100644 --- a/tasks/shell_pcs/sbd.yml +++ b/tasks/shell_pcs/sbd.yml @@ -20,7 +20,7 @@ loop: "{{ ha_cluster.sbd_watchdog_modules_blocklist | d([]) }}" - name: Unload watchdog kernel modules from blocklist - modprobe: + community.general.modprobe: name: "{{ item }}" state: absent loop: "{{ ha_cluster.sbd_watchdog_modules_blocklist | d([]) }}" @@ -38,7 +38,7 @@ loop: "{{ ha_cluster.sbd_watchdog_modules | d([]) }}" - name: Load watchdog kernel modules - modprobe: + community.general.modprobe: name: "{{ item }}" state: present loop: "{{ ha_cluster.sbd_watchdog_modules | d([]) }}" diff --git a/tests/tests_include_vars_from_parent.yml b/tests/tests_include_vars_from_parent.yml index e1fceadc..f41c400a 100644 --- a/tests/tests_include_vars_from_parent.yml +++ b/tests/tests_include_vars_from_parent.yml @@ -1,7 +1,7 @@ --- -- name: Test include vars from parent - gather_facts: true +- name: Test role include variable override hosts: all + gather_facts: true tasks: - name: Create var file in caller that can override the one in called role delegate_to: localhost @@ -13,7 +13,7 @@ # XXX ugly, self-modifying code - changes the "caller" role on # the controller dest: "{{ playbook_dir }}/roles/caller/vars/{{ item }}.yml" - mode: 0666 + mode: preserve loop: "{{ varfiles | unique }}" # In case the playbook is executed against multiple hosts, use # only the first one. Otherwise the hosts would stomp on each @@ -38,15 +38,18 @@ varfiles: "{{ [facts['distribution']] | product(separators) | map('join') | product(versions) | map('join') | list + [facts['distribution'], facts['os_family']] }}" - - - name: Set up test environment - include_role: - name: linux-system-roles.ha_cluster - tasks_from: test_setup.yml + register: __varfiles_created - name: Import role import_role: name: caller vars: - # noqa: var-naming[no-role-prefix] roletoinclude: linux-system-roles.ha_cluster + + - name: Cleanup + file: + path: "{{ item.dest }}" + state: absent + loop: "{{ __varfiles_created.results }}" + delegate_to: localhost + when: inventory_hostname == ansible_play_hosts_all[0] diff --git a/tox.ini b/tox.ini index 02fe62f4..4ca990b5 100644 --- a/tox.ini +++ b/tox.ini @@ -5,5 +5,11 @@ lsr_enable = true [lsr_ansible-lint] configfile = {toxinidir}/.ansible-lint +# NOTE: In order to run python unit tests locally, you will need +# to comment sitepackages=True then build and install the correct +# version of pcs in the testenv. See .github/workflows/python-unit-test.yml +# You will also need to comment out sitepackages=True in order to run +# most tox-lsr tests locally or you will get errors like this: +# ansible-lint-collection: failed with ansible-lint is not allowed, use allowlist_externals to allow it [testenv] sitepackages=True