diff --git a/Makefile b/Makefile index 7ce3323339..98caaba7df 100644 --- a/Makefile +++ b/Makefile @@ -404,7 +404,7 @@ test_collection_sanity: mkdir -p sanity/ansible_collections/awx cp -Ra awx_collection sanity/ansible_collections/awx/awx # symlinks do not work cd sanity/ansible_collections/awx/awx && git init && git add . # requires both this file structure and a git repo, so there you go - cd sanity/ansible_collections/awx/awx && ansible-test sanity --test validate-modules + cd sanity/ansible_collections/awx/awx && ansible-test sanity build_collection: ansible-playbook -i localhost, awx_collection/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION) diff --git a/awx_collection/plugins/doc_fragments/auth.py b/awx_collection/plugins/doc_fragments/auth.py index 5583710201..4187a17b2a 100644 --- a/awx_collection/plugins/doc_fragments/auth.py +++ b/awx_collection/plugins/doc_fragments/auth.py @@ -3,6 +3,9 @@ # Copyright: (c) 2017, Wayne Witzel III # 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 + class ModuleDocFragment(object): diff --git a/awx_collection/plugins/module_utils/ansible_tower.py b/awx_collection/plugins/module_utils/ansible_tower.py index c71f096455..97dff798a9 100644 --- a/awx_collection/plugins/module_utils/ansible_tower.py +++ b/awx_collection/plugins/module_utils/ansible_tower.py @@ -26,6 +26,9 @@ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import os import traceback diff --git a/awx_collection/plugins/modules/tower_credential.py b/awx_collection/plugins/modules/tower_credential.py index f43d7f5ab3..a87d76fecb 100644 --- a/awx_collection/plugins/modules/tower_credential.py +++ b/awx_collection/plugins/modules/tower_credential.py @@ -255,7 +255,7 @@ OLD_INPUT_NAMES = ( def credential_type_for_kind(params): credential_type_res = tower_cli.get_resource('credential_type') - kind = params.pop('kind') + kind = params.get('kind') arguments = {'managed_by_tower': True} if kind == 'ssh': if params.get('vault_password'): diff --git a/awx_collection/plugins/modules/tower_job_wait.py b/awx_collection/plugins/modules/tower_job_wait.py index c6866db2c8..87b1f9f27c 100644 --- a/awx_collection/plugins/modules/tower_job_wait.py +++ b/awx_collection/plugins/modules/tower_job_wait.py @@ -87,7 +87,9 @@ status: from ..module_utils.ansible_tower import TowerModule, tower_auth_config, tower_check_mode +from ansible.module_utils.six import PY2 from ansible.module_utils.six.moves import cStringIO as StringIO +from codecs import getwriter try: @@ -123,7 +125,10 @@ def main(): # tower-cli gets very noisy when monitoring. # We pass in our our outfile to suppress the out during our monitor call. - outfile = StringIO() + if PY2: + outfile = getwriter('utf-8')(StringIO()) + else: + outfile = StringIO() params['outfile'] = outfile job_id = params.get('job_id') @@ -136,6 +141,12 @@ def main(): json_output['timeout'] = True except exc.NotFound as excinfo: fail_json = dict(msg='Unable to wait, no job_id {0} found: {1}'.format(job_id, excinfo), changed=False) + except exc.JobFailure as excinfo: + fail_json = dict(msg='Job with id={0} failed, error: {1}'.format(job_id, excinfo)) + fail_json['success'] = False + result = job.get(job_id) + for k in ('id', 'status', 'elapsed', 'started', 'finished'): + fail_json[k] = result.get(k) except (exc.ConnectionError, exc.BadRequest, exc.AuthError) as excinfo: fail_json = dict(msg='Unable to wait for job: {0}'.format(excinfo), changed=False) diff --git a/awx_collection/plugins/modules/tower_project.py b/awx_collection/plugins/modules/tower_project.py index ce18b3ae71..5c5f90faaa 100644 --- a/awx_collection/plugins/modules/tower_project.py +++ b/awx_collection/plugins/modules/tower_project.py @@ -145,7 +145,7 @@ def main(): scm_clean=dict(type='bool', default=False), scm_delete_on_update=dict(type='bool', default=False), scm_update_on_launch=dict(type='bool', default=False), - scm_update_cache_timeout=dict(type='int', default=0), + scm_update_cache_timeout=dict(type='int'), job_timeout=dict(type='int', default=0), custom_virtualenv=dict(), local_path=dict(), diff --git a/awx_collection/plugins/modules/tower_workflow_template.py b/awx_collection/plugins/modules/tower_workflow_template.py index 56f85850b6..d0325cb0e5 100644 --- a/awx_collection/plugins/modules/tower_workflow_template.py +++ b/awx_collection/plugins/modules/tower_workflow_template.py @@ -92,7 +92,7 @@ EXAMPLES = ''' organization: My optional Organization schema: "{{ lookup('file', 'my_workflow.json') }}" -- tower_worflow_template: +- tower_workflow_template: name: Workflow Template state: absent ''' diff --git a/awx_collection/test/awx/conftest.py b/awx_collection/test/awx/conftest.py index 3cf7295246..ee99f03406 100644 --- a/awx_collection/test/awx/conftest.py +++ b/awx_collection/test/awx/conftest.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import io import json import datetime @@ -60,10 +63,10 @@ def run_module(): # Note that a proper Ansiballz explosion of the modules will have an import path like: # ansible_collections.awx.awx.plugins.modules.{} # We should consider supporting that in the future - resource_module = importlib.import_module('plugins.modules.{}'.format(module_name)) + resource_module = importlib.import_module('plugins.modules.{0}'.format(module_name)) if not isinstance(module_params, dict): - raise RuntimeError('Module params must be dict, got {}'.format(type(module_params))) + raise RuntimeError('Module params must be dict, got {0}'.format(type(module_params))) # Ansible params can be passed as an invocation argument or over stdin # this short circuits within the AnsibleModule interface diff --git a/awx_collection/test/awx/test_credential.py b/awx_collection/test/awx/test_credential.py index 12b4935ff7..9d246a1db8 100644 --- a/awx_collection/test/awx/test_credential.py +++ b/awx_collection/test/awx/test_credential.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import pytest from awx.main.models import Credential, CredentialType, Organization @@ -7,20 +10,52 @@ from awx.main.models import Credential, CredentialType, Organization def test_create_machine_credential(run_module, admin_user): Organization.objects.create(name='test-org') # create the ssh credential type - CredentialType.defaults['ssh']().save() + ct = CredentialType.defaults['ssh']() + ct.save() # Example from docs result = run_module('tower_credential', dict( - name='Team Name', - description='Team Description', + name='Test Machine Credential', organization='test-org', kind='ssh', state='present' ), admin_user) + assert result.get('changed'), result - cred = Credential.objects.get(name='Team Name') + cred = Credential.objects.get(name='Test Machine Credential') + assert cred.credential_type == ct result.pop('invocation') assert result == { - "credential": "Team Name", + "credential": "Test Machine Credential", + "state": "present", + "id": cred.pk, + "changed": True + } + + +@pytest.mark.django_db +def test_create_vault_credential(run_module, admin_user): + # https://github.com/ansible/ansible/issues/61324 + Organization.objects.create(name='test-org') + ct = CredentialType.defaults['vault']() + ct.save() + + result = run_module('tower_credential', dict( + name='Test Vault Credential', + organization='test-org', + kind='vault', + vault_id='bar', + vault_password='foobar', + state='present' + ), admin_user) + assert result.get('changed'), result + + cred = Credential.objects.get(name='Test Vault Credential') + assert cred.credential_type == ct + assert 'vault_id' in cred.inputs + assert 'vault_password' in cred.inputs + result.pop('invocation') + assert result == { + "credential": "Test Vault Credential", "state": "present", "id": cred.pk, "changed": True @@ -39,6 +74,7 @@ def test_create_custom_credential_type(run_module, admin_user): state='present', validate_certs='false' ), admin_user) + assert result.get('changed'), result ct = CredentialType.objects.get(name='Nexus') result.pop('invocation') diff --git a/awx_collection/test/awx/test_job.py b/awx_collection/test/awx/test_job.py new file mode 100644 index 0000000000..2fb616c2ac --- /dev/null +++ b/awx_collection/test/awx/test_job.py @@ -0,0 +1,56 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +from django.utils.timezone import now + +from awx.main.models import Job + + +@pytest.mark.django_db +def test_job_wait_successful(run_module, admin_user): + job = Job.objects.create(status='successful', started=now(), finished=now()) + result = run_module('tower_job_wait', dict( + job_id=job.id + ), admin_user) + result.pop('invocation', None) + assert result.pop('finished', '')[:10] == str(job.finished)[:10] + assert result.pop('started', '')[:10] == str(job.started)[:10] + assert result == { + "status": "successful", + "success": True, + "elapsed": str(job.elapsed), + "id": job.id + } + + +@pytest.mark.django_db +def test_job_wait_failed(run_module, admin_user): + job = Job.objects.create(status='failed', started=now(), finished=now()) + result = run_module('tower_job_wait', dict( + job_id=job.id + ), admin_user) + result.pop('invocation', None) + assert result.pop('finished', '')[:10] == str(job.finished)[:10] + assert result.pop('started', '')[:10] == str(job.started)[:10] + assert result == { + "status": "failed", + "failed": True, + "success": False, + "elapsed": str(job.elapsed), + "id": job.id, + "msg": "Job with id=1 failed, error: Job failed." + } + + +@pytest.mark.django_db +def test_job_wait_not_found(run_module, admin_user): + result = run_module('tower_job_wait', dict( + job_id=42 + ), admin_user) + result.pop('invocation', None) + assert result == { + "changed": False, + "failed": True, + "msg": "Unable to wait, no job_id 42 found: The requested object could not be found." + } diff --git a/awx_collection/test/awx/test_job_template.py b/awx_collection/test/awx/test_job_template.py index 9c5764b61c..daa9eacac5 100644 --- a/awx_collection/test/awx/test_job_template.py +++ b/awx_collection/test/awx/test_job_template.py @@ -1,6 +1,9 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import pytest -from awx.main.models import JobTemplate +from awx.main.models import JobTemplate, Job @pytest.mark.django_db @@ -31,6 +34,27 @@ def test_create_job_template(run_module, admin_user, project, inventory): assert jt.inventory_id == inventory.id +@pytest.mark.django_db +def test_job_launch_with_prompting(run_module, admin_user, project, inventory, machine_credential): + JobTemplate.objects.create( + name='foo', + project=project, + playbook='helloworld.yml', + ask_inventory_on_launch=True, + ask_credential_on_launch=True + ) + result = run_module('tower_job_launch', dict( + job_template='foo', + inventory=inventory.name, + credential=machine_credential.name + ), admin_user) + assert result.pop('changed', None), result + + job = Job.objects.get(id=result['id']) + assert job.inventory == inventory + assert [cred.id for cred in job.credentials.all()] == [machine_credential.id] + + @pytest.mark.django_db def test_create_job_template_with_old_machine_cred(run_module, admin_user, project, inventory, machine_credential): diff --git a/awx_collection/test/awx/test_organization.py b/awx_collection/test/awx/test_organization.py index ea76940fd4..ab5f453ae8 100644 --- a/awx_collection/test/awx/test_organization.py +++ b/awx_collection/test/awx/test_organization.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import pytest from awx.main.models import Organization diff --git a/awx_collection/test/awx/test_project.py b/awx_collection/test/awx/test_project.py new file mode 100644 index 0000000000..fa749cd6e0 --- /dev/null +++ b/awx_collection/test/awx/test_project.py @@ -0,0 +1,28 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +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' + ), admin_user) + assert result.pop('changed', None), result + + proj = Project.objects.get(name='foo') + assert proj.scm_url == 'https://foo.invalid' + assert proj.organization == organization + + result.pop('invocation') + assert result == { + 'id': proj.id, + 'project': 'foo', + 'state': 'present' + } diff --git a/awx_collection/test/awx/test_send_receive.py b/awx_collection/test/awx/test_send_receive.py index 67ced22833..f0244f61cc 100644 --- a/awx_collection/test/awx/test_send_receive.py +++ b/awx_collection/test/awx/test_send_receive.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + import pytest import json diff --git a/awx_collection/tests/sanity/ignore-2.10.txt b/awx_collection/tests/sanity/ignore-2.10.txt index 4d3942ddf0..fa05535904 100644 --- a/awx_collection/tests/sanity/ignore-2.10.txt +++ b/awx_collection/tests/sanity/ignore-2.10.txt @@ -1,44 +1,2 @@ -plugins/modules/tower_credential_type.py validate-modules:missing-module-utils-import -plugins/modules/tower_group.py validate-modules:missing-module-utils-import -plugins/modules/tower_host.py validate-modules:missing-module-utils-import -plugins/modules/tower_inventory.py validate-modules:missing-module-utils-import -plugins/modules/tower_inventory_source.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_cancel.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_launch.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_list.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_template.py validate-modules:missing-module-utils-import -plugins/modules/tower_label.py validate-modules:missing-module-utils-import -plugins/modules/tower_notification.py validate-modules:missing-module-utils-import -plugins/modules/tower_organization.py validate-modules:missing-module-utils-import -plugins/modules/tower_project.py validate-modules:missing-module-utils-import -plugins/modules/tower_receive.py validate-modules:missing-module-utils-import -plugins/modules/tower_role.py validate-modules:missing-module-utils-import -plugins/modules/tower_settings.py validate-modules:missing-module-utils-import -plugins/modules/tower_team.py validate-modules:missing-module-utils-import -plugins/modules/tower_user.py validate-modules:missing-module-utils-import -plugins/modules/tower_workflow_launch.py validate-modules:missing-module-utils-import -plugins/modules/tower_workflow_template.py validate-modules:missing-module-utils-import -plugins/modules/tower_credential_type.py validate-modules:import-error -plugins/modules/tower_credential.py validate-modules:import-error -plugins/modules/tower_group.py validate-modules:import-error -plugins/modules/tower_host.py validate-modules:import-error -plugins/modules/tower_inventory.py validate-modules:import-error -plugins/modules/tower_inventory_source.py validate-modules:import-error -plugins/modules/tower_job_cancel.py validate-modules:import-error -plugins/modules/tower_job_launch.py validate-modules:import-error -plugins/modules/tower_job_list.py validate-modules:import-error -plugins/modules/tower_job_wait.py validate-modules:import-error -plugins/modules/tower_job_template.py validate-modules:import-error -plugins/modules/tower_label.py validate-modules:import-error -plugins/modules/tower_notification.py validate-modules:import-error -plugins/modules/tower_organization.py validate-modules:import-error -plugins/modules/tower_project.py validate-modules:import-error -plugins/modules/tower_receive.py validate-modules:import-error -plugins/modules/tower_role.py validate-modules:import-error -plugins/modules/tower_settings.py validate-modules:import-error -plugins/modules/tower_send.py validate-modules:import-error -plugins/modules/tower_team.py validate-modules:import-error -plugins/modules/tower_user.py validate-modules:import-error -plugins/modules/tower_workflow_launch.py validate-modules:import-error -plugins/modules/tower_workflow_template.py validate-modules:import-error -plugins/modules/tower_workflow_job_template.py validate-modules:import-error \ No newline at end of file +plugins/modules/tower_group.py use-argspec-type-path +plugins/modules/tower_host.py use-argspec-type-path \ No newline at end of file diff --git a/awx_collection/tests/sanity/ignore-2.9.txt b/awx_collection/tests/sanity/ignore-2.9.txt index 4d3942ddf0..fa05535904 100644 --- a/awx_collection/tests/sanity/ignore-2.9.txt +++ b/awx_collection/tests/sanity/ignore-2.9.txt @@ -1,44 +1,2 @@ -plugins/modules/tower_credential_type.py validate-modules:missing-module-utils-import -plugins/modules/tower_group.py validate-modules:missing-module-utils-import -plugins/modules/tower_host.py validate-modules:missing-module-utils-import -plugins/modules/tower_inventory.py validate-modules:missing-module-utils-import -plugins/modules/tower_inventory_source.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_cancel.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_launch.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_list.py validate-modules:missing-module-utils-import -plugins/modules/tower_job_template.py validate-modules:missing-module-utils-import -plugins/modules/tower_label.py validate-modules:missing-module-utils-import -plugins/modules/tower_notification.py validate-modules:missing-module-utils-import -plugins/modules/tower_organization.py validate-modules:missing-module-utils-import -plugins/modules/tower_project.py validate-modules:missing-module-utils-import -plugins/modules/tower_receive.py validate-modules:missing-module-utils-import -plugins/modules/tower_role.py validate-modules:missing-module-utils-import -plugins/modules/tower_settings.py validate-modules:missing-module-utils-import -plugins/modules/tower_team.py validate-modules:missing-module-utils-import -plugins/modules/tower_user.py validate-modules:missing-module-utils-import -plugins/modules/tower_workflow_launch.py validate-modules:missing-module-utils-import -plugins/modules/tower_workflow_template.py validate-modules:missing-module-utils-import -plugins/modules/tower_credential_type.py validate-modules:import-error -plugins/modules/tower_credential.py validate-modules:import-error -plugins/modules/tower_group.py validate-modules:import-error -plugins/modules/tower_host.py validate-modules:import-error -plugins/modules/tower_inventory.py validate-modules:import-error -plugins/modules/tower_inventory_source.py validate-modules:import-error -plugins/modules/tower_job_cancel.py validate-modules:import-error -plugins/modules/tower_job_launch.py validate-modules:import-error -plugins/modules/tower_job_list.py validate-modules:import-error -plugins/modules/tower_job_wait.py validate-modules:import-error -plugins/modules/tower_job_template.py validate-modules:import-error -plugins/modules/tower_label.py validate-modules:import-error -plugins/modules/tower_notification.py validate-modules:import-error -plugins/modules/tower_organization.py validate-modules:import-error -plugins/modules/tower_project.py validate-modules:import-error -plugins/modules/tower_receive.py validate-modules:import-error -plugins/modules/tower_role.py validate-modules:import-error -plugins/modules/tower_settings.py validate-modules:import-error -plugins/modules/tower_send.py validate-modules:import-error -plugins/modules/tower_team.py validate-modules:import-error -plugins/modules/tower_user.py validate-modules:import-error -plugins/modules/tower_workflow_launch.py validate-modules:import-error -plugins/modules/tower_workflow_template.py validate-modules:import-error -plugins/modules/tower_workflow_job_template.py validate-modules:import-error \ No newline at end of file +plugins/modules/tower_group.py use-argspec-type-path +plugins/modules/tower_host.py use-argspec-type-path \ No newline at end of file