Merge pull request #8109 from AlanCoding/hack_null_org

Hack to delete orphaned organizations, consolidate get_one methods

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-09-10 18:15:45 +00:00
committed by GitHub
16 changed files with 166 additions and 120 deletions

View File

@@ -4,11 +4,9 @@ __metaclass__ = type
from . tower_module import TowerModule from . tower_module import TowerModule
from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError
from ansible.module_utils.six import PY2 from ansible.module_utils.six import PY2
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.six.moves.urllib.error import HTTPError 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.http_cookiejar import CookieJar
import time import time
import re
from json import loads, dumps from json import loads, dumps
@@ -117,22 +115,23 @@ class TowerAPIModule(TowerModule):
response['json']['next'] = next_page response['json']['next'] = next_page
return response return response
def get_one(self, endpoint, name_or_id=None, *args, **kwargs): def get_one(self, endpoint, name_or_id=None, allow_none=True, **kwargs):
new_kwargs = kwargs.copy()
if name_or_id: if name_or_id:
name_field = self.get_name_field_from_endpoint(endpoint) name_field = self.get_name_field_from_endpoint(endpoint)
new_args = kwargs.get('data', {}).copy() new_data = kwargs.get('data', {}).copy()
if name_field in new_args: if name_field in new_data:
self.fail_json(msg="You can't specify the field {0} in your search data if using the name_or_id field".format(name_field)) self.fail_json(msg="You can't specify the field {0} in your search data if using the name_or_id field".format(name_field))
new_args['or__{0}'.format(name_field)] = name_or_id
try: try:
new_args['or__id'] = int(name_or_id) new_data['or__id'] = int(name_or_id)
new_data['or__{0}'.format(name_field)] = name_or_id
except ValueError: except ValueError:
# If we get a value error, then we didn't have an integer so we can just pass and fall down to the fail # If we get a value error, then we didn't have an integer so we can just pass and fall down to the fail
pass new_data[name_field] = name_or_id
kwargs['data'] = new_args new_kwargs['data'] = new_data
response = self.get_endpoint(endpoint, *args, **kwargs) response = self.get_endpoint(endpoint, **new_kwargs)
if response['status_code'] != 200: if response['status_code'] != 200:
fail_msg = "Got a {0} response when trying to get one from {1}".format(response['status_code'], endpoint) fail_msg = "Got a {0} response when trying to get one from {1}".format(response['status_code'], endpoint)
if 'detail' in response.get('json', {}): if 'detail' in response.get('json', {}):
@@ -143,63 +142,52 @@ class TowerAPIModule(TowerModule):
self.fail_json(msg="The endpoint did not provide count and results") self.fail_json(msg="The endpoint did not provide count and results")
if response['json']['count'] == 0: if response['json']['count'] == 0:
return None if allow_none:
return None
else:
self.fail_wanted_one(response, endpoint, new_kwargs.get('data'))
elif response['json']['count'] > 1: elif response['json']['count'] > 1:
if name_or_id: if name_or_id:
# Since we did a name or ID search and got > 1 return something if the id matches # Since we did a name or ID search and got > 1 return something if the id matches
for asset in response['json']['results']: for asset in response['json']['results']:
if asset['id'] == name_or_id: if str(asset['id']) == name_or_id:
return asset return asset
# We got > 1 and either didn't find something by ID (which means multiple names) # We got > 1 and either didn't find something by ID (which means multiple names)
# Or we weren't running with a or search and just got back too many to begin with. # Or we weren't running with a or search and just got back too many to begin with.
self.fail_json(msg="An unexpected number of items was returned from the API ({0})".format(response['json']['count'])) self.fail_wanted_one(response, endpoint, new_kwargs.get('data'))
return response['json']['results'][0] return response['json']['results'][0]
def get_one_by_name_or_id(self, endpoint, name_or_id): def fail_wanted_one(self, response, endpoint, query_params):
name_field = self.get_name_field_from_endpoint(endpoint) sample = response.copy()
if len(sample['json']['results']) > 1:
sample['json']['results'] = sample['json']['results'][:2] + ['...more results snipped...']
url = self.build_url(endpoint, query_params)
display_endpoint = url.geturl()[len(self.host):] # truncate to not include the base URL
self.fail_json(
msg="Request to {0} returned {1} items, expected 1".format(
display_endpoint, response['json']['count']
),
query=query_params,
response=sample,
total_results=response['json']['count']
)
query_params = {'or__{0}'.format(name_field): name_or_id} def get_exactly_one(self, endpoint, name_or_id=None, **kwargs):
try: return self.get_one(endpoint, name_or_id=name_or_id, allow_none=False, **kwargs)
query_params['or__id'] = int(name_or_id)
except ValueError:
# If we get a value error, then we didn't have an integer so we can just pass and fall down to the fail
pass
response = self.get_endpoint(endpoint, **{'data': query_params})
if response['status_code'] != 200:
self.fail_json(
msg="Failed to query endpoint {0} for {1} {2} ({3}), see results".format(endpoint, name_field, name_or_id, response['status_code']),
resuls=response
)
if response['json']['count'] == 1:
return response['json']['results'][0]
elif response['json']['count'] > 1:
for tower_object in response['json']['results']:
# ID takes priority, so we match on that first
if str(tower_object['id']) == name_or_id:
return tower_object
# We didn't match on an ID but we found more than 1 object, therefore the results are ambiguous
self.fail_json(msg="The requested name or id was ambiguous and resulted in too many items")
elif response['json']['count'] == 0:
self.fail_json(msg="The {0} {1} was not found on the Tower server".format(endpoint, name_or_id))
def resolve_name_to_id(self, endpoint, name_or_id): def resolve_name_to_id(self, endpoint, name_or_id):
return self.get_one_by_name_or_id(endpoint, name_or_id)['id'] return self.get_exactly_one(endpoint, name_or_id)['id']
def make_request(self, method, endpoint, *args, **kwargs): def make_request(self, method, endpoint, *args, **kwargs):
# In case someone is calling us directly; make sure we were given a method, let's not just assume a GET # In case someone is calling us directly; make sure we were given a method, let's not just assume a GET
if not method: if not method:
raise Exception("The HTTP method must be defined") raise Exception("The HTTP method must be defined")
# Make sure we start with /api/vX if method in ['POST', 'PUT', 'PATCH']:
if not endpoint.startswith("/"): url = self.build_url(endpoint)
endpoint = "/{0}".format(endpoint) else:
if not endpoint.startswith("/api/"): url = self.build_url(endpoint, query_params=kwargs.get('data'))
endpoint = "/api/v2{0}".format(endpoint)
if not endpoint.endswith('/') and '?' not in endpoint:
endpoint = "{0}/".format(endpoint)
# Extract the headers, this will be used in a couple of places # Extract the headers, this will be used in a couple of places
headers = kwargs.get('headers', {}) headers = kwargs.get('headers', {})
@@ -212,46 +200,41 @@ class TowerAPIModule(TowerModule):
# If we have a oauth token, we just use a bearer header # If we have a oauth token, we just use a bearer header
headers['Authorization'] = 'Bearer {0}'.format(self.oauth_token) headers['Authorization'] = 'Bearer {0}'.format(self.oauth_token)
# Update the URL path with the endpoint
self.url = self.url._replace(path=endpoint)
if method in ['POST', 'PUT', 'PATCH']: if method in ['POST', 'PUT', 'PATCH']:
headers.setdefault('Content-Type', 'application/json') headers.setdefault('Content-Type', 'application/json')
kwargs['headers'] = headers kwargs['headers'] = headers
elif kwargs.get('data'):
self.url = self.url._replace(query=urlencode(kwargs.get('data')))
data = None # Important, if content type is not JSON, this should not be dict type data = None # Important, if content type is not JSON, this should not be dict type
if headers.get('Content-Type', '') == 'application/json': if headers.get('Content-Type', '') == 'application/json':
data = dumps(kwargs.get('data', {})) data = dumps(kwargs.get('data', {}))
try: try:
response = self.session.open(method, self.url.geturl(), headers=headers, validate_certs=self.verify_ssl, follow_redirects=True, data=data) response = self.session.open(method, url.geturl(), headers=headers, validate_certs=self.verify_ssl, follow_redirects=True, data=data)
except(SSLValidationError) as ssl_err: except(SSLValidationError) as ssl_err:
self.fail_json(msg="Could not establish a secure connection to your host ({1}): {0}.".format(self.url.netloc, ssl_err)) self.fail_json(msg="Could not establish a secure connection to your host ({1}): {0}.".format(url.netloc, ssl_err))
except(ConnectionError) as con_err: except(ConnectionError) as con_err:
self.fail_json(msg="There was a network error of some kind trying to connect to your host ({1}): {0}.".format(self.url.netloc, con_err)) self.fail_json(msg="There was a network error of some kind trying to connect to your host ({1}): {0}.".format(url.netloc, con_err))
except(HTTPError) as he: except(HTTPError) as he:
# Sanity check: Did the server send back some kind of internal error? # Sanity check: Did the server send back some kind of internal error?
if he.code >= 500: if he.code >= 500:
self.fail_json(msg='The host sent back a server error ({1}): {0}. Please check the logs and try again later'.format(self.url.path, he)) self.fail_json(msg='The host sent back a server error ({1}): {0}. Please check the logs and try again later'.format(url.path, he))
# Sanity check: Did we fail to authenticate properly? If so, fail out now; this is always a failure. # Sanity check: Did we fail to authenticate properly? If so, fail out now; this is always a failure.
elif he.code == 401: elif he.code == 401:
self.fail_json(msg='Invalid Tower authentication credentials for {0} (HTTP 401).'.format(self.url.path)) self.fail_json(msg='Invalid Tower authentication credentials for {0} (HTTP 401).'.format(url.path))
# Sanity check: Did we get a forbidden response, which means that the user isn't allowed to do this? Report that. # Sanity check: Did we get a forbidden response, which means that the user isn't allowed to do this? Report that.
elif he.code == 403: elif he.code == 403:
self.fail_json(msg="You don't have permission to {1} to {0} (HTTP 403).".format(self.url.path, method)) self.fail_json(msg="You don't have permission to {1} to {0} (HTTP 403).".format(url.path, method))
# Sanity check: Did we get a 404 response? # Sanity check: Did we get a 404 response?
# Requests with primary keys will return a 404 if there is no response, and we want to consistently trap these. # Requests with primary keys will return a 404 if there is no response, and we want to consistently trap these.
elif he.code == 404: elif he.code == 404:
if kwargs.get('return_none_on_404', False): if kwargs.get('return_none_on_404', False):
return None return None
self.fail_json(msg='The requested object could not be found at {0}.'.format(self.url.path)) self.fail_json(msg='The requested object could not be found at {0}.'.format(url.path))
# Sanity check: Did we get a 405 response? # Sanity check: Did we get a 405 response?
# A 405 means we used a method that isn't allowed. Usually this is a bad request, but it requires special treatment because the # A 405 means we used a method that isn't allowed. Usually this is a bad request, but it requires special treatment because the
# API sends it as a logic error in a few situations (e.g. trying to cancel a job that isn't running). # API sends it as a logic error in a few situations (e.g. trying to cancel a job that isn't running).
elif he.code == 405: elif he.code == 405:
self.fail_json(msg="The Tower server says you can't make a request with the {0} method to this endpoing {1}".format(method, self.url.path)) self.fail_json(msg="The Tower server says you can't make a request with the {0} method to this endpoing {1}".format(method, url.path))
# Sanity check: Did we get some other kind of error? If so, write an appropriate error message. # Sanity check: Did we get some other kind of error? If so, write an appropriate error message.
elif he.code >= 400: elif he.code >= 400:
# We are going to return a 400 so the module can decide what to do with it # We are going to return a 400 so the module can decide what to do with it
@@ -265,11 +248,9 @@ class TowerAPIModule(TowerModule):
# A 204 is a normal response for a delete function # A 204 is a normal response for a delete function
pass pass
else: else:
self.fail_json(msg="Unexpected return code when calling {0}: {1}".format(self.url.geturl(), he)) self.fail_json(msg="Unexpected return code when calling {0}: {1}".format(url.geturl(), he))
except(Exception) as e: except(Exception) as e:
self.fail_json(msg="There was an unknown error when trying to connect to {2}: {0} {1}".format(type(e).__name__, e, self.url.geturl())) self.fail_json(msg="There was an unknown error when trying to connect to {2}: {0} {1}".format(type(e).__name__, e, url.geturl()))
finally:
self.url = self.url._replace(query=None)
if not self.version_checked: if not self.version_checked:
# In PY2 we get back an HTTPResponse object but PY2 is returning an addinfourl # In PY2 we get back an HTTPResponse object but PY2 is returning an addinfourl
@@ -627,7 +608,7 @@ class TowerAPIModule(TowerModule):
# If we are past our time out fail with a message # If we are past our time out fail with a message
if timeout and timeout < time.time() - start: if timeout and timeout < time.time() - start:
# Account for Legacy messages # Account for Legacy messages
if object_type is 'legacy_job_wait': if object_type == 'legacy_job_wait':
self.json_output['msg'] = 'Monitoring of Job - {0} aborted due to timeout'.format(object_name) self.json_output['msg'] = 'Monitoring of Job - {0} aborted due to timeout'.format(object_name)
else: else:
self.json_output['msg'] = 'Monitoring of {0} - {1} aborted due to timeout'.format(object_type, object_name) self.json_output['msg'] = 'Monitoring of {0} - {1} aborted due to timeout'.format(object_type, object_name)
@@ -643,7 +624,7 @@ class TowerAPIModule(TowerModule):
# If the job has failed, we want to raise a task failure for that so we get a non-zero response. # If the job has failed, we want to raise a task failure for that so we get a non-zero response.
if result['json']['failed']: if result['json']['failed']:
# Account for Legacy messages # Account for Legacy messages
if object_type is 'legacy_job_wait': if object_type == 'legacy_job_wait':
self.json_output['msg'] = 'Job with id {0} failed'.format(object_name) self.json_output['msg'] = 'Job with id {0} failed'.format(object_name)
else: else:
self.json_output['msg'] = 'The {0} - {1}, failed'.format(object_type, object_name) self.json_output['msg'] = 'The {0} - {1}, failed'.format(object_type, object_name)

