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