1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4''' 5External inventory script for Abiquo 6==================================== 7 8Shamelessly copied from an existing inventory script. 9 10This script generates an inventory that Ansible can understand by making API requests to Abiquo API 11Requires some python libraries, ensure to have them installed when using this script. 12 13This script has been tested in Abiquo 3.0 but it may work also for Abiquo 2.6. 14 15Before using this script you may want to modify abiquo.ini config file. 16 17This script generates an Ansible hosts file with these host groups: 18 19ABQ_xxx: Defines a hosts itself by Abiquo VM name label 20all: Contains all hosts defined in Abiquo user's enterprise 21virtualdatecenter: Creates a host group for each virtualdatacenter containing all hosts defined on it 22virtualappliance: Creates a host group for each virtualappliance containing all hosts defined on it 23imagetemplate: Creates a host group for each image template containing all hosts using it 24 25''' 26 27# (c) 2014, Daniel Beneyto <daniel.beneyto@abiquo.com> 28# 29# This file is part of Ansible, 30# 31# Ansible is free software: you can redistribute it and/or modify 32# it under the terms of the GNU General Public License as published by 33# the Free Software Foundation, either version 3 of the License, or 34# (at your option) any later version. 35# 36# Ansible is distributed in the hope that it will be useful, 37# but WITHOUT ANY WARRANTY; without even the implied warranty of 38# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39# GNU General Public License for more details. 40# 41# You should have received a copy of the GNU General Public License 42# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 43 44import os 45import sys 46import time 47 48import json 49 50from ansible.module_utils.six.moves import configparser as ConfigParser 51from ansible.module_utils.urls import open_url 52 53 54def api_get(link, config): 55 try: 56 if link is None: 57 url = config.get('api', 'uri') + config.get('api', 'login_path') 58 headers = {"Accept": config.get('api', 'login_type')} 59 else: 60 url = link['href'] + '?limit=0' 61 headers = {"Accept": link['type']} 62 result = open_url(url, headers=headers, url_username=config.get('auth', 'apiuser').replace('\n', ''), 63 url_password=config.get('auth', 'apipass').replace('\n', '')) 64 return json.loads(result.read()) 65 except Exception: 66 return None 67 68 69def save_cache(data, config): 70 ''' saves item to cache ''' 71 dpath = config.get('cache', 'cache_dir') 72 try: 73 cache = open('/'.join([dpath, 'inventory']), 'w') 74 cache.write(json.dumps(data)) 75 cache.close() 76 except IOError as e: 77 pass # not really sure what to do here 78 79 80def get_cache(cache_item, config): 81 ''' returns cached item ''' 82 dpath = config.get('cache', 'cache_dir') 83 inv = {} 84 try: 85 cache = open('/'.join([dpath, 'inventory']), 'r') 86 inv = cache.read() 87 cache.close() 88 except IOError as e: 89 pass # not really sure what to do here 90 91 return inv 92 93 94def cache_available(config): 95 ''' checks if we have a 'fresh' cache available for item requested ''' 96 97 if config.has_option('cache', 'cache_dir'): 98 dpath = config.get('cache', 'cache_dir') 99 100 try: 101 existing = os.stat('/'.join([dpath, 'inventory'])) 102 except Exception: 103 # cache doesn't exist or isn't accessible 104 return False 105 106 if config.has_option('cache', 'cache_max_age'): 107 maxage = config.get('cache', 'cache_max_age') 108 if (int(time.time()) - int(existing.st_mtime)) <= int(maxage): 109 return True 110 111 return False 112 113 114def generate_inv_from_api(enterprise_entity, config): 115 try: 116 inventory['all'] = {} 117 inventory['all']['children'] = [] 118 inventory['all']['hosts'] = [] 119 inventory['_meta'] = {} 120 inventory['_meta']['hostvars'] = {} 121 122 enterprise = api_get(enterprise_entity, config) 123 vms_entity = next(link for link in enterprise['links'] if link['rel'] == 'virtualmachines') 124 vms = api_get(vms_entity, config) 125 for vmcollection in vms['collection']: 126 for link in vmcollection['links']: 127 if link['rel'] == 'virtualappliance': 128 vm_vapp = link['title'].replace('[', '').replace(']', '').replace(' ', '_') 129 elif link['rel'] == 'virtualdatacenter': 130 vm_vdc = link['title'].replace('[', '').replace(']', '').replace(' ', '_') 131 elif link['rel'] == 'virtualmachinetemplate': 132 vm_template = link['title'].replace('[', '').replace(']', '').replace(' ', '_') 133 134 # From abiquo.ini: Only adding to inventory VMs with public IP 135 if config.getboolean('defaults', 'public_ip_only') is True: 136 for link in vmcollection['links']: 137 if link['type'] == 'application/vnd.abiquo.publicip+json' and link['rel'] == 'ip': 138 vm_nic = link['title'] 139 break 140 else: 141 vm_nic = None 142 # Otherwise, assigning defined network interface IP address 143 else: 144 for link in vmcollection['links']: 145 if link['rel'] == config.get('defaults', 'default_net_interface'): 146 vm_nic = link['title'] 147 break 148 else: 149 vm_nic = None 150 151 vm_state = True 152 # From abiquo.ini: Only adding to inventory VMs deployed 153 if config.getboolean('defaults', 'deployed_only') is True and vmcollection['state'] == 'NOT_ALLOCATED': 154 vm_state = False 155 156 if vm_nic is not None and vm_state: 157 if vm_vapp not in inventory: 158 inventory[vm_vapp] = {} 159 inventory[vm_vapp]['children'] = [] 160 inventory[vm_vapp]['hosts'] = [] 161 if vm_vdc not in inventory: 162 inventory[vm_vdc] = {} 163 inventory[vm_vdc]['hosts'] = [] 164 inventory[vm_vdc]['children'] = [] 165 if vm_template not in inventory: 166 inventory[vm_template] = {} 167 inventory[vm_template]['children'] = [] 168 inventory[vm_template]['hosts'] = [] 169 if config.getboolean('defaults', 'get_metadata') is True: 170 meta_entity = next(link for link in vmcollection['links'] if link['rel'] == 'metadata') 171 try: 172 metadata = api_get(meta_entity, config) 173 if (config.getfloat("api", "version") >= 3.0): 174 vm_metadata = metadata['metadata'] 175 else: 176 vm_metadata = metadata['metadata']['metadata'] 177 inventory['_meta']['hostvars'][vm_nic] = vm_metadata 178 except Exception as e: 179 pass 180 181 inventory[vm_vapp]['children'].append(vmcollection['name']) 182 inventory[vm_vdc]['children'].append(vmcollection['name']) 183 inventory[vm_template]['children'].append(vmcollection['name']) 184 inventory['all']['children'].append(vmcollection['name']) 185 inventory[vmcollection['name']] = [] 186 inventory[vmcollection['name']].append(vm_nic) 187 188 return inventory 189 except Exception as e: 190 # Return empty hosts output 191 return {'all': {'hosts': []}, '_meta': {'hostvars': {}}} 192 193 194def get_inventory(enterprise, config): 195 ''' Reads the inventory from cache or Abiquo api ''' 196 197 if cache_available(config): 198 inv = get_cache('inventory', config) 199 else: 200 default_group = os.path.basename(sys.argv[0]).rstrip('.py') 201 # MAKE ABIQUO API CALLS # 202 inv = generate_inv_from_api(enterprise, config) 203 204 save_cache(inv, config) 205 return json.dumps(inv) 206 207 208if __name__ == '__main__': 209 inventory = {} 210 enterprise = {} 211 212 # Read config 213 config = ConfigParser.SafeConfigParser() 214 for configfilename in [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'abiquo.ini']: 215 if os.path.exists(configfilename): 216 config.read(configfilename) 217 break 218 219 try: 220 login = api_get(None, config) 221 enterprise = next(link for link in login['links'] if link['rel'] == 'enterprise') 222 except Exception as e: 223 enterprise = None 224 225 if cache_available(config): 226 inventory = get_cache('inventory', config) 227 else: 228 inventory = get_inventory(enterprise, config) 229 230 # return to ansible 231 sys.stdout.write(str(inventory)) 232 sys.stdout.flush() 233