diff options
author | Yunfan Zhang <39713606+YunfanZhang42@users.noreply.github.com> | 2018-08-21 20:41:26 +0200 |
---|---|---|
committer | AlanCoding <arominge@redhat.com> | 2019-09-27 20:28:52 +0200 |
commit | 7580f9c2b9f4e69c49baeb9f91b18c970e29d726 (patch) | |
tree | 1b1c39c9e94daab21f202622cfb47ac258c62fb6 | |
parent | To improve readability, we added a line feed. (#43801) (diff) | |
download | awx-7580f9c2b9f4e69c49baeb9f91b18c970e29d726.tar.xz awx-7580f9c2b9f4e69c49baeb9f91b18c970e29d726.zip |
Added Ansible Tower inventory plugin. (#41816)
Signed-off-by: Yunfan Zhang <yz322@duke.edu>
-rw-r--r-- | lib/ansible/plugins/inventory/tower.py | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/lib/ansible/plugins/inventory/tower.py b/lib/ansible/plugins/inventory/tower.py new file mode 100644 index 0000000000..dd48514634 --- /dev/null +++ b/lib/ansible/plugins/inventory/tower.py @@ -0,0 +1,187 @@ +# Copyright (c) 2018 Ansible Project +# 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 + +DOCUMENTATION = ''' + name: tower + plugin_type: inventory + authors: + - Matthew Jones (@matburt) + - Yunfan Zhang (@YunfanZhang42) + short_description: Ansible dynamic inventory plugin for Ansible Tower. + version_added: "2.7" + description: + - Reads inventories from Ansible Tower. + - Supports reading configuration from both YAML config file and environment variables. + - If reading from the YAML file, the file name must end with tower_inventory.(yml|yaml), + the path in the command would be /path/to/tower_inventory.(yml|yaml). If some arguments in the config file + are missing, this plugin will try to fill in missing arguments by reading from environment variables. + - If reading configurations from environment variables, the path in the command must be @tower_inventory. + options: + plugin: + description: the name of this plugin, it should always be set to 'tower' + for this plugin to recognize it as it's own. + env: + - name: ANSIBLE_INVENTORY_ENABLED + required: True + choices: ['tower'] + host: + description: The network address of your Ansible Tower host. + type: string + env: + - name: TOWER_HOST + required: True + username: + description: The user that you plan to use to access inventories on Ansible Tower. + type: string + env: + - name: TOWER_USERNAME + required: True + password: + description: The password for your Ansible Tower user. + type: string + env: + - name: TOWER_PASSWORD + required: True + inventory_id: + description: The ID of the Ansible Tower inventory that you wish to import. + type: string + env: + - name: TOWER_INVENTORY + required: True + verify_ssl: + description: Specify whether Ansible should verify the SSL certificate of Ansible Tower host. + type: bool + default: True + env: + - name: TOWER_VERIFY_SSL + required: False +''' + +EXAMPLES = ''' +# Before you execute the following commands, you should make sure this file is in your plugin path, +# and you enabled this plugin. + +# Example for using tower_inventory.yml file + +plugin: tower +host: your_ansible_tower_server_network_address +username: your_ansible_tower_username +password: your_ansible_tower_password +inventory_id: the_ID_of_targeted_ansible_tower_inventory +# Then you can run the following command. +# If some of the arguments are missing, Ansible will attempt to read them from environment variables. +# ansible-inventory -i /path/to/tower_inventory.yml --list + +# Example for reading from environment variables: + +# Set environment variables: +# export TOWER_HOST=YOUR_TOWER_HOST_ADDRESS +# export TOWER_USERNAME=YOUR_TOWER_USERNAME +# export TOWER_PASSWORD=YOUR_TOWER_PASSWORD +# export TOWER_INVENTORY=THE_ID_OF_TARGETED_INVENTORY +# Read the inventory specified in TOWER_INVENTORY from Ansible Tower, and list them. +# The inventory path must always be @tower_inventory if you are reading all settings from environment variables. +# ansible-inventory -i @tower_inventory --list +''' + +import re +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.plugins.inventory import BaseInventoryPlugin + +# Python 2/3 Compatibility +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin + + +class InventoryModule(BaseInventoryPlugin): + NAME = 'tower' + # Stays backward compatible with tower inventory script. + # If the user supplies '@tower_inventory' as path, the plugin will read from environment variables. + no_config_file_supplied = False + + def read_tower_inventory(self, tower_host, tower_user, tower_pass, inventory, verify_ssl=True): + if not re.match('(?:http|https)://', tower_host): + tower_host = 'https://{tower_host}'.format(tower_host=tower_host) + inventory_id = inventory.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) + + request_handler = Request(url_username=tower_user, + url_password=tower_pass, + force_basic_auth=True, + validate_certs=verify_ssl) + + try: + response = request_handler.get(inventory_url) + except (ConnectionError, urllib_error.URLError, socket.error, httplib.HTTPException) as e: + error_msg = 'Connection to remote host failed: {err}'.format(err=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)) + + # 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))) + + def verify_file(self, path): + if path.endswith('@tower_inventory'): + self.no_config_file_supplied = True + return True + elif super(InventoryModule, self).verify_file(path): + return path.endswith('tower_inventory.yml') or path.endswith('tower_inventory.yaml') + else: + return False + + def parse(self, inventory, loader, path, cache=True): + super(InventoryModule, self).parse(inventory, loader, path) + if not self.no_config_file_supplied and os.path.isfile(path): + self._read_config_data(path) + # Read inventory from tower server. + # Note the environment variables will be handled automatically by InventoryManager. + inventory = self.read_tower_inventory(self.get_option('host'), + self.get_option('username'), + self.get_option('password'), + self.get_option('inventory_id'), + verify_ssl=self.get_option('verify_ssl')) + # To start with, create all the groups. + for group_name in inventory: + if group_name != '_meta': + self.inventory.add_group(group_name) + + # Then, create all hosts and add the host vars. + all_hosts = inventory['_meta']['hostvars'] + for host_name, host_vars in six.iteritems(all_hosts): + self.inventory.add_host(host_name) + for var_name, var_value in six.iteritems(host_vars): + self.inventory.set_variable(host_name, var_name, var_value) + + # Lastly, create to group-host and group-group relationships, and set group vars. + for group_name, group_content in six.iteritems(inventory): + if group_name != 'all' and group_name != '_meta': + # First add hosts to groups + for host_name in group_content.get('hosts', []): + self.inventory.add_host(host_name, group_name) + # Then add the parent-children group relationships. + for child_group_name in group_content.get('children', []): + self.inventory.add_child(group_name, child_group_name) + # Set the group vars. Note we should set group var for 'all', but not '_meta'. + if group_name != '_meta': + for var_name, var_value in six.iteritems(group_content.get('vars', {})): + self.inventory.set_variable(group_name, var_name, var_value) + # Clean up the inventory. + self.inventory.reconcile_inventory() |