View File

@@ -2,9 +2,9 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.six import PY2, string_types from ansible.module_utils.six import string_types
from ansible.module_utils.six.moves import StringIO from ansible.module_utils.six.moves import StringIO
from ansible.module_utils.six.moves.urllib.parse import urlparse from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError
from socket import gethostbyname from socket import gethostbyname
import re import re
@@ -112,6 +112,23 @@ class TowerModule(AnsibleModule):
except Exception as e: except Exception as e:
self.fail_json(msg="Unable to resolve tower_host ({1}): {0}".format(hostname, e)) self.fail_json(msg="Unable to resolve tower_host ({1}): {0}".format(hostname, e))
def build_url(self, endpoint, query_params=None):
# Make sure we start with /api/vX
if not endpoint.startswith("/"):
endpoint = "/{0}".format(endpoint)
if not endpoint.startswith("/api/"):
endpoint = "/api/v2{0}".format(endpoint)
if not endpoint.endswith('/') and '?' not in endpoint:
endpoint = "{0}/".format(endpoint)
# Update the URL path with the endpoint
url = self.url._replace(path=endpoint)
if query_params:
url = url._replace(query=urlencode(query_params))
return url
def load_config_files(self): def load_config_files(self):
# Load configs like TowerCLI would have from least import to most # Load configs like TowerCLI would have from least import to most
config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))] config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))]

