1#!/usr/bin/env python 2 3# Copyright (C) 2014 Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr> 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18# Updated 2016 by Matt Harris <matthaeus.harris@gmail.com> 19# 20# Added support for Proxmox VE 4.x 21# Added support for using the Notes field of a VM to define groups and variables: 22# A well-formatted JSON object in the Notes field will be added to the _meta 23# section for that VM. In addition, the "groups" key of this JSON object may be 24# used to specify group membership: 25# 26# { "groups": ["utility", "databases"], "a": false, "b": true } 27 28import json 29import os 30import sys 31from optparse import OptionParser 32 33from ansible.module_utils.six import iteritems 34from ansible.module_utils.six.moves.urllib.parse import urlencode 35 36from ansible.module_utils.urls import open_url 37 38 39class ProxmoxNodeList(list): 40 def get_names(self): 41 return [node['node'] for node in self] 42 43 44class ProxmoxVM(dict): 45 def get_variables(self): 46 variables = {} 47 for key, value in iteritems(self): 48 variables['proxmox_' + key] = value 49 return variables 50 51 52class ProxmoxVMList(list): 53 def __init__(self, data=None): 54 data = [] if data is None else data 55 56 for item in data: 57 self.append(ProxmoxVM(item)) 58 59 def get_names(self): 60 return [vm['name'] for vm in self if vm['template'] != 1] 61 62 def get_by_name(self, name): 63 results = [vm for vm in self if vm['name'] == name] 64 return results[0] if len(results) > 0 else None 65 66 def get_variables(self): 67 variables = {} 68 for vm in self: 69 variables[vm['name']] = vm.get_variables() 70 71 return variables 72 73 74class ProxmoxPoolList(list): 75 def get_names(self): 76 return [pool['poolid'] for pool in self] 77 78 79class ProxmoxPool(dict): 80 def get_members_name(self): 81 return [member['name'] for member in self['members'] if member['template'] != 1] 82 83 84class ProxmoxAPI(object): 85 def __init__(self, options): 86 self.options = options 87 self.credentials = None 88 89 if not options.url: 90 raise Exception('Missing mandatory parameter --url (or PROXMOX_URL).') 91 elif not options.username: 92 raise Exception('Missing mandatory parameter --username (or PROXMOX_USERNAME).') 93 elif not options.password: 94 raise Exception('Missing mandatory parameter --password (or PROXMOX_PASSWORD).') 95 96 def auth(self): 97 request_path = '{0}api2/json/access/ticket'.format(self.options.url) 98 99 request_params = urlencode({ 100 'username': self.options.username, 101 'password': self.options.password, 102 }) 103 104 data = json.load(open_url(request_path, data=request_params)) 105 106 self.credentials = { 107 'ticket': data['data']['ticket'], 108 'CSRFPreventionToken': data['data']['CSRFPreventionToken'], 109 } 110 111 def get(self, url, data=None): 112 request_path = '{0}{1}'.format(self.options.url, url) 113 114 headers = {'Cookie': 'PVEAuthCookie={0}'.format(self.credentials['ticket'])} 115 request = open_url(request_path, data=data, headers=headers) 116 117 response = json.load(request) 118 return response['data'] 119 120 def nodes(self): 121 return ProxmoxNodeList(self.get('api2/json/nodes')) 122 123 def vms_by_type(self, node, type): 124 return ProxmoxVMList(self.get('api2/json/nodes/{0}/{1}'.format(node, type))) 125 126 def vm_description_by_type(self, node, vm, type): 127 return self.get('api2/json/nodes/{0}/{1}/{2}/config'.format(node, type, vm)) 128 129 def node_qemu(self, node): 130 return self.vms_by_type(node, 'qemu') 131 132 def node_qemu_description(self, node, vm): 133 return self.vm_description_by_type(node, vm, 'qemu') 134 135 def node_lxc(self, node): 136 return self.vms_by_type(node, 'lxc') 137 138 def node_lxc_description(self, node, vm): 139 return self.vm_description_by_type(node, vm, 'lxc') 140 141 def pools(self): 142 return ProxmoxPoolList(self.get('api2/json/pools')) 143 144 def pool(self, poolid): 145 return ProxmoxPool(self.get('api2/json/pools/{0}'.format(poolid))) 146 147 148def main_list(options): 149 results = { 150 'all': { 151 'hosts': [], 152 }, 153 '_meta': { 154 'hostvars': {}, 155 } 156 } 157 158 proxmox_api = ProxmoxAPI(options) 159 proxmox_api.auth() 160 161 for node in proxmox_api.nodes().get_names(): 162 qemu_list = proxmox_api.node_qemu(node) 163 results['all']['hosts'] += qemu_list.get_names() 164 results['_meta']['hostvars'].update(qemu_list.get_variables()) 165 lxc_list = proxmox_api.node_lxc(node) 166 results['all']['hosts'] += lxc_list.get_names() 167 results['_meta']['hostvars'].update(lxc_list.get_variables()) 168 169 for vm in results['_meta']['hostvars']: 170 vmid = results['_meta']['hostvars'][vm]['proxmox_vmid'] 171 try: 172 type = results['_meta']['hostvars'][vm]['proxmox_type'] 173 except KeyError: 174 type = 'qemu' 175 try: 176 description = proxmox_api.vm_description_by_type(node, vmid, type)['description'] 177 except KeyError: 178 description = None 179 180 try: 181 metadata = json.loads(description) 182 except TypeError: 183 metadata = {} 184 except ValueError: 185 metadata = { 186 'notes': description 187 } 188 189 if 'groups' in metadata: 190 # print metadata 191 for group in metadata['groups']: 192 if group not in results: 193 results[group] = { 194 'hosts': [] 195 } 196 results[group]['hosts'] += [vm] 197 198 results['_meta']['hostvars'][vm].update(metadata) 199 200 # pools 201 for pool in proxmox_api.pools().get_names(): 202 results[pool] = { 203 'hosts': proxmox_api.pool(pool).get_members_name(), 204 } 205 206 return results 207 208 209def main_host(options): 210 proxmox_api = ProxmoxAPI(options) 211 proxmox_api.auth() 212 213 for node in proxmox_api.nodes().get_names(): 214 qemu_list = proxmox_api.node_qemu(node) 215 qemu = qemu_list.get_by_name(options.host) 216 if qemu: 217 return qemu.get_variables() 218 219 return {} 220 221 222def main(): 223 parser = OptionParser(usage='%prog [options] --list | --host HOSTNAME') 224 parser.add_option('--list', action="store_true", default=False, dest="list") 225 parser.add_option('--host', dest="host") 226 parser.add_option('--url', default=os.environ.get('PROXMOX_URL'), dest='url') 227 parser.add_option('--username', default=os.environ.get('PROXMOX_USERNAME'), dest='username') 228 parser.add_option('--password', default=os.environ.get('PROXMOX_PASSWORD'), dest='password') 229 parser.add_option('--pretty', action="store_true", default=False, dest='pretty') 230 (options, args) = parser.parse_args() 231 232 if options.list: 233 data = main_list(options) 234 elif options.host: 235 data = main_host(options) 236 else: 237 parser.print_help() 238 sys.exit(1) 239 240 indent = None 241 if options.pretty: 242 indent = 2 243 244 print(json.dumps(data, indent=indent)) 245 246 247if __name__ == '__main__': 248 main() 249