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

/plugins/inventory/windows_azure.py

https://github.com/ajanthanm/ansible
Python | 232 lines | 132 code | 38 blank | 62 comment | 24 complexity | 983546ac2eee9b8c8a2f4a15a3474047 MD5 | raw file
  1#!/usr/bin/env python
  2
  3'''
  4Windows Azure external inventory script
  5=======================================
  6
  7Generates inventory that Ansible can understand by making API request to
  8Windows Azure using the azure python library.
  9
 10NOTE: This script assumes Ansible is being executed where azure is already
 11installed.
 12
 13    pip install azure
 14
 15Adapted from the ansible Linode plugin by Dan Slimmon.
 16'''
 17
 18# (c) 2013, John Whitbeck
 19#
 20# This file is part of Ansible,
 21#
 22# Ansible is free software: you can redistribute it and/or modify
 23# it under the terms of the GNU General Public License as published by
 24# the Free Software Foundation, either version 3 of the License, or
 25# (at your option) any later version.
 26#
 27# Ansible is distributed in the hope that it will be useful,
 28# but WITHOUT ANY WARRANTY; without even the implied warranty of
 29# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 30# GNU General Public License for more details.
 31#
 32# You should have received a copy of the GNU General Public License
 33# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 34
 35######################################################################
 36
 37# Standard imports
 38import re
 39import sys
 40import argparse
 41import os
 42from urlparse import urlparse
 43from time import time
 44try:
 45    import json
 46except ImportError:
 47    import simplejson as json
 48
 49try:
 50    import azure
 51    from azure import WindowsAzureError
 52    from azure.servicemanagement import ServiceManagementService
 53except ImportError as e:
 54    print "failed=True msg='`azure` library required for this script'"
 55    sys.exit(1)
 56
 57
 58# Imports for ansible
 59import ConfigParser
 60
 61class AzureInventory(object):
 62    def __init__(self):
 63        """Main execution path."""
 64        # Inventory grouped by display group
 65        self.inventory = {}
 66        # Index of deployment name -> host
 67        self.index = {}
 68
 69        # Read settings and parse CLI arguments
 70        self.read_settings()
 71        self.read_environment()
 72        self.parse_cli_args()
 73
 74        # Initialize Azure ServiceManagementService
 75        self.sms = ServiceManagementService(self.subscription_id, self.cert_path)
 76
 77        # Cache
 78        if self.args.refresh_cache:
 79            self.do_api_calls_update_cache()
 80        elif not self.is_cache_valid():
 81            self.do_api_calls_update_cache()
 82
 83        if self.args.list_images:
 84            data_to_print = self.json_format_dict(self.get_images(), True)
 85        elif self.args.list:
 86            # Display list of nodes for inventory
 87            if len(self.inventory) == 0:
 88                data_to_print = self.get_inventory_from_cache()
 89            else:
 90                data_to_print = self.json_format_dict(self.inventory, True)
 91
 92        print data_to_print
 93
 94    def get_images(self):
 95        images = []
 96        for image in self.sms.list_os_images():
 97            if str(image.label).lower().find(self.args.list_images.lower()) >= 0:
 98                images.append(vars(image))
 99        return json.loads(json.dumps(images, default=lambda o: o.__dict__))
