diff --git a/changelogs/fragments/61338-tower-inventory-integer-inventory_id.yaml b/changelogs/fragments/61338-tower-inventory-integer-inventory_id.yaml new file mode 100644 index 00000000000..023cf779c97 --- /dev/null +++ b/changelogs/fragments/61338-tower-inventory-integer-inventory_id.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: +- tower inventory plugin - fix TypeError when giving inventory_id as integer (https://github.com/ansible/ansible/issues/61333) diff --git a/lib/ansible/plugins/inventory/tower.py b/lib/ansible/plugins/inventory/tower.py index 76eb7eb67b7..f4f20fe5091 100644 --- a/lib/ansible/plugins/inventory/tower.py +++ b/lib/ansible/plugins/inventory/tower.py @@ -47,8 +47,12 @@ DOCUMENTATION = ''' - name: TOWER_PASSWORD required: True inventory_id: - description: The ID of the Ansible Tower inventory that you wish to import. - type: string + description: + - The ID of the Ansible Tower inventory that you wish to import. + - This is allowed to be either the inventory primary key or its named URL slug. + - Primary key values will be accepted as strings or integers, and URL slugs must be strings. + - Named URL slugs follow the syntax of "inventory_name++organization_name". + type: raw env: - name: TOWER_INVENTORY required: True @@ -99,9 +103,10 @@ import os import json from ansible.module_utils import six from ansible.module_utils.urls import Request, urllib_error, ConnectionError, socket, httplib -from ansible.module_utils._text import to_native -from ansible.errors import AnsibleParserError +from ansible.module_utils._text import to_text, to_native +from ansible.errors import AnsibleParserError, AnsibleOptionsError from ansible.plugins.inventory import BaseInventoryPlugin +from ansible.config.manager import ensure_type # Python 2/3 Compatibility try: @@ -122,18 +127,18 @@ class InventoryModule(BaseInventoryPlugin): try: response = request_handler.get(tower_url) except (ConnectionError, urllib_error.URLError, socket.error, httplib.HTTPException) as e: - error_msg = 'Connection to remote host failed: {err}'.format(err=e) + n_error_msg = 'Connection to remote host failed: {err}'.format(err=to_native(e)) # If Tower gives a readable error message, display that message to the user. if callable(getattr(e, 'read', None)): - error_msg += ' with message: {err_msg}'.format(err_msg=e.read()) - raise AnsibleParserError(to_native(error_msg)) + n_error_msg += ' with message: {err_msg}'.format(err_msg=to_native(e.read())) + raise AnsibleParserError(n_error_msg) # Attempt to parse JSON. try: return json.loads(response.read()) except (ValueError, TypeError) as e: # If the JSON parse fails, print the ValueError - raise AnsibleParserError(to_native('Failed to parse json from host: {err}'.format(err=e))) + raise AnsibleParserError('Failed to parse json from host: {err}'.format(err=to_native(e))) def verify_file(self, path): if path.endswith('@tower_inventory'): @@ -159,7 +164,19 @@ class InventoryModule(BaseInventoryPlugin): force_basic_auth=True, validate_certs=self.get_option('validate_certs')) - inventory_id = self.get_option('inventory_id').replace('/', '') + # validate type of inventory_id because we allow two types as special case + inventory_id = self.get_option('inventory_id') + if isinstance(inventory_id, int): + inventory_id = to_text(inventory_id, nonstring='simplerepr') + else: + try: + inventory_id = ensure_type(inventory_id, 'str') + except ValueError as e: + raise AnsibleOptionsError( + 'Invalid type for configuration option inventory_id, ' + 'not integer, and cannot convert to string: {err}'.format(err=to_native(e)) + ) + inventory_id = inventory_id.replace('/', '') inventory_url = '/api/v2/inventories/{inv_id}/script/?hostvars=1&towervars=1&all=1'.format(inv_id=inventory_id) inventory_url = urljoin(tower_host, inventory_url)