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