View File

@@ -104,7 +104,6 @@ options:
description: description:
- Name of organization for project. - Name of organization for project.
type: str type: str
required: True
state: state:
description: description:
- Desired state of the resource. - Desired state of the resource.
@@ -200,7 +199,7 @@ def main():
allow_override=dict(type='bool', aliases=['scm_allow_override']), allow_override=dict(type='bool', aliases=['scm_allow_override']),
timeout=dict(type='int', default=0, aliases=['job_timeout']), timeout=dict(type='int', default=0, aliases=['job_timeout']),
custom_virtualenv=dict(), custom_virtualenv=dict(),
organization=dict(required=True), organization=dict(),
notification_templates_started=dict(type="list", elements='str'), notification_templates_started=dict(type="list", elements='str'),
notification_templates_success=dict(type="list", elements='str'), notification_templates_success=dict(type="list", elements='str'),
notification_templates_error=dict(type="list", elements='str'), notification_templates_error=dict(type="list", elements='str'),
@@ -234,21 +233,22 @@ def main():
wait = module.params.get('wait') wait = module.params.get('wait')
# Attempt to look up the related items the user specified (these will fail the module if not found) # Attempt to look up the related items the user specified (these will fail the module if not found)
org_id = module.resolve_name_to_id('organizations', organization) lookup_data = {}
if credential is not None: org_id = None
credential = module.resolve_name_to_id('credentials', credential) if organization:
org_id = module.resolve_name_to_id('organizations', organization)
lookup_data['organization'] = org_id
# Attempt to look up project based on the provided name and org ID # Attempt to look up project based on the provided name and org ID
project = module.get_one('projects', name_or_id=name, **{ project = module.get_one('projects', name_or_id=name, data=lookup_data)
'data': {
'organization': org_id
}
})
if state == 'absent': if state == 'absent':
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
module.delete_if_needed(project) module.delete_if_needed(project)
if credential is not None:
credential = module.resolve_name_to_id('credentials', credential)
# Attempt to look up associated field items the user specified. # Attempt to look up associated field items the user specified.
association_fields = {} association_fields = {}

