From ac8cff75ce3afbf2b3423088a6b9a9b932b1dbfd Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Tue, 20 Dec 2022 16:06:25 -0500 Subject: [PATCH] Run collection sanity tests in CI (#13356) * Run collection sanity tests in CI This requires adding a Makefile install of ansible-core Fake the version to make semver check happy * Fixes from ansible-test sanity failures * Exclude the export module due to awxkit requirement * Fix broken ansible-test rule exceptions remove Ansible 2.14 exclusions that make ansible-test ERROR, saying they are not needed --- .github/workflows/ci.yml | 3 + Makefile | 16 +++- .../plugins/inventory/controller.py | 1 - .../plugins/lookup/controller_api.py | 2 +- .../plugins/lookup/schedule_rrule.py | 2 +- .../plugins/lookup/schedule_rruleset.py | 13 +-- .../plugins/module_utils/controller_api.py | 12 ++- .../plugins/modules/ad_hoc_command.py | 1 - .../plugins/modules/ad_hoc_command_cancel.py | 1 + .../plugins/modules/execution_environment.py | 4 +- awx_collection/plugins/modules/export.py | 10 +++ .../plugins/modules/job_template.py | 2 +- awx_collection/plugins/modules/project.py | 2 - awx_collection/plugins/modules/schedule.py | 1 - .../plugins/modules/subscriptions.py | 1 + awx_collection/plugins/modules/token.py | 1 - .../plugins/modules/workflow_job_template.py | 23 +++-- .../modules/workflow_job_template_node.py | 1 - awx_collection/test/awx/conftest.py | 2 +- awx_collection/test/awx/test_project.py | 2 +- awx_collection/tests/sanity/ignore-2.14.txt | 88 ------------------- .../roles/template_galaxy/tasks/main.yml | 7 ++ 22 files changed, 73 insertions(+), 122 deletions(-) delete mode 100644 awx_collection/tests/sanity/ignore-2.14.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b47efc0c3a..5e9a72392f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,9 @@ jobs: - name: awx-collection command: /start_tests.sh test_collection_all label: Run Collection Tests + - name: awx-collection-sanity + command: /start_tests.sh test_collection_sanity + label: Run Ansible core Collection Sanity tests - name: api-schema label: Check API Schema command: /start_tests.sh detect-schema-change SCHEMA_DIFF_BASE_BRANCH=${{ github.event.pull_request.base.ref }} diff --git a/Makefile b/Makefile index a7709849e3..3a85428e93 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,9 @@ CHROMIUM_BIN=/tmp/chrome-linux/chrome GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) MANAGEMENT_COMMAND ?= awx-manage VERSION := $(shell $(PYTHON) tools/scripts/scm_version.py) -COLLECTION_VERSION := $(shell $(PYTHON) tools/scripts/scm_version.py | cut -d . -f 1-3) + +# ansible-test requires semver compatable version, so we allow overrides to hack it +COLLECTION_VERSION ?= $(shell $(PYTHON) tools/scripts/scm_version.py | cut -d . -f 1-3) # NOTE: This defaults the container image version to the branch that's active COMPOSE_TAG ?= $(GIT_BRANCH) @@ -300,7 +302,8 @@ test_collection: if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi && \ - pip install ansible-core && \ + if ! [ -x "$(shell command -v ansible-playbook)" ]; then pip install ansible-core; fi + ansible --version py.test $(COLLECTION_TEST_DIRS) -v # The python path needs to be modified so that the tests can find Ansible within the container # First we will use anything expility set as PYTHONPATH @@ -330,8 +333,13 @@ install_collection: build_collection rm -rf $(COLLECTION_INSTALL) ansible-galaxy collection install awx_collection_build/$(COLLECTION_NAMESPACE)-$(COLLECTION_PACKAGE)-$(COLLECTION_VERSION).tar.gz -test_collection_sanity: install_collection - cd $(COLLECTION_INSTALL) && ansible-test sanity +test_collection_sanity: + rm -rf awx_collection_build/ + rm -rf $(COLLECTION_INSTALL) + if ! [ -x "$(shell command -v ansible-test)" ]; then pip install ansible-core; fi + ansible --version + COLLECTION_VERSION=1.0.0 make install_collection + cd $(COLLECTION_INSTALL) && ansible-test sanity --exclude=plugins/modules/export.py test_collection_integration: install_collection cd $(COLLECTION_INSTALL) && ansible-test integration $(COLLECTION_TEST_TARGET) diff --git a/awx_collection/plugins/inventory/controller.py b/awx_collection/plugins/inventory/controller.py index 9cf36550a1..ee049c80fd 100644 --- a/awx_collection/plugins/inventory/controller.py +++ b/awx_collection/plugins/inventory/controller.py @@ -7,7 +7,6 @@ __metaclass__ = type DOCUMENTATION = ''' name: controller -plugin_type: inventory author: - Matthew Jones (@matburt) - Yunfan Zhang (@YunfanZhang42) diff --git a/awx_collection/plugins/lookup/controller_api.py b/awx_collection/plugins/lookup/controller_api.py index 94111c816d..6f0d07a4d7 100644 --- a/awx_collection/plugins/lookup/controller_api.py +++ b/awx_collection/plugins/lookup/controller_api.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ -lookup: controller_api +name: controller_api author: John Westcott IV (@john-westcott-iv) short_description: Search the API for objects requirements: diff --git a/awx_collection/plugins/lookup/schedule_rrule.py b/awx_collection/plugins/lookup/schedule_rrule.py index b623dfc86e..5f1d34c0ae 100644 --- a/awx_collection/plugins/lookup/schedule_rrule.py +++ b/awx_collection/plugins/lookup/schedule_rrule.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ - lookup: schedule_rrule + name: schedule_rrule author: John Westcott IV (@john-westcott-iv) short_description: Generate an rrule string which can be used for Schedules requirements: diff --git a/awx_collection/plugins/lookup/schedule_rruleset.py b/awx_collection/plugins/lookup/schedule_rruleset.py index a32b9d51cb..63d8f97fcc 100644 --- a/awx_collection/plugins/lookup/schedule_rruleset.py +++ b/awx_collection/plugins/lookup/schedule_rruleset.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ - lookup: schedule_rruleset + name: schedule_rruleset author: John Westcott IV (@john-westcott-iv) short_description: Generate an rruleset string requirements: @@ -31,7 +31,8 @@ DOCUMENTATION = """ rules: description: - Array of rules in the rruleset - type: array + type: list + elements: dict required: True suboptions: frequency: @@ -192,14 +193,14 @@ class LookupModule(LookupBase): # something: [1,2,3] - A list of ints return_values = [] # If they give us a single int, lets make it a list of ints - if type(rule[field_name]) == int: + if isinstance(rule[field_name], int): rule[field_name] = [rule[field_name]] # If its not a list, we need to split it into a list - if type(rule[field_name]) != list: + if isinstance(rule[field_name], list): rule[field_name] = rule[field_name].split(',') for value in rule[field_name]: # If they have a list of strs we want to strip the str incase its space delineated - if type(value) == str: + if isinstance(value, str): value = value.strip() # If value happens to be an int (from a list of ints) we need to coerce it into a str for the re.match if not re.match(r"^\d+$", str(value)) or int(value) < min_value or int(value) > max_value: @@ -209,7 +210,7 @@ class LookupModule(LookupBase): def process_list(self, field_name, rule, valid_list, rule_number): return_values = [] - if type(rule[field_name]) != list: + if isinstance(rule[field_name], list): rule[field_name] = rule[field_name].split(',') for value in rule[field_name]: value = value.strip() diff --git a/awx_collection/plugins/module_utils/controller_api.py b/awx_collection/plugins/module_utils/controller_api.py index 50d10f8104..92c36b5415 100644 --- a/awx_collection/plugins/module_utils/controller_api.py +++ b/awx_collection/plugins/module_utils/controller_api.py @@ -4,6 +4,7 @@ __metaclass__ = type from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError +from ansible.module_utils.parsing.convert_bool import boolean as strtobool from ansible.module_utils.six import PY2 from ansible.module_utils.six import raise_from, string_types from ansible.module_utils.six.moves import StringIO @@ -11,14 +12,21 @@ from ansible.module_utils.six.moves.urllib.error import HTTPError from ansible.module_utils.six.moves.http_cookiejar import CookieJar from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError -from distutils.version import LooseVersion as Version from socket import getaddrinfo, IPPROTO_TCP import time import re from json import loads, dumps from os.path import isfile, expanduser, split, join, exists, isdir from os import access, R_OK, getcwd -from distutils.util import strtobool + + +try: + from ansible.module_utils.compat.version import LooseVersion as Version +except ImportError: + try: + from distutils.version import LooseVersion as Version + except ImportError: + raise AssertionError('To use this plugin or module with ansible-core 2.11, you need to use Python < 3.12 with distutils.version present') try: import yaml diff --git a/awx_collection/plugins/modules/ad_hoc_command.py b/awx_collection/plugins/modules/ad_hoc_command.py index cb6e24a255..f453cef2f8 100644 --- a/awx_collection/plugins/modules/ad_hoc_command.py +++ b/awx_collection/plugins/modules/ad_hoc_command.py @@ -55,7 +55,6 @@ options: description: - The arguments to pass to the module. type: str - default: "" forks: description: - The number of forks to use for this ad hoc execution. diff --git a/awx_collection/plugins/modules/ad_hoc_command_cancel.py b/awx_collection/plugins/modules/ad_hoc_command_cancel.py index 98a0d414cb..12cdaeaaf9 100644 --- a/awx_collection/plugins/modules/ad_hoc_command_cancel.py +++ b/awx_collection/plugins/modules/ad_hoc_command_cancel.py @@ -42,6 +42,7 @@ options: - Maximum time in seconds to wait for a job to finish. - Not specifying means the task will wait until the controller cancels the command. type: int + default: 0 extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/execution_environment.py b/awx_collection/plugins/modules/execution_environment.py index 5544225ab2..8401944693 100644 --- a/awx_collection/plugins/modules/execution_environment.py +++ b/awx_collection/plugins/modules/execution_environment.py @@ -80,9 +80,9 @@ def main(): name=dict(required=True), new_name=dict(), image=dict(required=True), - description=dict(default=''), + description=dict(), organization=dict(), - credential=dict(default=''), + credential=dict(), state=dict(choices=['present', 'absent'], default='present'), pull=dict(choices=['always', 'missing', 'never'], default='missing'), ) diff --git a/awx_collection/plugins/modules/export.py b/awx_collection/plugins/modules/export.py index 8e737b14b9..d04e996056 100644 --- a/awx_collection/plugins/modules/export.py +++ b/awx_collection/plugins/modules/export.py @@ -86,6 +86,16 @@ options: - workflow names to export type: list elements: str + applications: + description: + - OAuth2 application names to export + type: list + elements: str + schedules: + description: + - schedule names to export + type: list + elements: str requirements: - "awxkit >= 9.3.0" notes: diff --git a/awx_collection/plugins/modules/job_template.py b/awx_collection/plugins/modules/job_template.py index 84d0450767..98c35f52f2 100644 --- a/awx_collection/plugins/modules/job_template.py +++ b/awx_collection/plugins/modules/job_template.py @@ -266,6 +266,7 @@ options: description: - Maximum time in seconds to wait for a job to finish (server-side). type: int + default: 0 job_slice_count: description: - The number of jobs to slice into at runtime. Will cause the Job Template to launch a workflow if value is greater than 1. @@ -287,7 +288,6 @@ options: description: - Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true. type: str - default: '' labels: description: - The labels applied to this job template diff --git a/awx_collection/plugins/modules/project.py b/awx_collection/plugins/modules/project.py index 4a7c7a4ffd..c224f309f0 100644 --- a/awx_collection/plugins/modules/project.py +++ b/awx_collection/plugins/modules/project.py @@ -60,12 +60,10 @@ options: description: - The branch to use for the SCM resource. type: str - default: '' scm_refspec: description: - The refspec to use for the SCM resource. type: str - default: '' credential: description: - Name of the credential to use with this SCM resource. diff --git a/awx_collection/plugins/modules/schedule.py b/awx_collection/plugins/modules/schedule.py index d0fac2384e..5bf821025d 100644 --- a/awx_collection/plugins/modules/schedule.py +++ b/awx_collection/plugins/modules/schedule.py @@ -51,7 +51,6 @@ options: - Specify C(extra_vars) for the template. required: False type: dict - default: {} forks: description: - Forks applied as a prompt, assuming job template prompts for forks diff --git a/awx_collection/plugins/modules/subscriptions.py b/awx_collection/plugins/modules/subscriptions.py index 4cb2506af9..146ef7c451 100644 --- a/awx_collection/plugins/modules/subscriptions.py +++ b/awx_collection/plugins/modules/subscriptions.py @@ -39,6 +39,7 @@ options: - Note This is a client side search, not an API side search required: False type: dict + default: {} extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/token.py b/awx_collection/plugins/modules/token.py index 27e72fe68d..c0089240f4 100644 --- a/awx_collection/plugins/modules/token.py +++ b/awx_collection/plugins/modules/token.py @@ -35,7 +35,6 @@ options: - Optional description of this access token. required: False type: str - default: '' application: description: - The application tied to this token. diff --git a/awx_collection/plugins/modules/workflow_job_template.py b/awx_collection/plugins/modules/workflow_job_template.py index e1667b3b21..ba4d5cf102 100644 --- a/awx_collection/plugins/modules/workflow_job_template.py +++ b/awx_collection/plugins/modules/workflow_job_template.py @@ -214,7 +214,8 @@ options: type: int job_slice_count: description: - - The number of jobs to slice into at runtime, if job template prompts for job slices. Will cause the Job Template to launch a workflow if value is greater than 1. + - The number of jobs to slice into at runtime, if job template prompts for job slices. + - Will cause the Job Template to launch a workflow if value is greater than 1. type: int default: '1' timeout: @@ -328,42 +329,46 @@ options: - Nodes that will run after this node completes. - List of node identifiers. type: list + elements: dict suboptions: identifier: description: - Identifier of Node that will run after this node completes given this option. - elements: str + type: str success_nodes: description: - Nodes that will run after this node on success. - List of node identifiers. type: list + elements: dict suboptions: identifier: description: - Identifier of Node that will run after this node completes given this option. - elements: str + type: str failure_nodes: description: - Nodes that will run after this node on failure. - List of node identifiers. type: list + elements: dict suboptions: identifier: description: - Identifier of Node that will run after this node completes given this option. - elements: str + type: str credentials: description: - Credentials to be applied to job as launch-time prompts. - List of credential names. - Uniqueness is not handled rigorously. type: list + elements: dict suboptions: name: description: - Name Credentials to be applied to job as launch-time prompts. - elements: str + type: str organization: description: - Name of key for use in model for organizational reference @@ -379,11 +384,12 @@ options: - List of Label names. - Uniqueness is not handled rigorously. type: list + elements: dict suboptions: name: description: - Name Labels to be applied to job as launch-time prompts. - elements: str + type: str organization: description: - Name of key for use in model for organizational reference @@ -399,11 +405,12 @@ options: - List of Instance group names. - Uniqueness is not handled rigorously. type: list + elements: dict suboptions: name: description: - Name of Instance groups to be applied to job as launch-time prompts. - elements: str + type: str destroy_current_nodes: description: - Set in order to destroy current workflow_nodes on the workflow. @@ -789,7 +796,7 @@ def main(): allow_simultaneous=dict(type='bool'), ask_variables_on_launch=dict(type='bool'), ask_labels_on_launch=dict(type='bool', aliases=['ask_labels']), - ask_tags_on_launch=dict(type='bool'), + ask_tags_on_launch=dict(type='bool', aliases=['ask_tags']), ask_skip_tags_on_launch=dict(type='bool', aliases=['ask_skip_tags']), inventory=dict(), limit=dict(), diff --git a/awx_collection/plugins/modules/workflow_job_template_node.py b/awx_collection/plugins/modules/workflow_job_template_node.py index f91d308282..61f713f843 100644 --- a/awx_collection/plugins/modules/workflow_job_template_node.py +++ b/awx_collection/plugins/modules/workflow_job_template_node.py @@ -30,7 +30,6 @@ options: - Variables to apply at launch time. - Will only be accepted if job template prompts for vars or has a survey asking for those vars. type: dict - default: {} inventory: description: - Inventory applied as a prompt, if job template prompts for inventory diff --git a/awx_collection/test/awx/conftest.py b/awx_collection/test/awx/conftest.py index 42325da6fb..626f859363 100644 --- a/awx_collection/test/awx/conftest.py +++ b/awx_collection/test/awx/conftest.py @@ -159,7 +159,7 @@ def run_module(request, collection_import): elif getattr(resource_module, 'TowerLegacyModule', None): resource_class = resource_module.TowerLegacyModule else: - raise ("The module has neither a TowerLegacyModule, ControllerAWXKitModule or a ControllerAPIModule") + raise RuntimeError("The module has neither a TowerLegacyModule, ControllerAWXKitModule or a ControllerAPIModule") with mock.patch.object(resource_class, '_load_params', new=mock_load_params): # Call the test utility (like a mock server) instead of issuing HTTP requests diff --git a/awx_collection/test/awx/test_project.py b/awx_collection/test/awx/test_project.py index 9f6da405db..8ec3e8816e 100644 --- a/awx_collection/test/awx/test_project.py +++ b/awx_collection/test/awx/test_project.py @@ -14,7 +14,7 @@ def test_create_project(run_module, admin_user, organization, silence_warning): dict(name='foo', organization=organization.name, scm_type='git', scm_url='https://foo.invalid', wait=False, scm_update_cache_timeout=5), admin_user, ) - silence_warning.assert_called_once_with('scm_update_cache_timeout will be ignored since scm_update_on_launch ' 'was not set to true') + silence_warning.assert_called_once_with('scm_update_cache_timeout will be ignored since scm_update_on_launch was not set to true') assert result.pop('changed', None), result diff --git a/awx_collection/tests/sanity/ignore-2.14.txt b/awx_collection/tests/sanity/ignore-2.14.txt deleted file mode 100644 index cd98399548..0000000000 --- a/awx_collection/tests/sanity/ignore-2.14.txt +++ /dev/null @@ -1,88 +0,0 @@ -plugins/module_utils/awxkit.py import-3.9 -plugins/module_utils/controller_api.py import-3.9 -plugins/modules/ad_hoc_command.py import-3.9 -plugins/modules/ad_hoc_command_cancel.py import-3.9 -plugins/modules/ad_hoc_command_wait.py import-3.9 -plugins/modules/application.py import-3.9 -plugins/modules/controller_meta.py import-3.9 -plugins/modules/credential.py import-3.92 -plugins/modules/credential_input_source.py import-3.9 -plugins/modules/credential_type.py import-3.9 -plugins/modules/execution_environment.py import-3.9 -plugins/modules/export.py import-3.9 -plugins/modules/group.py import-3.9 -plugins/modules/host.py import-3.9 -plugins/modules/import.py import-3.9 -plugins/modules/instance.py import-3.9 -plugins/modules/instance_group.py import-3.9 -plugins/modules/inventory.py import-3.9 -plugins/modules/inventory_source.py import-3.9 -plugins/modules/inventory_source_update.py import-3.9 -plugins/modules/job_cancel.py import-3.9 -plugins/modules/job_launch.py import-3.9 -plugins/modules/job_list.py import-3.9 -plugins/modules/job_template.py import-3.93 -plugins/modules/job_wait.py import-3.9 -plugins/modules/label.py import-3.9 -plugins/modules/license.py import-3.9 -plugins/modules/notification_template.py import-3.9 -plugins/modules/organization.py import-3.9 -plugins/modules/project.py import-3.92 -plugins/modules/project_update.py import-3.9 -plugins/modules/role.py import-3.9 -plugins/modules/schedule.py import-3.9 -plugins/modules/settings.py import-3.9 -plugins/modules/subscriptions.py import-3.9 -plugins/modules/team.py import-3.9 -plugins/modules/token.py import-3.9 -plugins/modules/user.py import-3.9 -plugins/modules/workflow_approval.py import-3.9 -plugins/modules/workflow_job_template.py import-3.9 -plugins/modules/workflow_job_template_node.py import-3.9 -plugins/modules/workflow_launch.py import-3.9 -plugins/modules/workflow_node_wait.py import-3.9 -plugins/inventory/controller.py import-3.10 -plugins/lookup/controller_api.py import-3.10 -plugins/module_utils/awxkit.py import-3.10 -plugins/module_utils/controller_api.py import-3.10 -plugins/modules/ad_hoc_command.py import-3.10 -plugins/modules/ad_hoc_command_cancel.py import-3.10 -plugins/modules/ad_hoc_command_wait.py import-3.10 -plugins/modules/application.py import-3.10 -plugins/modules/controller_meta.py import-3.10 -plugins/modules/credential.py import-3.10 -plugins/modules/credential_input_source.py import-3.10 -plugins/modules/credential_type.py import-3.10 -plugins/modules/execution_environment.py import-3.10 -plugins/modules/export.py import-3.10 -plugins/modules/group.py import-3.10 -plugins/modules/host.py import-3.10 -plugins/modules/import.py import-3.10 -plugins/modules/instance.py import-3.10 -plugins/modules/instance_group.py import-3.10 -plugins/modules/inventory.py import-3.10 -plugins/modules/inventory_source.py import-3.10 -plugins/modules/inventory_source_update.py import-3.10 -plugins/modules/job_cancel.py import-3.10 -plugins/modules/job_launch.py import-3.10 -plugins/modules/job_list.py import-3.10 -plugins/modules/job_template.py import-3.10 -plugins/modules/job_wait.py import-3.10 -plugins/modules/label.py import-3.10 -plugins/modules/license.py import-3.10 -plugins/modules/notification_template.py import-3.10 -plugins/modules/organization.py import-3.10 -plugins/modules/project.py import-3.10 -plugins/modules/project_update.py import-3.10 -plugins/modules/role.py import-3.10 -plugins/modules/schedule.py import-3.10 -plugins/modules/settings.py import-3.10 -plugins/modules/subscriptions.py import-3.10 -plugins/modules/team.py import-3.10 -plugins/modules/token.py import-3.10 -plugins/modules/user.py import-3.10 -plugins/modules/workflow_approval.py import-3.10 -plugins/modules/workflow_job_template.py import-3.10 -plugins/modules/workflow_job_template_node.py import-3.10 -plugins/modules/workflow_launch.py import-3.10 -plugins/modules/workflow_node_wait.py import-3.10 diff --git a/awx_collection/tools/roles/template_galaxy/tasks/main.yml b/awx_collection/tools/roles/template_galaxy/tasks/main.yml index aaa21a023f..3a213f5997 100644 --- a/awx_collection/tools/roles/template_galaxy/tasks/main.yml +++ b/awx_collection/tools/roles/template_galaxy/tasks/main.yml @@ -1,4 +1,11 @@ --- +- name: Sanity assertions, that some variables have a non-blank value + assert: + that: + - collection_version + - collection_package + - collection_path + - name: Set the collection version in the controller_api.py file replace: path: "{{ collection_path }}/plugins/module_utils/controller_api.py"