1#!/usr/bin/env python 2# Copyright 2015 IIX Inc. 3# 4# This file is part of Ansible 5# 6# Ansible is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# Ansible is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 18 19""" 20ovirt external inventory script 21================================= 22 23Generates inventory that Ansible can understand by making API requests to 24oVirt via the ovirt-engine-sdk-python library. 25 26When run against a specific host, this script returns the following variables 27based on the data obtained from the ovirt_sdk Node object: 28 - ovirt_uuid 29 - ovirt_id 30 - ovirt_image 31 - ovirt_machine_type 32 - ovirt_ips 33 - ovirt_name 34 - ovirt_description 35 - ovirt_status 36 - ovirt_zone 37 - ovirt_tags 38 - ovirt_stats 39 40When run in --list mode, instances are grouped by the following categories: 41 42 - zone: 43 zone group name. 44 - instance tags: 45 An entry is created for each tag. For example, if you have two instances 46 with a common tag called 'foo', they will both be grouped together under 47 the 'tag_foo' name. 48 - network name: 49 the name of the network is appended to 'network_' (e.g. the 'default' 50 network will result in a group named 'network_default') 51 - running status: 52 group name prefixed with 'status_' (e.g. status_up, status_down,..) 53 54Examples: 55 Execute uname on all instances in the us-central1-a zone 56 $ ansible -i ovirt.py us-central1-a -m shell -a "/bin/uname -a" 57 58 Use the ovirt inventory script to print out instance specific information 59 $ contrib/inventory/ovirt.py --host my_instance 60 61Author: Josha Inglis <jinglis@iix.net> based on the gce.py by Eric Johnson <erjohnso@google.com> 62Version: 0.0.1 63""" 64 65USER_AGENT_PRODUCT = "Ansible-ovirt_inventory_plugin" 66USER_AGENT_VERSION = "v1" 67 68import sys 69import os 70import argparse 71from collections import defaultdict 72from ansible.module_utils.six.moves import configparser as ConfigParser 73 74import json 75 76try: 77 # noinspection PyUnresolvedReferences 78 from ovirtsdk.api import API 79 # noinspection PyUnresolvedReferences 80 from ovirtsdk.xml import params 81except ImportError: 82 print("ovirt inventory script requires ovirt-engine-sdk-python") 83 sys.exit(1) 84 85 86class OVirtInventory(object): 87 def __init__(self): 88 # Read settings and parse CLI arguments 89 self.args = self.parse_cli_args() 90 self.driver = self.get_ovirt_driver() 91 92 # Just display data for specific host 93 if self.args.host: 94 print(self.json_format_dict( 95 self.node_to_dict(self.get_instance(self.args.host)), 96 pretty=self.args.pretty 97 )) 98 sys.exit(0) 99 100 # Otherwise, assume user wants all instances grouped 101 print( 102 self.json_format_dict( 103 data=self.group_instances(), 104 pretty=self.args.pretty 105 ) 106 ) 107 sys.exit(0) 108 109 @staticmethod 110 def get_ovirt_driver(): 111 """ 112 Determine the ovirt authorization settings and return a ovirt_sdk driver. 113 114 :rtype : ovirtsdk.api.API 115 """ 116 kwargs = {} 117 118 ovirt_ini_default_path = os.path.join( 119 os.path.dirname(os.path.realpath(__file__)), "ovirt.ini") 120 ovirt_ini_path = os.environ.get('OVIRT_INI_PATH', ovirt_ini_default_path) 121 122 # Create a ConfigParser. 123 # This provides empty defaults to each key, so that environment 124 # variable configuration (as opposed to INI configuration) is able 125 # to work. 126 config = ConfigParser.SafeConfigParser(defaults={ 127 'ovirt_url': '', 128 'ovirt_username': '', 129 'ovirt_password': '', 130 'ovirt_api_secrets': '', 131 }) 132 if 'ovirt' not in config.sections(): 133 config.add_section('ovirt') 134 config.read(ovirt_ini_path) 135 136 # Attempt to get ovirt params from a configuration file, if one 137 # exists. 138 secrets_path = config.get('ovirt', 'ovirt_api_secrets') 139 secrets_found = False 140 try: 141 # noinspection PyUnresolvedReferences,PyPackageRequirements 142 import secrets 143 144 kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {}) 145 secrets_found = True 146 except ImportError: 147 pass 148 149 if not secrets_found and secrets_path: 150 if not secrets_path.endswith('secrets.py'): 151 err = "Must specify ovirt_sdk secrets file as /absolute/path/to/secrets.py" 152 print(err) 153 sys.exit(1) 154 sys.path.append(os.path.dirname(secrets_path)) 155 try: 156 # noinspection PyUnresolvedReferences,PyPackageRequirements 157 import secrets 158 159 kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {}) 160 except ImportError: 161 pass 162 if not secrets_found: 163 kwargs = { 164 'url': config.get('ovirt', 'ovirt_url'), 165 'username': config.get('ovirt', 'ovirt_username'), 166 'password': config.get('ovirt', 'ovirt_password'), 167 } 168 169 # If the appropriate environment variables are set, they override 170 # other configuration; process those into our args and kwargs. 171 kwargs['url'] = os.environ.get('OVIRT_URL', kwargs['url']) 172 kwargs['username'] = next(val for val in [os.environ.get('OVIRT_EMAIL'), os.environ.get('OVIRT_USERNAME'), kwargs['username']] if val is not None) 173 kwargs['password'] = next(val for val in [os.environ.get('OVIRT_PASS'), os.environ.get('OVIRT_PASSWORD'), kwargs['password']] if val is not None) 174 175 # Retrieve and return the ovirt driver. 176 return API(insecure=True, **kwargs) 177 178 @staticmethod 179 def parse_cli_args(): 180 """ 181 Command line argument processing 182 183 :rtype : argparse.Namespace 184 """ 185 186 parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on ovirt') 187 parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') 188 parser.add_argument('--host', action='store', help='Get all information about an instance') 189 parser.add_argument('--pretty', action='store_true', default=False, help='Pretty format (default: False)') 190 return parser.parse_args() 191 192 def node_to_dict(self, inst): 193 """ 194 :type inst: params.VM 195 """ 196 if inst is None: 197 return {} 198 199 inst.get_custom_properties() 200 ips = [ip.get_address() for ip in inst.get_guest_info().get_ips().get_ip()] \ 201 if inst.get_guest_info() is not None else [] 202 stats = {} 203 for stat in inst.get_statistics().list(): 204 stats[stat.get_name()] = stat.get_values().get_value()[0].get_datum() 205 206 return { 207 'ovirt_uuid': inst.get_id(), 208 'ovirt_id': inst.get_id(), 209 'ovirt_image': inst.get_os().get_type(), 210 'ovirt_machine_type': self.get_machine_type(inst), 211 'ovirt_ips': ips, 212 'ovirt_name': inst.get_name(), 213 'ovirt_description': inst.get_description(), 214 'ovirt_status': inst.get_status().get_state(), 215 'ovirt_zone': inst.get_cluster().get_id(), 216 'ovirt_tags': self.get_tags(inst), 217 'ovirt_stats': stats, 218 # Hosts don't have a public name, so we add an IP 219 'ansible_ssh_host': ips[0] if len(ips) > 0 else None 220 } 221 222 @staticmethod 223 def get_tags(inst): 224 """ 225 :type inst: params.VM 226 """ 227 return [x.get_name() for x in inst.get_tags().list()] 228 229 def get_machine_type(self, inst): 230 inst_type = inst.get_instance_type() 231 if inst_type: 232 return self.driver.instancetypes.get(id=inst_type.id).name 233 234 # noinspection PyBroadException,PyUnusedLocal 235 def get_instance(self, instance_name): 236 """Gets details about a specific instance """ 237 try: 238 return self.driver.vms.get(name=instance_name) 239 except Exception as e: 240 return None 241 242 def group_instances(self): 243 """Group all instances""" 244 groups = defaultdict(list) 245 meta = {"hostvars": {}} 246 247 for node in self.driver.vms.list(): 248 assert isinstance(node, params.VM) 249 name = node.get_name() 250 251 meta["hostvars"][name] = self.node_to_dict(node) 252 253 zone = node.get_cluster().get_name() 254 groups[zone].append(name) 255 256 tags = self.get_tags(node) 257 for t in tags: 258 tag = 'tag_%s' % t 259 groups[tag].append(name) 260 261 nets = [x.get_name() for x in node.get_nics().list()] 262 for net in nets: 263 net = 'network_%s' % net 264 groups[net].append(name) 265 266 status = node.get_status().get_state() 267 stat = 'status_%s' % status.lower() 268 if stat in groups: 269 groups[stat].append(name) 270 else: 271 groups[stat] = [name] 272 273 groups["_meta"] = meta 274 275 return groups 276 277 @staticmethod 278 def json_format_dict(data, pretty=False): 279 """ Converts a dict to a JSON object and dumps it as a formatted 280 string """ 281 282 if pretty: 283 return json.dumps(data, sort_keys=True, indent=2) 284 else: 285 return json.dumps(data) 286 287 288# Run the script 289OVirtInventory() 290