1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# (c) 2015, René Moser <mail@renemoser.net>
5#
6# This file is part of Ansible,
7#
8# Ansible is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# Ansible is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
20
21######################################################################
22
23"""
24Ansible CloudStack external inventory script.
25=============================================
26
27Generates Ansible inventory from CloudStack. Configuration is read from
28'cloudstack.ini'. If you need to pass the project, write a simple wrapper
29script, e.g. project_cloudstack.sh:
30
31  #!/bin/bash
32  cloudstack.py --project <your_project> $@
33
34
35When run against a specific host, this script returns the following attributes
36based on the data obtained from CloudStack API:
37
38  "web01": {
39    "cpu_number": 2,
40    "nic": [
41      {
42        "ip": "10.102.76.98",
43        "mac": "02:00:50:99:00:01",
44        "type": "Isolated",
45        "netmask": "255.255.255.0",
46        "gateway": "10.102.76.1"
47      },
48      {
49        "ip": "10.102.138.63",
50        "mac": "06:b7:5a:00:14:84",
51        "type": "Shared",
52        "netmask": "255.255.255.0",
53        "gateway": "10.102.138.1"
54      }
55    ],
56    "default_ip": "10.102.76.98",
57    "zone": "ZUERICH",
58    "created": "2014-07-02T07:53:50+0200",
59    "hypervisor": "VMware",
60    "memory": 2048,
61    "state": "Running",
62    "tags": [],
63    "cpu_speed": 1800,
64    "affinity_group": [],
65    "service_offering": "Small",
66    "cpu_used": "62%"
67  }
68
69
70usage: cloudstack.py [--list] [--host HOST] [--project PROJECT]  [--domain DOMAIN]
71"""
72
73from __future__ import print_function
74
75import sys
76import argparse
77import json
78
79try:
80    from cs import CloudStack, CloudStackException, read_config
81except ImportError:
82    print("Error: CloudStack library must be installed: pip install cs.",
83          file=sys.stderr)
84    sys.exit(1)
85
86
87class CloudStackInventory(object):
88    def __init__(self):
89
90        parser = argparse.ArgumentParser()
91        parser.add_argument('--host')
92        parser.add_argument('--list', action='store_true')
93        parser.add_argument('--tag', help="Filter machines by a tag. Should be in the form key=value.")
94        parser.add_argument('--project')
95        parser.add_argument('--domain')
96
97        options = parser.parse_args()
98        try:
99            self.cs = CloudStack(**read_config())
100        except CloudStackException:
101            print("Error: Could not connect to CloudStack API", file=sys.stderr)
102
103        domain_id = None
104        if options.domain:
105            domain_id = self.get_domain_id(options.domain)
106
107        project_id = None
108        if options.project:
109            project_id = self.get_project_id(options.project, domain_id)
110
111        if options.host:
112            data = self.get_host(options.host, project_id, domain_id)
113            print(json.dumps(data, indent=2))
114
115        elif options.list:
116            tags = dict()
117            if options.tag:
118                tags['tags[0].key'], tags['tags[0].value'] = options.tag.split('=')
119            data = self.get_list(project_id, domain_id, **tags)
120            print(json.dumps(data, indent=2))
121        else:
122            print("usage: --list [--tag <tag>] | --host <hostname> [--project <project>] [--domain <domain_path>]",
123                  file=sys.stderr)
124            sys.exit(1)
125
126    def get_domain_id(self, domain):
127        domains = self.cs.listDomains(listall=True)
128        if domains:
129            for d in domains['domain']:
130                if d['path'].lower() == domain.lower():
131                    return d['id']
132        print("Error: Domain %s not found." % domain, file=sys.stderr)
133        sys.exit(1)
134
135    def get_project_id(self, project, domain_id=None):
136        projects = self.cs.listProjects(domainid=domain_id)
137        if projects:
138            for p in projects['project']:
139                if p['name'] == project or p['id'] == project:
140                    return p['id']
141        print("Error: Project %s not found." % project, file=sys.stderr)
142        sys.exit(1)
143
144    def get_host(self, name, project_id=None, domain_id=None, **kwargs):
145        hosts = self.cs.listVirtualMachines(projectid=project_id, domainid=domain_id, fetch_list=True, **kwargs)
146        data = {}
147        if not hosts:
148            return data
149        for host in hosts:
150            host_name = host['displayname']
151            if name == host_name:
152                data['zone'] = host['zonename']
153                if 'group' in host:
154                    data['group'] = host['group']
155                data['state'] = host['state']
156                data['service_offering'] = host['serviceofferingname']
157                data['affinity_group'] = host['affinitygroup']
158                data['security_group'] = host['securitygroup']
159                data['cpu_number'] = host['cpunumber']
160                if 'cpu_speed' in host:
161                    data['cpu_speed'] = host['cpuspeed']
162                if 'cpuused' in host:
163                    data['cpu_used'] = host['cpuused']
164                data['memory'] = host['memory']
165                data['tags'] = host['tags']
166                if 'hypervisor' in host:
167                    data['hypervisor'] = host['hypervisor']
168                data['created'] = host['created']
169                data['nic'] = []
170                for nic in host['nic']:
171                    nicdata = {
172                        'ip': nic['ipaddress'],
173                        'mac': nic['macaddress'],
174                        'netmask': nic['netmask'],
175                        'gateway': nic['gateway'],
176                        'type': nic['type'],
177                    }
178                    if 'ip6address' in nic:
179                        nicdata['ip6'] = nic['ip6address']
180                    if 'gateway' in nic:
181                        nicdata['gateway'] = nic['gateway']
182                    if 'netmask' in nic:
183                        nicdata['netmask'] = nic['netmask']
184                    data['nic'].append(nicdata)
185                    if nic['isdefault']:
186                        data['default_ip'] = nic['ipaddress']
187                        if 'ip6address' in nic:
188                            data['default_ip6'] = nic['ip6address']
189                break
190        return data
191
192    def get_list(self, project_id=None, domain_id=None, **kwargs):
193        data = {
194            'all': {
195                'hosts': [],
196            },
197            '_meta': {
198                'hostvars': {},
199            },
200        }
201
202        groups = self.cs.listInstanceGroups(projectid=project_id, domainid=domain_id)
203        if groups:
204            for group in groups['instancegroup']:
205                group_name = group['name']
206                if group_name and group_name not in data:
207                    data[group_name] = {
208                        'hosts': []
209                    }
210
211        hosts = self.cs.listVirtualMachines(projectid=project_id, domainid=domain_id, fetch_list=True, **kwargs)
212        if not hosts:
213            return data
214        for host in hosts:
215            host_name = host['displayname']
216            data['all']['hosts'].append(host_name)
217            data['_meta']['hostvars'][host_name] = {}
218
219            # Make a group per zone
220            data['_meta']['hostvars'][host_name]['zone'] = host['zonename']
221            group_name = host['zonename']
222            if group_name not in data:
223                data[group_name] = {
224                    'hosts': []
225                }
226            data[group_name]['hosts'].append(host_name)
227
228            if 'group' in host:
229                data['_meta']['hostvars'][host_name]['group'] = host['group']
230            data['_meta']['hostvars'][host_name]['state'] = host['state']
231            data['_meta']['hostvars'][host_name]['service_offering'] = host['serviceofferingname']
232            data['_meta']['hostvars'][host_name]['affinity_group'] = host['affinitygroup']
233            data['_meta']['hostvars'][host_name]['security_group'] = host['securitygroup']
234            data['_meta']['hostvars'][host_name]['cpu_number'] = host['cpunumber']
235            if 'cpuspeed' in host:
236                data['_meta']['hostvars'][host_name]['cpu_speed'] = host['cpuspeed']
237            if 'cpuused' in host:
238                data['_meta']['hostvars'][host_name]['cpu_used'] = host['cpuused']
239            data['_meta']['hostvars'][host_name]['created'] = host['created']
240            data['_meta']['hostvars'][host_name]['memory'] = host['memory']
241            data['_meta']['hostvars'][host_name]['tags'] = host['tags']
242            if 'hypervisor' in host:
243                data['_meta']['hostvars'][host_name]['hypervisor'] = host['hypervisor']
244            data['_meta']['hostvars'][host_name]['created'] = host['created']
245            data['_meta']['hostvars'][host_name]['nic'] = []
246            for nic in host['nic']:
247                nicdata = {
248                    'ip': nic['ipaddress'],
249                    'mac': nic['macaddress'],
250                    'netmask': nic['netmask'],
251                    'gateway': nic['gateway'],
252                    'type': nic['type'],
253                }
254                if 'ip6address' in nic:
255                    nicdata['ip6'] = nic['ip6address']
256                if 'gateway' in nic:
257                    nicdata['gateway'] = nic['gateway']
258                if 'netmask' in nic:
259                    nicdata['netmask'] = nic['netmask']
260                data['_meta']['hostvars'][host_name]['nic'].append(nicdata)
261                if nic['isdefault']:
262                    data['_meta']['hostvars'][host_name]['default_ip'] = nic['ipaddress']
263                    if 'ip6address' in nic:
264                        data['_meta']['hostvars'][host_name]['default_ip6'] = nic['ip6address']
265
266            group_name = ''
267            if 'group' in host:
268                group_name = host['group']
269
270            if group_name and group_name in data:
271                data[group_name]['hosts'].append(host_name)
272        return data
273
274
275if __name__ == '__main__':
276    CloudStackInventory()
277