View File

@@ -129,12 +129,7 @@ def main():
resource_name = params.get(param) resource_name = params.get(param)
if resource_name: if resource_name:
resource = module.get_one_by_name_or_id(module.param_to_endpoint(param), resource_name) resource = module.get_exactly_one(module.param_to_endpoint(param), resource_name)
if not resource:
module.fail_json(
msg='Failed to update role, {0} not found in {1}'.format(param, endpoint),
changed=False
)
resource_data[param] = resource resource_data[param] = resource
# separate actors from resources # separate actors from resources

View File

@@ -127,7 +127,9 @@ def test_missing_credential_type(run_module, admin_user, organization):
state='present' state='present'
), admin_user) ), admin_user)
assert result.get('failed', False), result assert result.get('failed', False), result
assert 'foobar was not found on the Tower server' in result['msg'] assert 'credential_type' in result['msg']
assert 'foobar' in result['msg']
assert 'returned 0 items, expected 1' in result['msg']
@pytest.mark.django_db @pytest.mark.django_db

View File

@@ -4,7 +4,7 @@ __metaclass__ = type
import json import json
import sys import sys
from awx.main.models import Organization, Team from awx.main.models import Organization, Team, Project, Inventory
from requests.models import Response from requests.models import Response
from unittest import mock from unittest import mock
@@ -125,3 +125,19 @@ def test_conflicting_name_and_id(run_module, admin_user):
'Lookup by id should be preferenced over name in cases of conflict.' 'Lookup by id should be preferenced over name in cases of conflict.'
) )
assert team.organization.name == 'foo' assert team.organization.name == 'foo'
def test_multiple_lookup(run_module, admin_user):
org1 = Organization.objects.create(name='foo')
org2 = Organization.objects.create(name='bar')
inv = Inventory.objects.create(name='Foo Inv')
proj1 = Project.objects.create(name='foo', organization=org1, scm_type='git', scm_url="https://github.com/ansible/ansible-tower-samples",)
proj2 = Project.objects.create(name='foo', organization=org2, scm_type='git', scm_url="https://github.com/ansible/ansible-tower-samples",)
result = run_module('tower_job_template', {
'name': 'Demo Job Template', 'project': proj1.name, 'inventory': inv.id, 'playbook': 'hello_world.yml'
}, admin_user)
assert result.get('failed', False)
assert 'projects' in result['msg']
assert 'foo' in result['msg']
assert 'returned 2 items, expected 1' in result['msg']
assert 'query' in result

