diff --git a/lib/ansible/module_utils/vultr.py b/lib/ansible/module_utils/vultr.py index dc9b9c124a..236b9d2468 100644 --- a/lib/ansible/module_utils/vultr.py +++ b/lib/ansible/module_utils/vultr.py @@ -14,6 +14,7 @@ from ansible.module_utils.urls import fetch_url VULTR_API_ENDPOINT = "https://api.vultr.com" +VULTR_USER_AGENT = 'Ansible Vultr' def vultr_argument_spec(): @@ -49,7 +50,7 @@ class Vultr: try: config = self.read_env_variables() - config.update(self.read_ini_config()) + config.update(Vultr.read_ini_config(self.module.params.get('api_account'))) except KeyError: config = {} @@ -79,7 +80,7 @@ class Vultr: # Headers to be passed to the API self.headers = { 'API-Key': "%s" % self.api_config['api_key'], - 'User-Agent': "Ansible Vultr", + 'User-Agent': VULTR_USER_AGENT, 'Accept': 'application/json', } @@ -93,9 +94,8 @@ class Vultr: return env_conf - def read_ini_config(self): - ini_group = self.module.params.get('api_account') - + @staticmethod + def read_ini_config(ini_group): paths = ( os.path.join(os.path.expanduser('~'), '.vultr.ini'), os.path.join(os.getcwd(), 'vultr.ini'), @@ -236,12 +236,14 @@ class Vultr: self.module.fail_json(msg="Could not find %s with %s: %s" % (resource, key, value)) - def normalize_result(self, resource): - fields_to_remove = set(resource.keys()) - set(self.returns.keys()) - for field in fields_to_remove: - resource.pop(field) + @staticmethod + def normalize_result(resource, schema, remove_missing_keys=True): + if remove_missing_keys: + fields_to_remove = set(resource.keys()) - set(schema.keys()) + for field in fields_to_remove: + resource.pop(field) - for search_key, config in self.returns.items(): + for search_key, config in schema.items(): if search_key in resource: if 'convert_to' in config: if config['convert_to'] == 'int': @@ -263,9 +265,9 @@ class Vultr: def get_result(self, resource): if resource: if isinstance(resource, list): - self.result[self.namespace] = [self.normalize_result(item) for item in resource] + self.result[self.namespace] = [Vultr.normalize_result(item, self.returns) for item in resource] else: - self.result[self.namespace] = self.normalize_result(resource) + self.result[self.namespace] = Vultr.normalize_result(resource, self.returns) return self.result diff --git a/lib/ansible/plugins/inventory/vultr.py b/lib/ansible/plugins/inventory/vultr.py new file mode 100644 index 0000000000..e5339e867e --- /dev/null +++ b/lib/ansible/plugins/inventory/vultr.py @@ -0,0 +1,151 @@ +# (c) 2018, Yanis Guenane +# 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 = r''' + name: vultr + plugin_type: inventory + author: + - "Yanis Guenane (@Spredzy)" + short_description: Vultr inventory source + description: + - Get inventory hosts from Vultr public cloud. + - Uses C(api_config), C(~/.vultr.ini), C(./vultr.ini) or VULTR_API_CONFIG path to config file. + options: + plugin: + description: token that ensures this is a source file for the 'vultr' plugin. + required: True + choices: ['vultr'] + api_account: + description: Specify the account to be used. + default: default + api_config: + description: Path to the vultr configuration file. If not specified will be taken from regular Vultr configuration. + env: + - name: VULTR_API_CONFIG + api_key: + description: Vultr API key. If not specified will be taken from regular Vultr configuration. + env: + - name: VULTR_API_KEY + hostname: + description: Field to match the hostname + type: string + default: v4_main_ip + choices: + - v4_main_ip + - v6_main_ip + - name +''' + +EXAMPLES = r''' +# vultr_inventory.yml file in YAML format +# Example command line: ansible-inventory --list -i vultr_inventory.yml + +plugin: vultr +''' + +import json + +from ansible.errors import AnsibleError +from ansible.plugins.inventory import BaseInventoryPlugin +from ansible.module_utils.six.moves import configparser +from ansible.module_utils.urls import open_url +from ansible.module_utils._text import to_native +from ansible.module_utils.vultr import Vultr, VULTR_USER_AGENT + + +SCHEMA = { + 'SUBID': dict(key='id'), + 'label': dict(key='name'), + 'date_created': dict(), + 'allowed_bandwidth_gb': dict(convert_to='int'), + 'auto_backups': dict(key='auto_backup_enabled', convert_to='bool'), + 'current_bandwidth_gb': dict(), + 'kvm_url': dict(), + 'default_password': dict(), + 'internal_ip': dict(), + 'disk': dict(), + 'cost_per_month': dict(convert_to='float'), + 'location': dict(key='region'), + 'main_ip': dict(key='v4_main_ip'), + 'network_v4': dict(key='v4_network'), + 'gateway_v4': dict(key='v4_gateway'), + 'os': dict(), + 'pending_charges': dict(convert_to='float'), + 'power_status': dict(), + 'ram': dict(), + 'plan': dict(), + 'server_state': dict(), + 'status': dict(), + 'firewall_group': dict(), + 'tag': dict(), + 'v6_main_ip': dict(), + 'v6_network': dict(), + 'v6_network_size': dict(), + 'v6_networks': dict(), + 'vcpu_count': dict(convert_to='int'), +} + + +def _load_conf(path, account): + + if path: + conf = configparser.ConfigParser() + conf.read(path) + + if not conf._sections.get(account): + return None + + return dict(conf.items(account)) + else: + return Vultr.read_ini_config(account) + + +def _retrieve_servers(api_key): + api_url = 'https://api.vultr.com/v1/server/list' + + try: + response = open_url( + api_url, headers={'API-Key': api_key, 'Content-type': 'application/json'}, + http_agent=VULTR_USER_AGENT, + ) + servers_list = json.loads(response.read()) + if not servers_list: + return [] + + return [server for id, server in servers_list.items()] + except ValueError as e: + raise AnsibleError("Incorrect JSON payload") + except Exception as e: + raise AnsibleError("Error while fetching %s: %s" % (api_url, to_native(e))) + + +class InventoryModule(BaseInventoryPlugin): + NAME = 'vultr' + + def parse(self, inventory, loader, path, cache=True): + super(InventoryModule, self).parse(inventory, loader, path) + self._read_config_data(path=path) + + api_account = self.get_option('api_account') or 'default' + conf = _load_conf(self.get_option('api_config'), api_account) + + try: + api_key = self.get_option('api_key') or conf.get('key') + except Exception as e: + raise AnsibleError('Could not find an API key. Check the configuration files.') + + hostname_preference = self.get_option('hostname') or 'v4_main_ip' + + for server in _retrieve_servers(api_key): + server = Vultr.normalize_result(server, SCHEMA) + for group in ['region', 'os']: + self.inventory.add_group(group=server[group]) + self.inventory.add_host(group=server[group], host=server['name']) + + for attribute, value in server.items(): + self.inventory.set_variable(server['name'], attribute, value) + self.inventory.set_variable(server['name'], 'ansible_host', server[hostname_preference])