From 2e4e687d694c9b559189224e785ae7c104145bb4 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Mon, 17 Feb 2020 09:20:54 -0500 Subject: [PATCH] Optional tower cli (#3) * Allow running tests without tower_cli * patch up test mutability * Fix test import error, warning mock * flake8 error Update documentation for non-converted modules --- awx_collection/plugins/doc_fragments/auth.py | 3 --- .../plugins/module_utils/tower_api.py | 7 ++--- .../plugins/modules/tower_credential.py | 4 +++ .../plugins/modules/tower_job_template.py | 5 ++++ .../plugins/modules/tower_job_wait.py | 4 +++ awx_collection/plugins/modules/tower_label.py | 4 +++ .../plugins/modules/tower_notification.py | 4 +++ awx_collection/plugins/modules/tower_role.py | 4 +++ .../plugins/modules/tower_workflow_launch.py | 2 ++ .../modules/tower_workflow_template.py | 4 +++ awx_collection/test/awx/conftest.py | 17 ++++++++++-- awx_collection/test/awx/test_group.py | 5 ---- .../test/awx/test_inventory_source.py | 6 ----- awx_collection/test/awx/test_organization.py | 4 --- awx_collection/test/awx/test_project.py | 26 +++++++++---------- awx_collection/test/awx/test_send_receive.py | 5 +++- awx_collection/test/awx/test_team.py | 8 ------ 17 files changed, 67 insertions(+), 45 deletions(-) diff --git a/awx_collection/plugins/doc_fragments/auth.py b/awx_collection/plugins/doc_fragments/auth.py index 4187a17b2a..bc02d5f420 100644 --- a/awx_collection/plugins/doc_fragments/auth.py +++ b/awx_collection/plugins/doc_fragments/auth.py @@ -36,9 +36,6 @@ options: - Path to the Tower or AWX config file. type: path -requirements: -- ansible-tower-cli >= 3.0.2 - notes: - If no I(config_file) is provided we will attempt to use the tower-cli library defaults to find your Tower host information. diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index e03fbc06ac..c954fac4ac 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -33,7 +33,7 @@ class ItemNotDefined(Exception): class TowerModule(AnsibleModule): url = None - honorred_settings = ['host', 'username', 'password', 'verify_ssl', 'oauth_token'] + honorred_settings = ('host', 'username', 'password', 'verify_ssl', 'oauth_token') host = '127.0.0.1' username = None password = None @@ -43,7 +43,6 @@ class TowerModule(AnsibleModule): session = None cookie_jar = CookieJar() authenticated = False - json_output = {'changed': False} config_name = 'tower_cli.cfg' def __init__(self, argument_spec, **kwargs): @@ -58,6 +57,8 @@ class TowerModule(AnsibleModule): args.update(argument_spec) kwargs['supports_check_mode'] = True + self.json_output = {'changed': False} + # We have to take off mutually_exclusive_if in order to init with Ansible mutually_exclusive_if = kwargs.pop('mutually_exclusive_if', None) @@ -136,7 +137,7 @@ class TowerModule(AnsibleModule): raise ConfigFileException('The specified config file does not exist') if not access(config_path, R_OK): - raise ConfigFileException("The specified config file can not be read") + raise ConfigFileException("The specified config file cannot be read") # Read in the file contents: with open(config_path, 'r') as f: diff --git a/awx_collection/plugins/modules/tower_credential.py b/awx_collection/plugins/modules/tower_credential.py index 91643f65d4..867916332d 100644 --- a/awx_collection/plugins/modules/tower_credential.py +++ b/awx_collection/plugins/modules/tower_credential.py @@ -160,6 +160,10 @@ options: choices: ["present", "absent"] default: "present" type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 99e986e043..0fbd2d8fe3 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -219,7 +219,12 @@ options: default: "present" choices: ["present", "absent"] type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth + notes: - JSON for survey_spec can be found in Tower API Documentation. See U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html#/Job_Templates/Job_Templates_job_templates_survey_spec_create) diff --git a/awx_collection/plugins/modules/tower_job_wait.py b/awx_collection/plugins/modules/tower_job_wait.py index 87b1f9f27c..00011468e0 100644 --- a/awx_collection/plugins/modules/tower_job_wait.py +++ b/awx_collection/plugins/modules/tower_job_wait.py @@ -42,6 +42,10 @@ options: description: - Maximum time in seconds to wait for a job to finish. type: int + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_label.py b/awx_collection/plugins/modules/tower_label.py index e5085cfd27..c9c412b8b2 100644 --- a/awx_collection/plugins/modules/tower_label.py +++ b/awx_collection/plugins/modules/tower_label.py @@ -39,6 +39,10 @@ options: default: "present" choices: ["present", "absent"] type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_notification.py b/awx_collection/plugins/modules/tower_notification.py index b583efc056..ab79779915 100644 --- a/awx_collection/plugins/modules/tower_notification.py +++ b/awx_collection/plugins/modules/tower_notification.py @@ -191,6 +191,10 @@ options: default: "present" choices: ["present", "absent"] type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_role.py b/awx_collection/plugins/modules/tower_role.py index 533d64075f..38fb5ff203 100644 --- a/awx_collection/plugins/modules/tower_role.py +++ b/awx_collection/plugins/modules/tower_role.py @@ -68,6 +68,10 @@ options: default: "present" choices: ["present", "absent"] type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_workflow_launch.py b/awx_collection/plugins/modules/tower_workflow_launch.py index c0bf96fef6..a1ba4ea943 100644 --- a/awx_collection/plugins/modules/tower_workflow_launch.py +++ b/awx_collection/plugins/modules/tower_workflow_launch.py @@ -44,6 +44,8 @@ options: requirements: - "python >= 2.6" + - ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/plugins/modules/tower_workflow_template.py b/awx_collection/plugins/modules/tower_workflow_template.py index d0325cb0e5..e00ca48b6b 100644 --- a/awx_collection/plugins/modules/tower_workflow_template.py +++ b/awx_collection/plugins/modules/tower_workflow_template.py @@ -81,6 +81,10 @@ options: default: "present" choices: ["present", "absent"] type: str + +requirements: +- ansible-tower-cli >= 3.0.2 + extends_documentation_fragment: awx.awx.auth ''' diff --git a/awx_collection/test/awx/conftest.py b/awx_collection/test/awx/conftest.py index 49badc22dd..067a5b468b 100644 --- a/awx_collection/test/awx/conftest.py +++ b/awx_collection/test/awx/conftest.py @@ -5,7 +5,7 @@ import io import json import datetime import importlib -from contextlib import redirect_stdout +from contextlib import redirect_stdout, suppress from unittest import mock import logging @@ -16,6 +16,12 @@ import pytest from awx.main.tests.functional.conftest import _request from awx.main.models import Organization, Project, Inventory, Credential, CredentialType +try: + import tower_cli # noqa + HAS_TOWER_CLI = True +except ImportError: + HAS_TOWER_CLI = False + logger = logging.getLogger('awx.main.tests') @@ -104,7 +110,11 @@ def run_module(request): with mock.patch.object(resource_module.TowerModule, '_load_params', new=mock_load_params): # Call the test utility (like a mock server) instead of issuing HTTP requests with mock.patch('ansible.module_utils.urls.Request.open', new=new_open): - with mock.patch('tower_cli.api.Session.request', new=new_request): + if HAS_TOWER_CLI: + tower_cli_mgr = mock.patch('tower_cli.api.Session.request', new=new_request) + else: + tower_cli_mgr = suppress() + with tower_cli_mgr: # Ansible modules return data to the mothership over stdout with redirect_stdout(stdout_buffer): try: @@ -114,6 +124,9 @@ def run_module(request): module_stdout = stdout_buffer.getvalue().strip() result = json.loads(module_stdout) + # A module exception should never be a test expectation + if 'exception' in result: + raise Exception('Module encountered error:\n{0}'.format(result['exception'])) return result return rf diff --git a/awx_collection/test/awx/test_group.py b/awx_collection/test/awx/test_group.py index e4e8e1afac..4f0ea3e7b8 100644 --- a/awx_collection/test/awx/test_group.py +++ b/awx_collection/test/awx/test_group.py @@ -25,11 +25,9 @@ def test_create_group(run_module, admin_user): result.pop('invocation') assert result == { - 'credential_type': 'Nexus', 'id': group.id, 'name': 'Test Group', 'changed': True, - 'state': 'present' } @@ -54,8 +52,5 @@ def test_tower_group_idempotent(run_module, admin_user): result.pop('invocation') assert result == { 'id': group.id, - 'credential_type': 'Nexus', - 'name': 'Test Group', 'changed': False, # idempotency assertion - 'state': 'present' } diff --git a/awx_collection/test/awx/test_inventory_source.py b/awx_collection/test/awx/test_inventory_source.py index 6ab3b50354..dcdebbf44c 100644 --- a/awx_collection/test/awx/test_inventory_source.py +++ b/awx_collection/test/awx/test_inventory_source.py @@ -38,8 +38,6 @@ def test_inventory_source_create(run_module, admin_user, base_inventory): assert result == { 'id': inv_src.id, 'name': 'foo', - 'state': 'present', - 'credential_type': 'Nexus' } @@ -61,9 +59,7 @@ def test_create_inventory_source_implied_org(run_module, admin_user): result.pop('invocation') assert result == { - "credential_type": "Nexus", "name": "Test Inventory Source", - "state": "present", "id": inv_src.id, } @@ -90,9 +86,7 @@ def test_create_inventory_source_multiple_orgs(run_module, admin_user): result.pop('invocation') assert result == { - "credential_type": "Nexus", "name": "Test Inventory Source", - "state": "present", "id": inv_src.id, } diff --git a/awx_collection/test/awx/test_organization.py b/awx_collection/test/awx/test_organization.py index 95d6866d35..8f4872c303 100644 --- a/awx_collection/test/awx/test_organization.py +++ b/awx_collection/test/awx/test_organization.py @@ -30,8 +30,6 @@ def test_create_organization(run_module, admin_user): assert result == { "name": "foo", "changed": True, - "state": "present", - "credential_type": "Nexus", "id": org.id, "invocation": { "module_args": module_args @@ -55,8 +53,6 @@ def test_create_organization_with_venv(run_module, admin_user, mocker): org = Organization.objects.get(name='foo') result.pop('invocation') assert result == { - "credential_type": "Nexus", - "state": "present", "name": "foo", "id": org.id } diff --git a/awx_collection/test/awx/test_project.py b/awx_collection/test/awx/test_project.py index e99afd82dd..babe2edf54 100644 --- a/awx_collection/test/awx/test_project.py +++ b/awx_collection/test/awx/test_project.py @@ -3,20 +3,23 @@ __metaclass__ = type import pytest +from unittest import mock + from awx.main.models import Project @pytest.mark.django_db def test_create_project(run_module, admin_user, organization): - result = run_module('tower_project', dict( - name='foo', - organization=organization.name, - scm_type='git', - scm_url='https://foo.invalid', - wait=False, - scm_update_cache_timeout=5 - ), admin_user) - warning = ['scm_update_cache_timeout will be ignored since scm_update_on_launch was not set to true'] + with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as mock_warn: + result = run_module('tower_project', dict( + name='foo', + organization=organization.name, + scm_type='git', + scm_url='https://foo.invalid', + wait=False, + scm_update_cache_timeout=5 + ), admin_user) + mock_warn.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 proj = Project.objects.get(name='foo') @@ -25,9 +28,6 @@ def test_create_project(run_module, admin_user, organization): result.pop('invocation') assert result == { - 'credential_type': 'Nexus', - 'state': 'present', 'name': 'foo', - 'id': proj.id, - 'warnings': warning + 'id': proj.id } diff --git a/awx_collection/test/awx/test_send_receive.py b/awx_collection/test/awx/test_send_receive.py index f0244f61cc..01cad4ae38 100644 --- a/awx_collection/test/awx/test_send_receive.py +++ b/awx_collection/test/awx/test_send_receive.py @@ -2,6 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import pytest +from unittest import mock import json from awx.main.models import ( @@ -65,7 +66,9 @@ def test_receive_send_jt(run_module, admin_user, mocker): # recreate everything with mocker.patch('sys.stdin.isatty', return_value=True): with mocker.patch('tower_cli.models.base.MonitorableResource.wait'): - result = run_module('tower_send', dict(assets=json.dumps(assets)), admin_user) + # warns based on password_management param, but not security issue + with mock.patch('ansible.module_utils.basic.AnsibleModule.warn'): + result = run_module('tower_send', dict(assets=json.dumps(assets)), admin_user) assert not result.get('failed'), result diff --git a/awx_collection/test/awx/test_team.py b/awx_collection/test/awx/test_team.py index b4eef38185..ccc164dcdf 100644 --- a/awx_collection/test/awx/test_team.py +++ b/awx_collection/test/awx/test_team.py @@ -23,8 +23,6 @@ def test_create_team(run_module, admin_user): assert result == { "changed": True, "name": "foo_team", - "credential_type": "Nexus", - "state": "present", "id": team.id if team else None, } team = Team.objects.get(name='foo_team') @@ -50,10 +48,7 @@ def test_modify_team(run_module, admin_user): team.refresh_from_db() result.pop('invocation') assert result == { - "state": "present", "changed": True, - "name": "foo_team", - "credential_type": "Nexus", "id": team.id, } assert team.description == 'fooin around' @@ -66,9 +61,6 @@ def test_modify_team(run_module, admin_user): }, admin_user) result.pop('invocation') assert result == { - "credential_type": "Nexus", - "name": "foo_team", "id": team.id, - "state": "present", "changed": False }