View File

@@ -3,6 +3,18 @@
tower_organization: tower_organization:
name: Default name: Default
- name: HACK - delete orphaned projects from preload data where organization deletd
tower_project:
name: "{{ item['id'] }}"
scm_type: git
state: absent
loop: >
{{ query('awx.awx.tower_api', 'projects',
query_params={'organization__isnull': true, 'name': 'Demo Project'})
}}
loop_control:
label: "Deleting Demo Project with null organization id={{ item['id'] }}"
- name: Assure that demo project exists - name: Assure that demo project exists
tower_project: tower_project:
name: "Demo Project" name: "Demo Project"

View File

@@ -288,7 +288,7 @@
- name: Create an invalid SSH credential (Organization not found) - name: Create an invalid SSH credential (Organization not found)
tower_credential: tower_credential:
name: SSH Credential name: SSH Credential
organization: Missing Organization organization: Missing_Organization
state: present state: present
kind: ssh kind: ssh
username: joe username: joe
@@ -298,7 +298,9 @@
- assert: - assert:
that: that:
- "result is failed" - "result is failed"
- "'The organizations Missing Organization was not found on the Tower server' in result.msg" - "result is not changed"
- "'Missing_Organization' in result.msg"
- "result.total_results == 0"
- name: Delete an SSH credential - name: Delete an SSH credential
tower_credential: tower_credential:
@@ -750,5 +752,7 @@
- assert: - assert:
that: that:
- result is failed - "result is failed"
- "result.msg =='The organizations test-non-existing-org was not found on the Tower server'" - "result is not changed"
- "'test-non-existing-org' in result.msg"
- "result.total_results == 0"

