1#!/usr/bin/env python
2
3# (c) 2013, Greg Buehler
4# (c) 2018, Filippo Ferrazini
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"""
24Zabbix Server external inventory script.
25========================================
26
27Returns hosts and hostgroups from Zabbix Server.
28If you want to run with --limit against a host group with space in the
29name, use asterisk. For example --limit="Linux*servers".
30
31Configuration is read from `zabbix.ini`.
32
33Tested with Zabbix Server 2.0.6, 3.2.3 and 3.4.
34"""
35
36from __future__ import print_function
37
38import os
39import sys
40import argparse
41from ansible.module_utils.six.moves import configparser
42
43try:
44    from zabbix_api import ZabbixAPI
45except Exception:
46    print("Error: Zabbix API library must be installed: pip install zabbix-api.",
47          file=sys.stderr)
48    sys.exit(1)
49
50import json
51
52
53class ZabbixInventory(object):
54
55    def read_settings(self):
56        config = configparser.SafeConfigParser()
57        conf_path = './zabbix.ini'
58        if not os.path.exists(conf_path):
59            conf_path = os.path.dirname(os.path.realpath(__file__)) + '/zabbix.ini'
60        if os.path.exists(conf_path):
61            config.read(conf_path)
62        # server
63        if config.has_option('zabbix', 'server'):
64            self.zabbix_server = config.get('zabbix', 'server')
65
66        # login
67        if config.has_option('zabbix', 'username'):
68            self.zabbix_username = config.get('zabbix', 'username')
69        if config.has_option('zabbix', 'password'):
70            self.zabbix_password = config.get('zabbix', 'password')
71        # ssl certs
72        if config.has_option('zabbix', 'validate_certs'):
73            if config.get('zabbix', 'validate_certs') in ['false', 'False', False]:
74                self.validate_certs = False
75        # host inventory
76        if config.has_option('zabbix', 'read_host_inventory'):
77            if config.get('zabbix', 'read_host_inventory') in ['true', 'True', True]:
78                self.read_host_inventory = True
79        # host interface
80        if config.has_option('zabbix', 'use_host_interface'):
81            if config.get('zabbix', 'use_host_interface') in ['false', 'False', False]:
82                self.use_host_interface = False
83
84    def read_cli(self):
85        parser = argparse.ArgumentParser()
86        parser.add_argument('--host')
87        parser.add_argument('--list', action='store_true')
88        self.options = parser.parse_args()
89
90    def hoststub(self):
91        return {
92            'hosts': []
93        }
94
95    def get_host(self, api, name):
96        api_query = {'output': 'extend', 'selectGroups': 'extend', "filter": {"host": [name]}}
97        if self.use_host_interface:
98            api_query['selectInterfaces'] = ['useip', 'ip', 'dns']
99        if self.read_host_inventory:
100            api_query['selectInventory'] = "extend"
101
102        data = {'ansible_ssh_host': name}
103        if self.use_host_interface or self.read_host_inventory:
104            try:
105                hosts_data = api.host.get(api_query)[0]
106                if 'interfaces' in hosts_data:
107                    # use first interface only
108                    if hosts_data['interfaces'][0]['useip'] == 0:
109                        data['ansible_ssh_host'] = hosts_data['interfaces'][0]['dns']
110                    else:
111                        data['ansible_ssh_host'] = hosts_data['interfaces'][0]['ip']
112                if ('inventory' in hosts_data) and (hosts_data['inventory']):
113                    data.update(hosts_data['inventory'])
114            except IndexError:
115                # Host not found in zabbix
116                pass
117        return data
118
119    def get_list(self, api):
120        api_query = {'output': 'extend', 'selectGroups': 'extend'}
121        if self.use_host_interface:
122            api_query['selectInterfaces'] = ['useip', 'ip', 'dns']
123        if self.read_host_inventory:
124            api_query['selectInventory'] = "extend"
125
126        hosts_data = api.host.get(api_query)
127        data = {'_meta': {'hostvars': {}}}
128
129        data[self.defaultgroup] = self.hoststub()
130        for host in hosts_data:
131            hostname = host['name']
132            hostvars = dict()
133            data[self.defaultgroup]['hosts'].append(hostname)
134
135            for group in host['groups']:
136                groupname = group['name']
137
138                if groupname not in data:
139                    data[groupname] = self.hoststub()
140
141                data[groupname]['hosts'].append(hostname)
142            if 'interfaces' in host:
143                # use first interface only
144                if host['interfaces'][0]['useip'] == 0:
145                    hostvars['ansible_ssh_host'] = host['interfaces'][0]['dns']
146                else:
147                    hostvars['ansible_ssh_host'] = host['interfaces'][0]['ip']
148            if ('inventory' in host) and (host['inventory']):
149                hostvars.update(host['inventory'])
150            data['_meta']['hostvars'][hostname] = hostvars
151
152        return data
153
154    def __init__(self):
155
156        self.defaultgroup = 'group_all'
157        self.zabbix_server = None
158        self.zabbix_username = None
159        self.zabbix_password = None
160        self.validate_certs = True
161        self.read_host_inventory = False
162        self.use_host_interface = True
163
164        self.meta = {}
165
166        self.read_settings()
167        self.read_cli()
168
169        if self.zabbix_server and self.zabbix_username:
170            try:
171                api = ZabbixAPI(server=self.zabbix_server, validate_certs=self.validate_certs)
172                api.login(user=self.zabbix_username, password=self.zabbix_password)
173            # zabbix_api tries to exit if it cannot parse what the zabbix server returned
174            # so we have to use SystemExit here
175            except (Exception, SystemExit) as e:
176                print("Error: Could not login to Zabbix server. Check your zabbix.ini.", file=sys.stderr)
177                sys.exit(1)
178
179            if self.options.host:
180                data = self.get_host(api, self.options.host)
181                print(json.dumps(data, indent=2))
182
183            elif self.options.list:
184                data = self.get_list(api)
185                print(json.dumps(data, indent=2))
186
187            else:
188                print("usage: --list  ..OR.. --host <hostname>", file=sys.stderr)
189                sys.exit(1)
190
191        else:
192            print("Error: Configuration of server and credentials are required. See zabbix.ini.", file=sys.stderr)
193            sys.exit(1)
194
195
196ZabbixInventory()
197