100
101    def is_cache_valid(self):
102        """Determines if the cache file has expired, or if it is still valid."""
103        if os.path.isfile(self.cache_path_cache):
104            mod_time = os.path.getmtime(self.cache_path_cache)
105            current_time = time()
106            if (mod_time + self.cache_max_age) > current_time:
107                if os.path.isfile(self.cache_path_index):
108                    return True
109        return False
110
111    def read_settings(self):
112        """Reads the settings from the .ini file."""
113        config = ConfigParser.SafeConfigParser()
114        config.read(os.path.dirname(os.path.realpath(__file__)) + '/windows_azure.ini')
115
116        # Credentials related
117        if config.has_option('azure', 'subscription_id'):
118            self.subscription_id = config.get('azure', 'subscription_id')
119        if config.has_option('azure', 'cert_path'):
120            self.cert_path = config.get('azure', 'cert_path')
121
122        # Cache related
123        if config.has_option('azure', 'cache_path'):
124            cache_path = config.get('azure', 'cache_path')
125            self.cache_path_cache = cache_path + "/ansible-azure.cache"
126            self.cache_path_index = cache_path + "/ansible-azure.index"
127        if config.has_option('azure', 'cache_max_age'):
128            self.cache_max_age = config.getint('azure', 'cache_max_age')
129
130    def read_environment(self):
131        ''' Reads the settings from environment variables '''
132        # Credentials
133        if os.getenv("AZURE_SUBSCRIPTION_ID"): self.subscription_id = os.getenv("AZURE_SUBSCRIPTION_ID")
134        if os.getenv("AZURE_CERT_PATH"):       self.cert_path = os.getenv("AZURE_CERT_PATH")
135
136
137    def parse_cli_args(self):
138        """Command line argument processing"""
139        parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Azure')
140        parser.add_argument('--list', action='store_true', default=True,
141                           help='List nodes (default: True)')
142        parser.add_argument('--list-images', action='store',
143                           help='Get all available images.')
144        parser.add_argument('--refresh-cache', action='store_true', default=False,
145                           help='Force refresh of cache by making API requests to Azure (default: False - use cache files)')
146        self.args = parser.parse_args()
147
148    def do_api_calls_update_cache(self):
149        """Do API calls, and save data in cache files."""
150        self.add_cloud_services()
151        self.write_to_cache(self.inventory, self.cache_path_cache)
152        self.write_to_cache(self.index, self.cache_path_index)
153
154    def add_cloud_services(self):
155        """Makes an Azure API call to get the list of cloud services."""
156        try:
157            for cloud_service in self.sms.list_hosted_services():
158                self.add_deployments(cloud_service)
159        except WindowsAzureError as e:
160            print "Looks like Azure's API is down:"
161            print
162            print e
163            sys.exit(1)
164
165    def add_deployments(self, cloud_service):
166        """Makes an Azure API call to get the list of virtual machines associated with a cloud service"""
167        try:
168            for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
169                if deployment.deployment_slot == "Production":
170                    self.add_deployment(cloud_service, deployment)
171        except WindowsAzureError as e:
172            print "Looks like Azure's API is down:"
173            print
174            print e
175            sys.exit(1)
176
177    def add_deployment(self, cloud_service, deployment):
178        """Adds a deployment to the inventory and index"""
179
180        dest = urlparse(deployment.url).hostname
181
182        # Add to index
183        self.index[dest] = deployment.name
184
185        # List of all azure deployments
186        self.push(self.inventory, "azure", dest)
187
188        # Inventory: Group by service name
189        self.push(self.inventory, self.to_safe(cloud_service.service_name), dest)
190
191        # Inventory: Group by region
192        self.push(self.inventory, self.to_safe(cloud_service.hosted_service_properties.location), dest)
193
194    def push(self, my_dict, key, element):
195        """Pushed an element onto an array that may not have been defined in the dict."""
196        if key in my_dict:
197            my_dict[key].append(element);
198        else:
199            my_dict[key] = [element]
200
201    def get_inventory_from_cache(self):
202        """Reads the inventory from the cache file and returns it as a JSON object."""
203        cache = open(self.cache_path_cache, 'r')
204        json_inventory = cache.read()
205        return json_inventory
206
207    def load_index_from_cache(self):
208        """Reads the index from the cache file and sets self.index."""
209        cache = open(self.cache_path_index, 'r')
210        json_index = cache.read()
211        self.index = json.loads(json_index)
212
213    def write_to_cache(self, data, filename):
214        """Writes data in JSON format to a file."""
215        json_data = self.json_format_dict(data, True)
216        cache = open(filename, 'w')
217        cache.write(json_data)
218        cache.close()
219
220    def to_safe(self, word):
221        """Escapes any characters that would be invalid in an ansible group name."""
222        return re.sub("[^A-Za-z0-9\-]", "_", word)
223
224    def json_format_dict(self, data, pretty=False):
225        """Converts a dict to a JSON object and dumps it as a formatted string."""
226        if pretty:
227            return json.dumps(data, sort_keys=True, indent=2)
228        else:
229            return json.dumps(data)
230
231
232AzureInventory()