View File

@@ -51,8 +51,10 @@
- assert: - assert:
that: that:
- "result.msg =='Failed to update the group, inventory not found: The requested object could not be found.' or - "result is failed"
result.msg =='The inventories test-non-existing-inventory was not found on the Tower server'" - "result is not changed"
- "'test-non-existing-inventory' in result.msg"
- "result.total_results == 0"
- name: add hosts - name: add hosts
tower_host: tower_host:

View File

@@ -46,5 +46,6 @@
- assert: - assert:
that: that:
- "result.msg =='The inventories test-non-existing-inventory was not found on the Tower server' or - "result is failed"
result.msg =='Failed to update host, inventory not found: The requested object could not be found.'" - "'test-non-existing-inventory' in result.msg"
- "result.total_results == 0"

View File

@@ -119,9 +119,11 @@
- assert: - assert:
that: that:
- "result is failed"
- "result is not changed" - "result is not changed"
- "result.msg =='Failed to update inventory, organization not found: The requested object could not be found.' - "'test-non-existing-org' in result.msg"
or result.msg =='The organizations test-non-existing-org was not found on the Tower server'" - "result.total_results == 0"
always: always:
- name: Delete Inventories - name: Delete Inventories
tower_inventory: tower_inventory:

View File

@@ -29,16 +29,16 @@
- name: Check module fails with correct msg - name: Check module fails with correct msg
tower_job_launch: tower_job_launch:
job_template: "Non Existing Job Template" job_template: "Non_Existing_Job_Template"
inventory: "Test Inventory" inventory: "Demo Inventory"
credential: "Test Credential"
register: result register: result
ignore_errors: true ignore_errors: true
- assert: - assert:
that: that:
- "result.msg =='Unable to launch job, job_template/Non Existing Job Template was not found: The requested object could not be found.' - "result is failed"
or result.msg == 'The inventories Test Inventory was not found on the Tower server'" - "result is not changed"
- "'Non_Existing_Job_Template' in result.msg"
- name: Create a Job Template for testing prompt on launch - name: Create a Job Template for testing prompt on launch
tower_job_template: tower_job_template:

View File

@@ -12,13 +12,16 @@
- name: Check module fails with correct msg - name: Check module fails with correct msg
tower_label: tower_label:
name: "Test Label" name: "Test Label"
organization: "Non existing org" organization: "Non_existing_org"
state: present state: present
register: result register: result
ignore_errors: true ignore_errors: true
- assert: - assert:
that: that:
- "'Non existing org was not found on the Tower server' in result.msg" - "result is failed"
- "result is not changed"
- "'Non_existing_org' in result.msg"
- "result.total_results == 0"
# TODO: Deleting labels doesn't seem to work currently # TODO: Deleting labels doesn't seem to work currently

