├── vars
└── main.yml
├── handlers
└── main.yml
├── defaults
└── main.yml
├── meta
└── main.yml
├── .gitignore
├── tasks
└── main.yml
├── README.md
├── LICENSE
└── library
└── zabbix_api
/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # vars file for ansible-zabbix-api
3 |
--------------------------------------------------------------------------------
/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # handlers file for ansible-zabbix-api
3 |
--------------------------------------------------------------------------------
/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | zabbix_default_group: ansible
4 | zabbix_templates: ''
5 | zabbix_groups: ''
6 | zabbix_macros: ''
7 | zabbix_url: ''
8 | zabbix_user: ''
9 | zabbix_password: ''
10 | zabbix_basic_auth_user: ''
11 | zabbix_basic_auth_password: ''
12 |
--------------------------------------------------------------------------------
/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | galaxy_info:
3 | author: Robert Jerzak
4 | description: Synchronizes hosts state (groups, templates, macros) with Zabbix.
5 | license: MIT
6 | min_ansible_version: 1.4
7 | platforms:
8 | - name: EL
9 | versions:
10 | - 5
11 | - 6
12 | - 7
13 | - name: Ubuntu
14 | versions:
15 | - precise
16 | - quantal
17 | - raring
18 | - saucy
19 | - trusty
20 | - wily
21 | - name: Debian
22 | versions:
23 | - wheezy
24 | galaxy_tags:
25 | - monitoring
26 | - zabbix
27 | dependencies: []
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 |
31 | # Installer logs
32 | pip-log.txt
33 | pip-delete-this-directory.txt
34 |
35 | # Unit test / coverage reports
36 | htmlcov/
37 | .tox/
38 | .coverage
39 | .cache
40 | nosetests.xml
41 | coverage.xml
42 |
43 | # Translations
44 | *.mo
45 | *.pot
46 |
47 | # Django stuff:
48 | *.log
49 |
50 | # Sphinx documentation
51 | docs/_build/
52 |
53 | # PyBuilder
54 | target/
55 |
--------------------------------------------------------------------------------
/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: install python-requests (debian)
4 | apt: name=python-requests state=present
5 | become: yes
6 | when: ansible_os_family == "Debian"
7 |
8 | - name: install python-requests (redhat)
9 | yum: name=python-requests state=present
10 | become: yes
11 | when: ansible_os_family == "RedHat"
12 |
13 | - name: sync host with zabbix
14 | zabbix_api:
15 | action=sync_host
16 | hostname={{ inventory_hostname }}
17 | ipv4={{ ansible_default_ipv4.address }}
18 | default_group={{ zabbix_default_group }}
19 | groups="{{ zabbix_groups }}"
20 | templates="{{ zabbix_templates }}"
21 | macros="{{ zabbix_macros }}"
22 | api_url={{ zabbix_url }}
23 | api_user={{ zabbix_user }}
24 | api_password={{ zabbix_password }}
25 | api_basic_auth_user={{ zabbix_basic_auth_user }}
26 | api_basic_auth_password={{ zabbix_basic_auth_password }}
27 |
28 | - name: disable invalid zabbix hosts
29 | zabbix_api:
30 | action=disable_invalid
31 | default_group={{ zabbix_default_group }}
32 | all_hosts="{{ groups.all|join(',') }}"
33 | api_url={{ zabbix_url }}
34 | api_user={{ zabbix_user }}
35 | api_password={{ zabbix_password }}
36 | api_basic_auth_user={{ zabbix_basic_auth_user }}
37 | api_basic_auth_password={{ zabbix_basic_auth_password }}
38 | run_once: yes
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ansible Zabbix-API
2 | ==================
3 |
4 | This module will let you synchronize ansible hosts state with Zabbix. It means that it sets hosts via Zabbix API based on ansible variables:
5 |
6 | - groups the host should belong to;
7 | - templates the host should be linked to;
8 | - macros the host should contain.
9 |
10 | Groups and Tempalates provided by ansible variables MUST be already present in Zabbix.
11 |
12 | Web access from nodes managed by ansible to the Zabbix API should be allowed (firewall).
13 |
14 | Make sure API user for provided credentials has enough permissions for host groups in Zabbix.
15 |
16 | You can treat ansible inventory as an authoritative source of host information and sync it to Zabbix.
17 |
18 | Tested on Zabbix 3.0.1.
19 |
20 | Requirements
21 | ============
22 |
23 | None.
24 |
25 | Role Variables
26 | ==============
27 |
28 | Available variables are listed below, along with default values (see `defaults/main.yml`):
29 |
30 | - `zabbix_default_group` - Default Zabbix group all hosts should belong to. Only hosts from this group are considered during synchronization (default "ansible").
31 | - `zabbix_templates` - Comma separated list of Zabbix templates the host should be linked to.
32 | - `zabbix_groups` - Comma separated list of Zabbix groups the host should belong to.
33 | - `zabbix_macros` - Comma separated list of Zabbix macros which should be added to the host. Odd items are macro keys, even items are macro values, eg.: '{$MACRO1},value1,{$MACRO2},value2'.
34 | - `zabbix_url` - Zabbix API URL.
35 | - `zabbix_user` and `zabbix_password` - Zabbix API credentials.
36 | - `zabbix_basic_auth_user` and `zabbix_basic_auth_password` - Optional basic AUTH credentials if required.
37 |
38 | Example
39 | =======
40 |
41 | Variables
42 | ---------
43 | ```
44 | zabbix_url: 'http://zabbixhost.com/api_jsonrpc.php'
45 | zabbix_user: 'ansible'
46 | zabbix_password: 'secretpassword'
47 |
48 | zabbix_templates: 'mysql,nginx,php-fpm,LXC Containers,Web Test'
49 | zabbix_groups: 'LXC Containers,Webservers'
50 | zabbix_macros: '{$PAGE_STRING},Welcome on main page,{$SNMP_COMMUNITY},public'
51 | ```
52 | Playbook
53 | --------
54 |
55 | ```yaml
56 | - name: zabbix API
57 | hosts: all
58 | roles:
59 | - role: r0bj.zabbix-api
60 | ```
61 |
62 | License
63 | =======
64 | MIT
65 |
66 | Author Information
67 | ==================
68 | This role was created in 2014 by Robert Jerzak.
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/library/zabbix_api:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # Copyright (c) 2015 Robert Jerzak
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 .
17 |
18 | import sys
19 | import json
20 | import re
21 | import requests
22 | import signal
23 | import shlex
24 | import itertools
25 |
26 | connection_timeout = 5
27 | verify_ssl = False
28 |
29 | class ZabbixAPIError(Exception):
30 | pass
31 |
32 | class Alarm(Exception):
33 | pass
34 |
35 | def alarm_handler(signum, frame):
36 | raise Alarm
37 |
38 | class ZabbixConnection:
39 | def __init__(self, url, user, passwd, basicauth_user, basicauth_pass):
40 | if url and user and passwd:
41 | self.__api_url = url
42 | self.__api_user = user
43 | self.__api_pass = passwd
44 | self.__api_basicauth_user = basicauth_user
45 | self.__api_basicauth_pass = basicauth_pass
46 | self.__api_token = self.__auth()
47 | else:
48 | raise ZabbixAPIError('No valid credentials')
49 |
50 | def __callAPI(self, json_data):
51 | headers = {
52 | 'Content-Type': 'application/json-rpc',
53 | 'User-Agent': 'ZabbixAPI Client',
54 | }
55 |
56 | signal.signal(signal.SIGALRM, alarm_handler)
57 | signal.alarm(connection_timeout)
58 | try:
59 | if self.__api_basicauth_user and self.__api_basicauth_pass:
60 | r = requests.post(
61 | self.__api_url,
62 | data=json_data,
63 | headers=headers,
64 | timeout=connection_timeout,
65 | verify=verify_ssl,
66 | auth=(self.__api_basicauth_user, self.__api_basicauth_pass))
67 | else:
68 | r = requests.post(
69 | self.__api_url,
70 | data=json_data,
71 | headers=headers,
72 | timeout=connection_timeout,
73 | verify=verify_ssl)
74 | signal.alarm(0)
75 | except requests.exceptions.ConnectionError:
76 | raise ZabbixAPIError('HTTP connection error, json: %s' % self.__blind_password(json_data))
77 | except Alarm:
78 | raise ZabbixAPIError('HTTP connection timeout, json: %s' % self.__blind_password(json_data))
79 |
80 | if r.status_code == 200 and r.content != '':
81 | try:
82 | c = json.loads(r.content)
83 | except ValueError, e:
84 | raise ZabbixAPIError(e)
85 | if 'error' in c:
86 | raise ZabbixAPIError('API error: code: %s message: %s data: %s'
87 | %(str(c['error']['code']), str(c['error']['message']), str(c['error']['data'])))
88 | elif 'result' in c:
89 | return c['result']
90 | else:
91 | raise ZabbixAPIError('Wrong API result, json: %s' % self.__blind_password(json_data))
92 | else:
93 | raise ZabbixAPIError('Wrong API response content: HTTP code %s, json: %s'
94 | %(r.status_code, self.__blind_password(json_data)))
95 |
96 | def __auth(self):
97 | req = {
98 | 'jsonrpc': '2.0',
99 | 'method': 'user.login',
100 | 'params': {
101 | 'user': self.__api_user,
102 | 'password': self.__api_pass,
103 | },
104 | 'id': '0',
105 | }
106 |
107 | token = self.__callAPI(json.dumps(req))
108 | if token != '':
109 | return token
110 | else:
111 | raise ZabbixAPIError('Wrong API auth token')
112 |
113 | def __blind_password(self, json_data):
114 | dict = json.loads(json_data)
115 | if 'params' in dict and 'password' in dict['params']:
116 | dict['params']['password'] = 'XXX'
117 | return json.dumps(dict)
118 |
119 | def send(self, data):
120 | data['auth'] = self.__api_token
121 | data['jsonrpc'] = '2.0'
122 | data['id'] = '0'
123 | return self.__callAPI(json.dumps(data))
124 |
125 | class ZabbixAPI:
126 | def __init__(self, url, user, passwd, basicauth_user, basicauth_pass):
127 | self.__zconn = ZabbixConnection(url, user, passwd, basicauth_user, basicauth_pass)
128 | self.__changed = []
129 |
130 | def get_changed(self):
131 | return self.__changed
132 |
133 | def __find_hostgroupid(self, hostgroup):
134 | req = {
135 | 'method': 'hostgroup.get',
136 | 'params': {
137 | 'output': [
138 | 'groupid'
139 | ],
140 | 'filter': {
141 | 'name': [
142 | hostgroup
143 | ],
144 | },
145 | },
146 | }
147 |
148 | result = self.__zconn.send(req)
149 | try:
150 | return result[0]['groupid']
151 | except (KeyError, IndexError):
152 | raise ZabbixAPIError('Error: cannot find hostgroupid for hostgroup: %s' % hostgroup)
153 |
154 | def __find_hostid(self, hostname):
155 | req = {
156 | 'method': 'host.get',
157 | 'params': {
158 | 'output': [
159 | 'hostid'
160 | ],
161 | 'filter': {
162 | 'name': [
163 | hostname
164 | ],
165 | },
166 | },
167 | }
168 |
169 | result = self.__zconn.send(req)
170 | try:
171 | return result[0]['hostid']
172 | except (KeyError, IndexError):
173 | raise ZabbixAPIError('Error: cannot find hostnameid for hostname: %s' % hostname)
174 |
175 | def __find_templateid(self, template):
176 | req = {
177 | 'method': 'template.get',
178 | 'params': {
179 | 'output': [
180 | 'templateid'
181 | ],
182 | 'filter': {
183 | 'host': [
184 | template
185 | ],
186 | },
187 | },
188 | }
189 |
190 | result = self.__zconn.send(req)
191 | try:
192 | return result[0]['templateid']
193 | except (KeyError, IndexError):
194 | raise ZabbixAPIError('Error: cannot find templateid for template: %s' % template)
195 |
196 | def __get_host_if_exists(self, hostname):
197 | req = {
198 | 'method': 'host.get',
199 | 'params': {
200 | 'output': 'extend',
201 | 'selectInterfaces': 'extend',
202 | 'selectMacros': 'extend',
203 | 'selectGroups': 'extend',
204 | 'selectParentTemplates': 'extend',
205 | 'filter': {
206 | 'host': [
207 | hostname
208 | ],
209 | },
210 | },
211 | }
212 |
213 | result = self.__zconn.send(req)
214 | try:
215 | return result[0]
216 | except (KeyError, IndexError):
217 | return False
218 |
219 | def __update_interface_ip(self, interfaceid, ip):
220 | req = {
221 | 'method': 'hostinterface.update',
222 | 'params': {
223 | 'interfaceid': interfaceid,
224 | 'ip': ip,
225 | },
226 | }
227 |
228 | result = self.__zconn.send(req)
229 | try:
230 | return result['interfaceids'][0]
231 | except (KeyError, IndexError):
232 | raise ZabbixAPIError('Error: cannot update interface id: %s' % interfaceid)
233 |
234 | def __update_host_groups(self, hostid, hostgroups):
235 | req = {
236 | 'method': 'host.update',
237 | 'params': {
238 | 'hostid': hostid,
239 | 'groups': self.__get_hostgroupids(hostgroups),
240 | },
241 | }
242 |
243 | result = self.__zconn.send(req)
244 | try:
245 | return result['hostids'][0]
246 | except (KeyError, IndexError):
247 | raise ZabbixAPIError('Error: cannot update host id: %s' % hostid)
248 |
249 | def __remove_host(self, hostid):
250 | req = {
251 | 'method': 'host.delete',
252 | 'params': [
253 | hostid,
254 | ],
255 | }
256 |
257 | result = self.__zconn.send(req)
258 | try:
259 | return result['hostids'][0]
260 | except (KeyError, IndexError):
261 | raise ZabbixAPIError('Error: cannot remove host id: %s' % hostid)
262 |
263 | def __update_host_template_clear(self, hostid, templateid):
264 | req = {
265 | 'method': 'host.update',
266 | 'params': {
267 | 'hostid': hostid,
268 | 'templates_clear': {
269 | 'templateid': templateid,
270 | },
271 | },
272 | }
273 |
274 | result = self.__zconn.send(req)
275 | try:
276 | return result['hostids'][0]
277 | except (KeyError, IndexError):
278 | raise ZabbixAPIError('Error: cannot update host id: %s' % hostid)
279 |
280 | def __update_host_status(self, hostid, status):
281 | req = {
282 | 'method': 'host.update',
283 | 'params': {
284 | 'hostid': hostid,
285 | 'status': status,
286 | },
287 | }
288 |
289 | result = self.__zconn.send(req)
290 | try:
291 | return result['hostids'][0]
292 | except (KeyError, IndexError):
293 | raise ZabbixAPIError('Error: cannot update host id: %s' % hostid)
294 |
295 | def __update_host_templates(self, hostid, templates):
296 | req = {
297 | 'method': 'host.update',
298 | 'params': {
299 | 'hostid': hostid,
300 | 'templates': self.__get_templateids(templates),
301 | },
302 | }
303 |
304 | result = self.__zconn.send(req)
305 | try:
306 | return result['hostids'][0]
307 | except (KeyError, IndexError):
308 | raise ZabbixAPIError('Error: cannot update host id: %s' % hostid)
309 |
310 | def __compare_ip(self, hostdump, ip):
311 | for interface in hostdump['interfaces']:
312 | if interface['main'] == '1' and interface['type'] == '1':
313 | if interface['ip'] != ip:
314 | self.__update_interface_ip(interface['interfaceid'], ip)
315 | self.__changed.append('set ip: %s' %ip)
316 |
317 | def __compare_hostgroups(self, hostdump, hostgroups):
318 | dump_groups = []
319 | update = False
320 | for dump_group in hostdump['groups']:
321 | dump_groups.append(dump_group['name'])
322 | if dump_group['name'] not in hostgroups:
323 | self.__update_host_groups(hostdump['hostid'], hostgroups)
324 | self.__changed.append('add to group(s): %s' %(', '.join(hostgroups)))
325 | update = True
326 | break
327 | if not update:
328 | for hostgroup in hostgroups:
329 | if hostgroup not in dump_groups:
330 | self.__update_host_groups(hostdump['hostid'], hostgroups)
331 | self.__changed.append('add to group(s): %s' %(', '.join(hostgroups)))
332 | break
333 |
334 | def __compare_templates(self, hostdump, templates):
335 | dump_templates = []
336 | for dump_template in hostdump['parentTemplates']:
337 | if dump_template['name'] not in templates:
338 | self.__update_host_template_clear(hostdump['hostid'], dump_template['templateid'])
339 | self.__changed.append('unlink and clear template: %s' %dump_template['name'])
340 | else:
341 | dump_templates.append(dump_template['name'])
342 |
343 | for template in templates:
344 | if template not in dump_templates:
345 | self.__update_host_templates(hostdump['hostid'], templates)
346 | self.__changed.append('link to template(s): %s' %(', ').join(templates))
347 | break
348 |
349 | def __list_to_dict(self, list):
350 | i = iter(list)
351 | return dict(itertools.izip(i, i))
352 |
353 | def __validate_macro_name(self, macros):
354 | for key, value in macros.items():
355 | if not re.match(r'^\{\$[A-Z0-9_]+\}$', key):
356 | raise ZabbixAPIError('Error: macro %s validation failed, allowed characters are A-Z, 0-9, _' % key)
357 |
358 | def __compare_macros(self, hostdump, macros_list):
359 | macros = self.__list_to_dict(macros_list)
360 | self.__validate_macro_name(macros)
361 | keyvalues = {}
362 | for key, value in macros.items():
363 | keyvalues[key + value] = { 'macro': key, 'value': value }
364 |
365 | dump_keyvalues = []
366 | for dump_macro in hostdump['macros']:
367 | if dump_macro['macro'] + dump_macro['value'] not in keyvalues:
368 | self.__delete_macro(dump_macro['hostmacroid'])
369 | self.__changed.append('delete macro: %s' %dump_macro['macro'])
370 | else:
371 | dump_keyvalues.append(dump_macro['macro'] + dump_macro['value'])
372 |
373 | for keyvalue in keyvalues:
374 | if keyvalue not in dump_keyvalues:
375 | self.__create_macro(hostdump['hostid'], keyvalues[keyvalue]['macro'], keyvalues[keyvalue]['value'])
376 | self.__changed.append('add macro: %s' %keyvalues[keyvalue]['macro'])
377 |
378 | def __validate_list(self, list):
379 | list_validated = []
380 | for item in list:
381 | if item:
382 | list_validated.append(item)
383 | return list_validated
384 |
385 | def sync_host(self, hostname, ip, hostgroups, macros_list, templates):
386 | hostdump = self.__get_host_if_exists(hostname)
387 | hostgroups = self.__validate_list(hostgroups)
388 | templates = self.__validate_list(templates)
389 |
390 | if hostdump:
391 | self.__compare_ip(hostdump, ip)
392 | self.__compare_hostgroups(hostdump, hostgroups)
393 | self.__compare_templates(hostdump, templates)
394 | self.__compare_macros(hostdump, macros_list)
395 | if hostdump['status'] != '0':
396 | self.__update_host_status(hostdump['hostid'], 0)
397 | self.__changed.append('enable host')
398 | else:
399 | hostid = self.__create_host(hostname, ip, hostgroups, templates)
400 | self.__changed.append('create host')
401 | macros = self.__list_to_dict(macros_list)
402 | for macro, value in macros.items():
403 | self.__create_macro(hostid, macro, value)
404 |
405 | def __get_hostgroupids(self, hostgroups):
406 | hostgroupids = []
407 | for hostgroup in hostgroups:
408 | hostgroupids.append({'groupid': self.__find_hostgroupid(hostgroup)})
409 | return hostgroupids
410 |
411 | def __get_templateids(self, templates):
412 | templateids = []
413 | for template in templates:
414 | templateids.append({'templateid': self.__find_templateid(template)})
415 | return templateids
416 |
417 | def __create_host(self, hostname, ip, hostgroups, templates):
418 | req = {
419 | 'method': 'host.create',
420 | 'params': {
421 | 'host': hostname,
422 | 'interfaces': {
423 | 'type': 1,
424 | 'main': 1,
425 | 'useip': 1,
426 | 'ip': ip,
427 | 'dns': '',
428 | 'port': '10050',
429 | },
430 | 'groups': self.__get_hostgroupids(hostgroups),
431 | 'templates': self.__get_templateids(templates),
432 | },
433 | }
434 |
435 | result = self.__zconn.send(req)
436 | try:
437 | return result['hostids'][0]
438 | except (KeyError, IndexError):
439 | raise ZabbixAPIError('Error: cannot create host: %s' % hostname)
440 |
441 | def __create_macro(self, hostid, macro, value):
442 | req = {
443 | 'method': 'usermacro.create',
444 | 'params': {
445 | 'hostid': hostid,
446 | 'macro': macro,
447 | 'value': value,
448 | },
449 | }
450 |
451 | result = self.__zconn.send(req)
452 | try:
453 | return result['hostmacroids'][0]
454 | except (KeyError, IndexError):
455 | raise ZabbixAPIError('Error: cannot create macro for hostname: %s' % hostname)
456 |
457 | def __update_macro(self, macroid, macro, value):
458 | req = {
459 | 'method': 'usermacro.update',
460 | 'params': {
461 | 'hostid': hostid,
462 | 'macro': macro,
463 | 'value': value,
464 | },
465 | }
466 |
467 | result = self.__zconn.send(req)
468 | try:
469 | return result['hostmacroids'][0]
470 | except (KeyError, IndexError):
471 | raise ZabbixAPIError('Error: cannot update macro id: %s' % macroid)
472 |
473 | def __delete_macro(self, macroid):
474 | req = {
475 | 'method': 'usermacro.delete',
476 | 'params': [
477 | macroid,
478 | ],
479 | }
480 |
481 | result = self.__zconn.send(req)
482 | try:
483 | return result['hostmacroids'][0]
484 | except (KeyError, IndexError):
485 | raise ZabbixAPIError('Error: cannot delete macro id: %s' % macroid)
486 |
487 | def __get_hostgroup_hosts(self, hostgroup):
488 | req = {
489 | 'method': 'hostgroup.get',
490 | 'params': {
491 | 'output': [
492 | 'groupid',
493 | ],
494 | 'selectHosts': [
495 | 'host',
496 | 'hostid',
497 | 'status',
498 | ],
499 | 'filter': {
500 | 'name': [
501 | hostgroup
502 | ],
503 | },
504 | },
505 | }
506 |
507 | result = self.__zconn.send(req)
508 | return result
509 |
510 | def disable_invalid_hosts(self, default_hostgroup, all_hosts):
511 | result = self.__get_hostgroup_hosts(default_hostgroup)
512 | for group in result:
513 | for host in group['hosts']:
514 | if host['host'] not in all_hosts and host['status'] == '0':
515 | self.__update_host_status(host['hostid'], 1)
516 | self.__changed.append('disable host: %s' % host['host'])
517 |
518 | def remove_invalid_hosts(self, default_hostgroup, all_hosts):
519 | result = self.__get_hostgroup_hosts(default_hostgroup)
520 | for group in result:
521 | for host in group['hosts']:
522 | if host['host'] not in all_hosts:
523 | self.__remove_host(host['hostid'])
524 | self.__changed.append('remove host: %s' % host['host'])
525 |
526 | def main():
527 | module = AnsibleModule(
528 | argument_spec=dict(
529 | action=dict(default='sync_host', choices=['sync_host', 'disable_invalid']),
530 | hostname=dict(default=None),
531 | ipv4=dict(default=None),
532 | default_group=dict(required=True, default=None),
533 | groups=dict(default=None),
534 | templates=dict(default=None),
535 | macros=dict(default=None),
536 | all_hosts=dict(default=None),
537 | remove=dict(default=False, type='bool'),
538 | api_url=dict(required=True, default=None),
539 | api_user=dict(required=True, default=None),
540 | api_password=dict(required=True, default=None),
541 | api_basic_auth_user=dict(default=''),
542 | api_basic_auth_password=dict(default=''),
543 | ),
544 | supports_check_mode=False,
545 | )
546 |
547 | try:
548 | module.params['api_url']
549 | module.params['api_user']
550 | module.params['api_password']
551 | except NameError:
552 | module.fail_json(msg='missing required API parameter(s)')
553 |
554 | if module.params['action'] == 'sync_host':
555 | try:
556 | module.params['hostname']
557 | module.params['ipv4']
558 | module.params['templates']
559 | module.params['groups']
560 | module.params['default_group']
561 | module.params['macros']
562 | except NameError:
563 | module.fail_json(msg='missing parameter(s) for action %s' % module.params['action'])
564 |
565 | macros = module.params['macros'].split(',')
566 | groups = module.params['groups'].split(',')
567 | templates = module.params['templates'].split(',')
568 | groups.append(module.params['default_group'])
569 | try:
570 | zapi = ZabbixAPI(
571 | module.params['api_url'],
572 | module.params['api_user'],
573 | module.params['api_password'],
574 | module.params['api_basic_auth_user'],
575 | module.params['api_basic_auth_password'],
576 | )
577 | zapi.sync_host(
578 | module.params['hostname'],
579 | module.params['ipv4'],
580 | groups,
581 | macros,
582 | templates,
583 | )
584 | except ZabbixAPIError, e:
585 | module.fail_json(msg=str(e))
586 |
587 | elif module.params['action'] == 'disable_invalid':
588 | try:
589 | module.params['all_hosts']
590 | module.params['default_group']
591 | except NameError:
592 | module.fail_json(msg='missing parameter(s) for action %s' % module.params['action'])
593 | all_hosts = module.params['all_hosts'].split(',')
594 | try:
595 | zapi = ZabbixAPI(
596 | module.params['api_url'],
597 | module.params['api_user'],
598 | module.params['api_password'],
599 | module.params['api_basic_auth_user'],
600 | module.params['api_basic_auth_password'],
601 | )
602 | if module.params['remove']:
603 | zapi.remove_invalid_hosts(module.params['default_group'], all_hosts)
604 | else:
605 | zapi.disable_invalid_hosts(module.params['default_group'], all_hosts)
606 | except ZabbixAPIError, e:
607 | module.fail_json(msg=str(e))
608 | else:
609 | module.fail_json(msg='wrong action parameter')
610 |
611 | if zapi.get_changed():
612 | module.exit_json(changed=True, msg='; '.join(zapi.get_changed()))
613 | else:
614 | module.exit_json(changed=False, msg='success')
615 |
616 | from ansible.module_utils.basic import *
617 | main()
618 |
--------------------------------------------------------------------------------