PageRenderTime 38ms CodeModel.GetById 11ms app.highlight 22ms RepoModel.GetById 2ms app.codeStats 0ms

/plugins/inventory/apache-libcloud.py

https://github.com/ajanthanm/ansible
Python | 355 lines | 238 code | 49 blank | 68 comment | 28 complexity | c322f105d52867b625ddfedb44366b1a MD5 | raw file
  1#!/usr/bin/env python
  2
  3# (c) 2013, Sebastien Goasguen <runseb@gmail.com>
  4#
  5# This file is part of Ansible,
  6#
  7# Ansible is free software: you can redistribute it and/or modify
  8# it under the terms of the GNU General Public License as published by
  9# the Free Software Foundation, either version 3 of the License, or
 10# (at your option) any later version.
 11#
 12# Ansible is distributed in the hope that it will be useful,
 13# but WITHOUT ANY WARRANTY; without even the implied warranty of
 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15# GNU General Public License for more details.
 16#
 17# You should have received a copy of the GNU General Public License
 18# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 19
 20######################################################################
 21
 22'''
 23Apache Libcloud generic external inventory script
 24=================================
 25
 26Generates inventory that Ansible can understand by making API request to
 27Cloud providers using the Apache libcloud library.
 28
 29This script also assumes there is a libcloud.ini file alongside it
 30
 31'''
 32
 33import sys
 34import os
 35import argparse
 36import re
 37from time import time
 38import ConfigParser
 39
 40from libcloud.compute.types import Provider
 41from libcloud.compute.providers import get_driver
 42import libcloud.security as sec
 43
 44try:
 45    import json
 46except ImportError:
 47    import simplejson as json
 48
 49
 50class LibcloudInventory(object):
 51    def __init__(self):
 52        ''' Main execution path '''
 53
 54        # Inventory grouped by instance IDs, tags, security groups, regions,
 55        # and availability zones
 56        self.inventory = {}
 57
 58        # Index of hostname (address) to instance ID
 59        self.index = {}
 60
 61        # Read settings and parse CLI arguments
 62        self.read_settings()
 63        self.parse_cli_args()
 64
 65        # Cache
 66        if self.args.refresh_cache:
 67            self.do_api_calls_update_cache()
 68        elif not self.is_cache_valid():
 69            self.do_api_calls_update_cache()
 70
 71        # Data to print
 72        if self.args.host:
 73            data_to_print = self.get_host_info()
 74
 75        elif self.args.list:
 76            # Display list of instances for inventory
 77            if len(self.inventory) == 0:
 78                data_to_print = self.get_inventory_from_cache()
 79            else:
 80                data_to_print = self.json_format_dict(self.inventory, True)
 81
 82        print data_to_print
 83
 84
 85    def is_cache_valid(self):
 86        ''' Determines if the cache files have expired, or if it is still valid '''
 87
 88        if os.path.isfile(self.cache_path_cache):
 89            mod_time = os.path.getmtime(self.cache_path_cache)
 90            current_time = time()
 91            if (mod_time + self.cache_max_age) > current_time:
 92                if os.path.isfile(self.cache_path_index):
 93                    return True
 94
 95        return False
 96
 97
 98    def read_settings(self):
 99        ''' Reads the settings from the libcloud.ini file '''
100
101        config = ConfigParser.SafeConfigParser()
102        libcloud_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'libcloud.ini')
103        libcloud_ini_path = os.environ.get('LIBCLOUD_INI_PATH', libcloud_default_ini_path)
104        config.read(libcloud_ini_path)
105
106        if not config.has_section('driver'):
107            raise ValueError('libcloud.ini file must contain a [driver] section')
108
109        if config.has_option('driver', 'provider'):
110            self.provider = config.get('driver','provider')
111        else:
112            raise ValueError('libcloud.ini does not have a provider defined')
113
114        if config.has_option('driver', 'key'):
115            self.key = config.get('driver','key')
116        else:
117            raise ValueError('libcloud.ini does not have a key defined')
118
119        if config.has_option('driver', 'secret'):
120            self.secret = config.get('driver','secret')
121        else:
122            raise ValueError('libcloud.ini does not have a secret defined')
123
124        if config.has_option('driver', 'host'):
125            self.host = config.get('driver', 'host')
126        if config.has_option('driver', 'secure'):
127            self.secure = config.get('driver', 'secure')
128        if config.has_option('driver', 'verify_ssl_cert'):
129            self.verify_ssl_cert = config.get('driver', 'verify_ssl_cert')
130        if config.has_option('driver', 'port'):
131            self.port = config.get('driver', 'port')
132        if config.has_option('driver', 'path'):
133            self.path = config.get('driver', 'path')
134        if config.has_option('driver', 'api_version'):
135            self.api_version = config.get('driver', 'api_version')    
136
137        Driver = get_driver(getattr(Provider, self.provider))
138
139        self.conn = Driver(key=self.key, secret=self.secret, secure=self.secure,
140                           host=self.host, path=self.path)
141
142        # Cache related
143        cache_path = config.get('cache', 'cache_path')
144        self.cache_path_cache = cache_path + "/ansible-libcloud.cache"
145        self.cache_path_index = cache_path + "/ansible-libcloud.index"
146        self.cache_max_age = config.getint('cache', 'cache_max_age')
147        
148
149    def parse_cli_args(self):
150        '''
151        Command line argument processing
152        '''
153
154        parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on libcloud supported providers')
155        parser.add_argument('--list', action='store_true', default=True,
156                           help='List instances (default: True)')
157        parser.add_argument('--host', action='store',
158                           help='Get all the variables about a specific instance')
159        parser.add_argument('--refresh-cache', action='store_true', default=False,
160                           help='Force refresh of cache by making API requests to libcloud supported providers (default: False - use cache files)')
161        self.args = parser.parse_args()
162
163
164    def do_api_calls_update_cache(self):
165        ''' 
166        Do API calls to a location, and save data in cache files 
167        '''
168
169        self.get_nodes()
170
171        self.write_to_cache(self.inventory, self.cache_path_cache)
172        self.write_to_cache(self.index, self.cache_path_index)
173
174
175    def get_nodes(self):
176        '''
177        Gets the list of all nodes
178        '''
179
180        for node in self.conn.list_nodes():
181            self.add_node(node)
182
183
184    def get_node(self, node_id):
185        '''
186        Gets details about a specific node
187        '''
188
189        return [node for node in self.conn.list_nodes() if node.id == node_id][0]
190
191
192    def add_node(self, node):
193        '''
194        Adds a node to the inventory and index, as long as it is
195        addressable 
196        '''
197
198        # Only want running instances
199        if node.state != 0:
200            return
201
202        # Select the best destination address
203        if not node.public_ips == []:
204            dest = node.public_ips[0]
205        if not dest:
206            # Skip instances we cannot address (e.g. private VPC subnet)
207            return
208
209        # Add to index
210        self.index[dest] = node.name
211
212        # Inventory: Group by instance ID (always a group of 1)
213        self.inventory[node.name] = [dest]
214        '''
215        # Inventory: Group by region
216        self.push(self.inventory, region, dest)
217
218        # Inventory: Group by availability zone
219        self.push(self.inventory, node.placement, dest)
220
221        # Inventory: Group by instance type
222        self.push(self.inventory, self.to_safe('type_' + node.instance_type), dest)
223        '''
224        # Inventory: Group by key pair
225        if node.extra['keyname']:
226            self.push(self.inventory, self.to_safe('key_' + node.extra['keyname']), dest)
227            
228        # Inventory: Group by security group, quick thing to handle single sg
229        if node.extra['securitygroup']:
230            self.push(self.inventory, self.to_safe('sg_' + node.extra['securitygroup'][0]), dest)
231
232    def get_host_info(self):
233        '''
234        Get variables about a specific host
235        '''
236
237        if len(self.index) == 0:
238            # Need to load index from cache
239            self.load_index_from_cache()
240
241        if not self.args.host in self.index:
242            # try updating the cache
243            self.do_api_calls_update_cache()
244            if not self.args.host in self.index:
245                # host migh not exist anymore
246                return self.json_format_dict({}, True)
247
248        node_id = self.index[self.args.host]
249
250        node = self.get_node(node_id)
251        instance_vars = {}
252        for key in vars(instance):
253            value = getattr(instance, key)
254            key = self.to_safe('ec2_' + key)
255
256            # Handle complex types
257            if type(value) in [int, bool]:
258                instance_vars[key] = value
259            elif type(value) in [str, unicode]:
260                instance_vars[key] = value.strip()
261            elif type(value) == type(None):
262                instance_vars[key] = ''
263            elif key == 'ec2_region':
264                instance_vars[key] = value.name
265            elif key == 'ec2_tags':
266                for k, v in value.iteritems():
267                    key = self.to_safe('ec2_tag_' + k)
268                    instance_vars[key] = v
269            elif key == 'ec2_groups':
270                group_ids = []
271                group_names = []
272                for group in value:
273                    group_ids.append(group.id)
274                    group_names.append(group.name)
275                instance_vars["ec2_security_group_ids"] = ','.join(group_ids)
276                instance_vars["ec2_security_group_names"] = ','.join(group_names)
277            else:
278                pass
279                # TODO Product codes if someone finds them useful
280                #print key
281                #print type(value)
282                #print value
283
284        return self.json_format_dict(instance_vars, True)
285
286
287    def push(self, my_dict, key, element):
288        '''
289        Pushed an element onto an array that may not have been defined in
290        the dict
291        '''
292
293        if key in my_dict:
294            my_dict[key].append(element);
295        else:
296            my_dict[key] = [element]
297
298
299    def get_inventory_from_cache(self):
300        '''
301        Reads the inventory from the cache file and returns it as a JSON
302        object
303        '''
304
305        cache = open(self.cache_path_cache, 'r')
306        json_inventory = cache.read()
307        return json_inventory
308
309
310    def load_index_from_cache(self):
311        '''
312        Reads the index from the cache file sets self.index
313        '''
314
315        cache = open(self.cache_path_index, 'r')
316        json_index = cache.read()
317        self.index = json.loads(json_index)
318
319
320    def write_to_cache(self, data, filename):
321        '''
322        Writes data in JSON format to a file
323        '''
324
325        json_data = self.json_format_dict(data, True)
326        cache = open(filename, 'w')
327        cache.write(json_data)
328        cache.close()
329
330
331    def to_safe(self, word):
332        '''
333        Converts 'bad' characters in a string to underscores so they can be
334        used as Ansible groups
335        '''
336
337        return re.sub("[^A-Za-z0-9\-]", "_", word)
338
339
340    def json_format_dict(self, data, pretty=False):
341        '''
342        Converts a dict to a JSON object and dumps it as a formatted
343        string
344        '''
345
346        if pretty:
347            return json.dumps(data, sort_keys=True, indent=2)
348        else:
349            return json.dumps(data)
350
351def main():
352    LibcloudInventory()
353
354if __name__ == '__main__':
355	main()