View File

@@ -93,7 +93,7 @@
- name: Check module fails with correct msg when given non-existing org as param - name: Check module fails with correct msg when given non-existing org as param
tower_project: tower_project:
name: "{{ project_name2 }}" name: "{{ project_name2 }}"
organization: Non Existing Org organization: Non_Existing_Org
scm_type: git scm_type: git
scm_url: https://github.com/ansible/test-playbooks scm_url: https://github.com/ansible/test-playbooks
scm_credential: "{{ cred_name }}" scm_credential: "{{ cred_name }}"
@@ -102,8 +102,10 @@
- assert: - assert:
that: that:
- "result.msg == 'The organizations Non Existing Org was not found on the Tower server' or - "result is failed"
result.msg == 'Failed to update project, organization not found: Non Existing Org'" - "result is not changed"
- "'Non_Existing_Org' in result.msg"
- "result.total_results == 0"
- name: Check module fails with correct msg when given non-existing credential as param - name: Check module fails with correct msg when given non-existing credential as param
tower_project: tower_project:
@@ -111,14 +113,16 @@
organization: "{{ org_name }}" organization: "{{ org_name }}"
scm_type: git scm_type: git
scm_url: https://github.com/ansible/test-playbooks scm_url: https://github.com/ansible/test-playbooks
scm_credential: Non Existing Credential scm_credential: Non_Existing_Credential
register: result register: result
ignore_errors: true ignore_errors: true
- assert: - assert:
that: that:
- "result.msg =='The credentials Non Existing Credential was not found on the Tower server' or - "result is failed"
result.msg =='Failed to update project, credential not found: Non Existing Credential'" - "result is not changed"
- "'Non_Existing_Credential' in result.msg"
- "result.total_results == 0"
- name: Create a git project without credentials without waiting - name: Create a git project without credentials without waiting
tower_project: tower_project:

View File

@@ -6,7 +6,7 @@
- name: Attempt to add a Tower team to a non-existant Organization - name: Attempt to add a Tower team to a non-existant Organization
tower_team: tower_team:
name: Test Team name: Test Team
organization: Missing Organization organization: Missing_Organization
state: present state: present
register: result register: result
ignore_errors: true ignore_errors: true
@@ -14,9 +14,10 @@
- name: Assert a meaningful error was provided for the failed Tower team creation - name: Assert a meaningful error was provided for the failed Tower team creation
assert: assert:
that: that:
- result is failed - "result is failed"
- "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or - "result is not changed"
result.msg =='The organizations Missing Organization was not found on the Tower server'" - "'Missing_Organization' in result.msg"
- "result.total_results == 0"
- name: Create a Tower team - name: Create a Tower team
tower_team: tower_team:
@@ -42,12 +43,15 @@
- name: Check module fails with correct msg - name: Check module fails with correct msg
tower_team: tower_team:
name: "{{ team_name }}" name: "{{ team_name }}"
organization: Non Existing Org organization: Non_Existing_Org
state: present state: present
register: result register: result
ignore_errors: true ignore_errors: true
- assert: - name: Lookup of the related organization should cause a failure
assert:
that: that:
- "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or - "result is failed"
result.msg =='The organizations Non Existing Org was not found on the Tower server'" - "result is not changed"
- "'Non_Existing_Org' in result.msg"
- "result.total_results == 0"

View File

@@ -207,13 +207,16 @@
- name: Check module fails with correct msg - name: Check module fails with correct msg
tower_workflow_job_template: tower_workflow_job_template:
name: "{{ wfjt_name }}" name: "{{ wfjt_name }}"
organization: Non Existing Organization organization: Non_Existing_Organization
register: result register: result
ignore_errors: true ignore_errors: true
- assert: - assert:
that: that:
- "'The organizations Non Existing Organization was not found' in result.msg" - "result is failed"
- "result is not changed"
- "'Non_Existing_Organization' in result.msg"
- "result.total_results == 0"
- name: Delete the Job Template - name: Delete the Job Template
tower_job_template: tower_job_template: