1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# (c) 2013-2014, Epic Games, Inc.
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15
16DOCUMENTATION = r'''
17---
18module: zabbix_host
19short_description: Create/update/delete Zabbix hosts
20description:
21   - This module allows you to create, modify and delete Zabbix host entries and associated group and template data.
22version_added: "2.0"
23author:
24    - "Cove (@cove)"
25    - Tony Minfei Ding (!UNKNOWN)
26    - Harrison Gu (@harrisongu)
27    - Werner Dijkerman (@dj-wasabi)
28    - Eike Frost (@eikef)
29requirements:
30    - "python >= 2.6"
31    - "zabbix-api >= 0.5.4"
32options:
33    host_name:
34        description:
35            - Name of the host in Zabbix.
36            - I(host_name) is the unique identifier used and cannot be updated using this module.
37        required: true
38        type: str
39    visible_name:
40        description:
41            - Visible name of the host in Zabbix.
42        version_added: '2.3'
43        type: str
44    description:
45        description:
46            - Description of the host in Zabbix.
47        version_added: '2.5'
48        type: str
49    host_groups:
50        description:
51            - List of host groups the host is part of.
52        type: list
53        elements: str
54    link_templates:
55        description:
56            - List of templates linked to the host.
57        type: list
58        elements: str
59    inventory_mode:
60        description:
61            - Configure the inventory mode.
62        choices: ['automatic', 'manual', 'disabled']
63        version_added: '2.1'
64        type: str
65    inventory_zabbix:
66        description:
67            - Add Facts for a zabbix inventory (e.g. Tag) (see example below).
68            - Please review the interface documentation for more information on the supported properties
69            - U(https://www.zabbix.com/documentation/3.2/manual/api/reference/host/object#host_inventory)
70        version_added: '2.5'
71        type: dict
72    status:
73        description:
74            - Monitoring status of the host.
75        choices: ['enabled', 'disabled']
76        default: 'enabled'
77        type: str
78    state:
79        description:
80            - State of the host.
81            - On C(present), it will create if host does not exist or update the host if the associated data is different.
82            - On C(absent) will remove a host if it exists.
83        choices: ['present', 'absent']
84        default: 'present'
85        type: str
86    proxy:
87        description:
88            - The name of the Zabbix proxy to be used.
89        type: str
90    interfaces:
91        type: list
92        elements: dict
93        description:
94            - List of interfaces to be created for the host (see example below).
95            - For more information, review host interface documentation at
96            - U(https://www.zabbix.com/documentation/4.0/manual/api/reference/hostinterface/object)
97        suboptions:
98            type:
99                description:
100                    - Interface type to add
101                    - Numerical values are also accepted for interface type
102                    - 1 = agent
103                    - 2 = snmp
104                    - 3 = ipmi
105                    - 4 = jmx
106                choices: ['agent', 'snmp', 'ipmi', 'jmx']
107                required: true
108            main:
109                type: int
110                description:
111                    - Whether the interface is used as default.
112                    - If multiple interfaces with the same type are provided, only one can be default.
113                    - 0 (not default), 1 (default)
114                default: 0
115                choices: [0, 1]
116            useip:
117                type: int
118                description:
119                    - Connect to host interface with IP address instead of DNS name.
120                    - 0 (don't use ip), 1 (use ip)
121                default: 0
122                choices: [0, 1]
123            ip:
124                type: str
125                description:
126                    - IP address used by host interface.
127                    - Required if I(useip=1).
128                default: ''
129            dns:
130                type: str
131                description:
132                    - DNS name of the host interface.
133                    - Required if I(useip=0).
134                default: ''
135            port:
136                type: str
137                description:
138                    - Port used by host interface.
139                    - If not specified, default port for each type of interface is used
140                    - 10050 if I(type='agent')
141                    - 161 if I(type='snmp')
142                    - 623 if I(type='ipmi')
143                    - 12345 if I(type='jmx')
144            bulk:
145                type: int
146                description:
147                    - Whether to use bulk SNMP requests.
148                    - 0 (don't use bulk requests), 1 (use bulk requests)
149                choices: [0, 1]
150                default: 1
151        default: []
152    tls_connect:
153        description:
154            - Specifies what encryption to use for outgoing connections.
155            - Possible values, 1 (no encryption), 2 (PSK), 4 (certificate).
156            - Works only with >= Zabbix 3.0
157        default: 1
158        version_added: '2.5'
159        type: int
160    tls_accept:
161        description:
162            - Specifies what types of connections are allowed for incoming connections.
163            - The tls_accept parameter accepts values of 1 to 7
164            - Possible values, 1 (no encryption), 2 (PSK), 4 (certificate).
165            - Values can be combined.
166            - Works only with >= Zabbix 3.0
167        default: 1
168        version_added: '2.5'
169        type: int
170    tls_psk_identity:
171        description:
172            - It is a unique name by which this specific PSK is referred to by Zabbix components
173            - Do not put sensitive information in the PSK identity string, it is transmitted over the network unencrypted.
174            - Works only with >= Zabbix 3.0
175        version_added: '2.5'
176        type: str
177    tls_psk:
178        description:
179            - PSK value is a hard to guess string of hexadecimal digits.
180            - The preshared key, at least 32 hex digits. Required if either I(tls_connect) or I(tls_accept) has PSK enabled.
181            - Works only with >= Zabbix 3.0
182        version_added: '2.5'
183        type: str
184    ca_cert:
185        description:
186            - Required certificate issuer.
187            - Works only with >= Zabbix 3.0
188        version_added: '2.5'
189        aliases: [ tls_issuer ]
190        type: str
191    tls_subject:
192        description:
193            - Required certificate subject.
194            - Works only with >= Zabbix 3.0
195        version_added: '2.5'
196        type: str
197    ipmi_authtype:
198        description:
199            - IPMI authentication algorithm.
200            - Please review the Host object documentation for more information on the supported properties
201            - 'https://www.zabbix.com/documentation/3.4/manual/api/reference/host/object'
202            - Possible values are, C(0) (none), C(1) (MD2), C(2) (MD5), C(4) (straight), C(5) (OEM), C(6) (RMCP+),
203              with -1 being the API default.
204            - Please note that the Zabbix API will treat absent settings as default when updating
205              any of the I(ipmi_)-options; this means that if you attempt to set any of the four
206              options individually, the rest will be reset to default values.
207        version_added: '2.5'
208        type: int
209    ipmi_privilege:
210        description:
211            - IPMI privilege level.
212            - Please review the Host object documentation for more information on the supported properties
213            - 'https://www.zabbix.com/documentation/3.4/manual/api/reference/host/object'
214            - Possible values are C(1) (callback), C(2) (user), C(3) (operator), C(4) (admin), C(5) (OEM), with C(2)
215              being the API default.
216            - also see the last note in the I(ipmi_authtype) documentation
217        version_added: '2.5'
218        type: int
219    ipmi_username:
220        description:
221            - IPMI username.
222            - also see the last note in the I(ipmi_authtype) documentation
223        version_added: '2.5'
224        type: str
225    ipmi_password:
226        description:
227            - IPMI password.
228            - also see the last note in the I(ipmi_authtype) documentation
229        version_added: '2.5'
230        type: str
231    force:
232        description:
233            - Overwrite the host configuration, even if already present.
234        type: bool
235        default: 'yes'
236        version_added: '2.0'
237extends_documentation_fragment:
238    - zabbix
239'''
240
241EXAMPLES = r'''
242- name: Create a new host or update an existing host's info
243  local_action:
244    module: zabbix_host
245    server_url: http://monitor.example.com
246    login_user: username
247    login_password: password
248    host_name: ExampleHost
249    visible_name: ExampleName
250    description: My ExampleHost Description
251    host_groups:
252      - Example group1
253      - Example group2
254    link_templates:
255      - Example template1
256      - Example template2
257    status: enabled
258    state: present
259    inventory_mode: manual
260    inventory_zabbix:
261      tag: "{{ your_tag }}"
262      alias: "{{ your_alias }}"
263      notes: "Special Informations: {{ your_informations | default('None') }}"
264      location: "{{ your_location }}"
265      site_rack: "{{ your_site_rack }}"
266      os: "{{ your_os }}"
267      hardware: "{{ your_hardware }}"
268    ipmi_authtype: 2
269    ipmi_privilege: 4
270    ipmi_username: username
271    ipmi_password: password
272    interfaces:
273      - type: 1
274        main: 1
275        useip: 1
276        ip: 10.xx.xx.xx
277        dns: ""
278        port: 10050
279      - type: 4
280        main: 1
281        useip: 1
282        ip: 10.xx.xx.xx
283        dns: ""
284        port: 12345
285    proxy: a.zabbix.proxy
286- name: Update an existing host's TLS settings
287  local_action:
288    module: zabbix_host
289    server_url: http://monitor.example.com
290    login_user: username
291    login_password: password
292    host_name: ExampleHost
293    visible_name: ExampleName
294    host_groups:
295      - Example group1
296    tls_psk_identity: test
297    tls_connect: 2
298    tls_psk: 123456789abcdef123456789abcdef12
299'''
300
301
302import atexit
303import copy
304import traceback
305
306try:
307    from zabbix_api import ZabbixAPI
308    HAS_ZABBIX_API = True
309except ImportError:
310    ZBX_IMP_ERR = traceback.format_exc()
311    HAS_ZABBIX_API = False
312
313from distutils.version import LooseVersion
314from ansible.module_utils.basic import AnsibleModule, missing_required_lib
315
316
317class Host(object):
318    def __init__(self, module, zbx):
319        self._module = module
320        self._zapi = zbx
321        self._zbx_api_version = zbx.api_version()[:5]
322
323    # exist host
324    def is_host_exist(self, host_name):
325        result = self._zapi.host.get({'filter': {'host': host_name}})
326        return result
327
328    # check if host group exists
329    def check_host_group_exist(self, group_names):
330        for group_name in group_names:
331            result = self._zapi.hostgroup.get({'filter': {'name': group_name}})
332            if not result:
333                self._module.fail_json(msg="Hostgroup not found: %s" % group_name)
334        return True
335
336    def get_template_ids(self, template_list):
337        template_ids = []
338        if template_list is None or len(template_list) == 0:
339            return template_ids
340        for template in template_list:
341            template_list = self._zapi.template.get({'output': 'extend', 'filter': {'host': template}})
342            if len(template_list) < 1:
343                self._module.fail_json(msg="Template not found: %s" % template)
344            else:
345                template_id = template_list[0]['templateid']
346                template_ids.append(template_id)
347        return template_ids
348
349    def add_host(self, host_name, group_ids, status, interfaces, proxy_id, visible_name, description, tls_connect,
350                 tls_accept, tls_psk_identity, tls_psk, tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege,
351                 ipmi_username, ipmi_password):
352        try:
353            if self._module.check_mode:
354                self._module.exit_json(changed=True)
355            parameters = {'host': host_name, 'interfaces': interfaces, 'groups': group_ids, 'status': status,
356                          'tls_connect': tls_connect, 'tls_accept': tls_accept}
357            if proxy_id:
358                parameters['proxy_hostid'] = proxy_id
359            if visible_name:
360                parameters['name'] = visible_name
361            if tls_psk_identity is not None:
362                parameters['tls_psk_identity'] = tls_psk_identity
363            if tls_psk is not None:
364                parameters['tls_psk'] = tls_psk
365            if tls_issuer is not None:
366                parameters['tls_issuer'] = tls_issuer
367            if tls_subject is not None:
368                parameters['tls_subject'] = tls_subject
369            if description:
370                parameters['description'] = description
371            if ipmi_authtype is not None:
372                parameters['ipmi_authtype'] = ipmi_authtype
373            if ipmi_privilege is not None:
374                parameters['ipmi_privilege'] = ipmi_privilege
375            if ipmi_username is not None:
376                parameters['ipmi_username'] = ipmi_username
377            if ipmi_password is not None:
378                parameters['ipmi_password'] = ipmi_password
379
380            host_list = self._zapi.host.create(parameters)
381            if len(host_list) >= 1:
382                return host_list['hostids'][0]
383        except Exception as e:
384            self._module.fail_json(msg="Failed to create host %s: %s" % (host_name, e))
385
386    def update_host(self, host_name, group_ids, status, host_id, interfaces, exist_interface_list, proxy_id,
387                    visible_name, description, tls_connect, tls_accept, tls_psk_identity, tls_psk, tls_issuer, tls_subject, ipmi_authtype,
388                    ipmi_privilege, ipmi_username, ipmi_password):
389        try:
390            if self._module.check_mode:
391                self._module.exit_json(changed=True)
392            parameters = {'hostid': host_id, 'groups': group_ids, 'status': status, 'tls_connect': tls_connect,
393                          'tls_accept': tls_accept}
394            if proxy_id >= 0:
395                parameters['proxy_hostid'] = proxy_id
396            if visible_name:
397                parameters['name'] = visible_name
398            if tls_psk_identity:
399                parameters['tls_psk_identity'] = tls_psk_identity
400            if tls_psk:
401                parameters['tls_psk'] = tls_psk
402            if tls_issuer:
403                parameters['tls_issuer'] = tls_issuer
404            if tls_subject:
405                parameters['tls_subject'] = tls_subject
406            if description:
407                parameters['description'] = description
408            if ipmi_authtype:
409                parameters['ipmi_authtype'] = ipmi_authtype
410            if ipmi_privilege:
411                parameters['ipmi_privilege'] = ipmi_privilege
412            if ipmi_username:
413                parameters['ipmi_username'] = ipmi_username
414            if ipmi_password:
415                parameters['ipmi_password'] = ipmi_password
416
417            self._zapi.host.update(parameters)
418            interface_list_copy = exist_interface_list
419            if interfaces:
420                for interface in interfaces:
421                    flag = False
422                    interface_str = interface
423                    for exist_interface in exist_interface_list:
424                        interface_type = int(interface['type'])
425                        exist_interface_type = int(exist_interface['type'])
426                        if interface_type == exist_interface_type:
427                            # update
428                            interface_str['interfaceid'] = exist_interface['interfaceid']
429                            self._zapi.hostinterface.update(interface_str)
430                            flag = True
431                            interface_list_copy.remove(exist_interface)
432                            break
433                    if not flag:
434                        # add
435                        interface_str['hostid'] = host_id
436                        self._zapi.hostinterface.create(interface_str)
437                        # remove
438                remove_interface_ids = []
439                for remove_interface in interface_list_copy:
440                    interface_id = remove_interface['interfaceid']
441                    remove_interface_ids.append(interface_id)
442                if len(remove_interface_ids) > 0:
443                    self._zapi.hostinterface.delete(remove_interface_ids)
444        except Exception as e:
445            self._module.fail_json(msg="Failed to update host %s: %s" % (host_name, e))
446
447    def delete_host(self, host_id, host_name):
448        try:
449            if self._module.check_mode:
450                self._module.exit_json(changed=True)
451            self._zapi.host.delete([host_id])
452        except Exception as e:
453            self._module.fail_json(msg="Failed to delete host %s: %s" % (host_name, e))
454
455    # get host by host name
456    def get_host_by_host_name(self, host_name):
457        host_list = self._zapi.host.get({'output': 'extend', 'selectInventory': 'extend', 'filter': {'host': [host_name]}})
458        if len(host_list) < 1:
459            self._module.fail_json(msg="Host not found: %s" % host_name)
460        else:
461            return host_list[0]
462
463    # get proxyid by proxy name
464    def get_proxyid_by_proxy_name(self, proxy_name):
465        proxy_list = self._zapi.proxy.get({'output': 'extend', 'filter': {'host': [proxy_name]}})
466        if len(proxy_list) < 1:
467            self._module.fail_json(msg="Proxy not found: %s" % proxy_name)
468        else:
469            return int(proxy_list[0]['proxyid'])
470
471    # get group ids by group names
472    def get_group_ids_by_group_names(self, group_names):
473        group_ids = []
474        if self.check_host_group_exist(group_names):
475            group_list = self._zapi.hostgroup.get({'output': 'extend', 'filter': {'name': group_names}})
476            for group in group_list:
477                group_id = group['groupid']
478                group_ids.append({'groupid': group_id})
479        return group_ids
480
481    # get host templates by host id
482    def get_host_templates_by_host_id(self, host_id):
483        template_ids = []
484        template_list = self._zapi.template.get({'output': 'extend', 'hostids': host_id})
485        for template in template_list:
486            template_ids.append(template['templateid'])
487        return template_ids
488
489    # get host groups by host id
490    def get_host_groups_by_host_id(self, host_id):
491        exist_host_groups = []
492        host_groups_list = self._zapi.hostgroup.get({'output': 'extend', 'hostids': host_id})
493
494        if len(host_groups_list) >= 1:
495            for host_groups_name in host_groups_list:
496                exist_host_groups.append(host_groups_name['name'])
497        return exist_host_groups
498
499    # check the exist_interfaces whether it equals the interfaces or not
500    def check_interface_properties(self, exist_interface_list, interfaces):
501        interfaces_port_list = []
502
503        if interfaces is not None:
504            if len(interfaces) >= 1:
505                for interface in interfaces:
506                    interfaces_port_list.append(int(interface['port']))
507
508        exist_interface_ports = []
509        if len(exist_interface_list) >= 1:
510            for exist_interface in exist_interface_list:
511                exist_interface_ports.append(int(exist_interface['port']))
512
513        if set(interfaces_port_list) != set(exist_interface_ports):
514            return True
515
516        for exist_interface in exist_interface_list:
517            exit_interface_port = int(exist_interface['port'])
518            for interface in interfaces:
519                interface_port = int(interface['port'])
520                if interface_port == exit_interface_port:
521                    for key in interface.keys():
522                        if str(exist_interface[key]) != str(interface[key]):
523                            return True
524
525        return False
526
527    # get the status of host by host
528    def get_host_status_by_host(self, host):
529        return host['status']
530
531    # check all the properties before link or clear template
532    def check_all_properties(self, host_id, host_groups, status, interfaces, template_ids,
533                             exist_interfaces, host, proxy_id, visible_name, description, host_name,
534                             inventory_mode, inventory_zabbix, tls_accept, tls_psk_identity, tls_psk,
535                             tls_issuer, tls_subject, tls_connect, ipmi_authtype, ipmi_privilege,
536                             ipmi_username, ipmi_password):
537        # get the existing host's groups
538        exist_host_groups = self.get_host_groups_by_host_id(host_id)
539        if set(host_groups) != set(exist_host_groups):
540            return True
541
542        # get the existing status
543        exist_status = self.get_host_status_by_host(host)
544        if int(status) != int(exist_status):
545            return True
546
547        # check the exist_interfaces whether it equals the interfaces or not
548        if self.check_interface_properties(exist_interfaces, interfaces):
549            return True
550
551        # get the existing templates
552        exist_template_ids = self.get_host_templates_by_host_id(host_id)
553        if set(list(template_ids)) != set(exist_template_ids):
554            return True
555
556        if int(host['proxy_hostid']) != int(proxy_id):
557            return True
558
559        # Check whether the visible_name has changed; Zabbix defaults to the technical hostname if not set.
560        if visible_name:
561            if host['name'] != visible_name and host['name'] != host_name:
562                return True
563
564        # Only compare description if it is given as a module parameter
565        if description:
566            if host['description'] != description:
567                return True
568
569        if inventory_mode:
570            if LooseVersion(self._zbx_api_version) <= LooseVersion('4.4.0'):
571                if host['inventory']:
572                    if int(host['inventory']['inventory_mode']) != self.inventory_mode_numeric(inventory_mode):
573                        return True
574                elif inventory_mode != 'disabled':
575                    return True
576            else:
577                if int(host['inventory_mode']) != self.inventory_mode_numeric(inventory_mode):
578                    return True
579
580        if inventory_zabbix:
581            proposed_inventory = copy.deepcopy(host['inventory'])
582            proposed_inventory.update(inventory_zabbix)
583            if proposed_inventory != host['inventory']:
584                return True
585
586        if tls_accept is not None and 'tls_accept' in host:
587            if int(host['tls_accept']) != tls_accept:
588                return True
589
590        if tls_psk_identity is not None and 'tls_psk_identity' in host:
591            if host['tls_psk_identity'] != tls_psk_identity:
592                return True
593
594        if tls_psk is not None and 'tls_psk' in host:
595            if host['tls_psk'] != tls_psk:
596                return True
597
598        if tls_issuer is not None and 'tls_issuer' in host:
599            if host['tls_issuer'] != tls_issuer:
600                return True
601
602        if tls_subject is not None and 'tls_subject' in host:
603            if host['tls_subject'] != tls_subject:
604                return True
605
606        if tls_connect is not None and 'tls_connect' in host:
607            if int(host['tls_connect']) != tls_connect:
608                return True
609        if ipmi_authtype is not None:
610            if int(host['ipmi_authtype']) != ipmi_authtype:
611                return True
612        if ipmi_privilege is not None:
613            if int(host['ipmi_privilege']) != ipmi_privilege:
614                return True
615        if ipmi_username is not None:
616            if host['ipmi_username'] != ipmi_username:
617                return True
618        if ipmi_password is not None:
619            if host['ipmi_password'] != ipmi_password:
620                return True
621
622        return False
623
624    # link or clear template of the host
625    def link_or_clear_template(self, host_id, template_id_list, tls_connect, tls_accept, tls_psk_identity, tls_psk,
626                               tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege, ipmi_username, ipmi_password):
627        # get host's exist template ids
628        exist_template_id_list = self.get_host_templates_by_host_id(host_id)
629
630        exist_template_ids = set(exist_template_id_list)
631        template_ids = set(template_id_list)
632        template_id_list = list(template_ids)
633
634        # get unlink and clear templates
635        templates_clear = exist_template_ids.difference(template_ids)
636        templates_clear_list = list(templates_clear)
637        request_str = {'hostid': host_id, 'templates': template_id_list, 'templates_clear': templates_clear_list,
638                       'tls_connect': tls_connect, 'tls_accept': tls_accept, 'ipmi_authtype': ipmi_authtype,
639                       'ipmi_privilege': ipmi_privilege, 'ipmi_username': ipmi_username, 'ipmi_password': ipmi_password}
640        if tls_psk_identity is not None:
641            request_str['tls_psk_identity'] = tls_psk_identity
642        if tls_psk is not None:
643            request_str['tls_psk'] = tls_psk
644        if tls_issuer is not None:
645            request_str['tls_issuer'] = tls_issuer
646        if tls_subject is not None:
647            request_str['tls_subject'] = tls_subject
648        try:
649            if self._module.check_mode:
650                self._module.exit_json(changed=True)
651            self._zapi.host.update(request_str)
652        except Exception as e:
653            self._module.fail_json(msg="Failed to link template to host: %s" % e)
654
655    def inventory_mode_numeric(self, inventory_mode):
656        if inventory_mode == "automatic":
657            return int(1)
658        elif inventory_mode == "manual":
659            return int(0)
660        elif inventory_mode == "disabled":
661            return int(-1)
662        return inventory_mode
663
664    # Update the host inventory_mode
665    def update_inventory_mode(self, host_id, inventory_mode):
666
667        # nothing was set, do nothing
668        if not inventory_mode:
669            return
670
671        inventory_mode = self.inventory_mode_numeric(inventory_mode)
672
673        # watch for - https://support.zabbix.com/browse/ZBX-6033
674        request_str = {'hostid': host_id, 'inventory_mode': inventory_mode}
675        try:
676            if self._module.check_mode:
677                self._module.exit_json(changed=True)
678            self._zapi.host.update(request_str)
679        except Exception as e:
680            self._module.fail_json(msg="Failed to set inventory_mode to host: %s" % e)
681
682    def update_inventory_zabbix(self, host_id, inventory):
683
684        if not inventory:
685            return
686
687        request_str = {'hostid': host_id, 'inventory': inventory}
688        try:
689            if self._module.check_mode:
690                self._module.exit_json(changed=True)
691            self._zapi.host.update(request_str)
692        except Exception as e:
693            self._module.fail_json(msg="Failed to set inventory to host: %s" % e)
694
695
696def main():
697    module = AnsibleModule(
698        argument_spec=dict(
699            server_url=dict(type='str', required=True, aliases=['url']),
700            login_user=dict(type='str', required=True),
701            login_password=dict(type='str', required=True, no_log=True),
702            host_name=dict(type='str', required=True),
703            http_login_user=dict(type='str', required=False, default=None),
704            http_login_password=dict(type='str', required=False, default=None, no_log=True),
705            validate_certs=dict(type='bool', required=False, default=True),
706            host_groups=dict(type='list', required=False),
707            link_templates=dict(type='list', required=False),
708            status=dict(type='str', default="enabled", choices=['enabled', 'disabled']),
709            state=dict(type='str', default="present", choices=['present', 'absent']),
710            inventory_mode=dict(type='str', required=False, choices=['automatic', 'manual', 'disabled']),
711            ipmi_authtype=dict(type='int', default=None),
712            ipmi_privilege=dict(type='int', default=None),
713            ipmi_username=dict(type='str', required=False, default=None),
714            ipmi_password=dict(type='str', required=False, default=None, no_log=True),
715            tls_connect=dict(type='int', default=1),
716            tls_accept=dict(type='int', default=1),
717            tls_psk_identity=dict(type='str', required=False),
718            tls_psk=dict(type='str', required=False),
719            ca_cert=dict(type='str', required=False, aliases=['tls_issuer']),
720            tls_subject=dict(type='str', required=False),
721            inventory_zabbix=dict(type='dict', required=False),
722            timeout=dict(type='int', default=10),
723            interfaces=dict(type='list', required=False),
724            force=dict(type='bool', default=True),
725            proxy=dict(type='str', required=False),
726            visible_name=dict(type='str', required=False),
727            description=dict(type='str', required=False)
728        ),
729        supports_check_mode=True
730    )
731
732    if not HAS_ZABBIX_API:
733        module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR)
734
735    server_url = module.params['server_url']
736    login_user = module.params['login_user']
737    login_password = module.params['login_password']
738    http_login_user = module.params['http_login_user']
739    http_login_password = module.params['http_login_password']
740    validate_certs = module.params['validate_certs']
741    host_name = module.params['host_name']
742    visible_name = module.params['visible_name']
743    description = module.params['description']
744    host_groups = module.params['host_groups']
745    link_templates = module.params['link_templates']
746    inventory_mode = module.params['inventory_mode']
747    ipmi_authtype = module.params['ipmi_authtype']
748    ipmi_privilege = module.params['ipmi_privilege']
749    ipmi_username = module.params['ipmi_username']
750    ipmi_password = module.params['ipmi_password']
751    tls_connect = module.params['tls_connect']
752    tls_accept = module.params['tls_accept']
753    tls_psk_identity = module.params['tls_psk_identity']
754    tls_psk = module.params['tls_psk']
755    tls_issuer = module.params['ca_cert']
756    tls_subject = module.params['tls_subject']
757    inventory_zabbix = module.params['inventory_zabbix']
758    status = module.params['status']
759    state = module.params['state']
760    timeout = module.params['timeout']
761    interfaces = module.params['interfaces']
762    force = module.params['force']
763    proxy = module.params['proxy']
764
765    # convert enabled to 0; disabled to 1
766    status = 1 if status == "disabled" else 0
767
768    zbx = None
769    # login to zabbix
770    try:
771        zbx = ZabbixAPI(server_url, timeout=timeout, user=http_login_user, passwd=http_login_password,
772                        validate_certs=validate_certs)
773        zbx.login(login_user, login_password)
774        atexit.register(zbx.logout)
775    except Exception as e:
776        module.fail_json(msg="Failed to connect to Zabbix server: %s" % e)
777
778    host = Host(module, zbx)
779
780    template_ids = []
781    if link_templates:
782        template_ids = host.get_template_ids(link_templates)
783
784    group_ids = []
785
786    if host_groups:
787        group_ids = host.get_group_ids_by_group_names(host_groups)
788
789    ip = ""
790    if interfaces:
791        # ensure interfaces are well-formed
792        for interface in interfaces:
793            if 'type' not in interface:
794                module.fail_json(msg="(interface) type needs to be specified for interface '%s'." % interface)
795            interfacetypes = {'agent': 1, 'snmp': 2, 'ipmi': 3, 'jmx': 4}
796            if interface['type'] in interfacetypes.keys():
797                interface['type'] = interfacetypes[interface['type']]
798            if interface['type'] < 1 or interface['type'] > 4:
799                module.fail_json(msg="Interface type can only be 1-4 for interface '%s'." % interface)
800            if 'useip' not in interface:
801                interface['useip'] = 0
802            if 'dns' not in interface:
803                if interface['useip'] == 0:
804                    module.fail_json(msg="dns needs to be set if useip is 0 on interface '%s'." % interface)
805                interface['dns'] = ''
806            if 'ip' not in interface:
807                if interface['useip'] == 1:
808                    module.fail_json(msg="ip needs to be set if useip is 1 on interface '%s'." % interface)
809                interface['ip'] = ''
810            if 'main' not in interface:
811                interface['main'] = 0
812            if 'port' not in interface:
813                if interface['type'] == 1:
814                    interface['port'] = "10050"
815                elif interface['type'] == 2:
816                    interface['port'] = "161"
817                elif interface['type'] == 3:
818                    interface['port'] = "623"
819                elif interface['type'] == 4:
820                    interface['port'] = "12345"
821
822            if interface['type'] == 1:
823                ip = interface['ip']
824
825    # Use proxy specified, or set to 0
826    if proxy:
827        proxy_id = host.get_proxyid_by_proxy_name(proxy)
828    else:
829        proxy_id = 0
830
831    # check if host exist
832    is_host_exist = host.is_host_exist(host_name)
833
834    if is_host_exist:
835        # get host id by host name
836        zabbix_host_obj = host.get_host_by_host_name(host_name)
837        host_id = zabbix_host_obj['hostid']
838
839        # If proxy is not specified as a module parameter, use the existing setting
840        if proxy is None:
841            proxy_id = int(zabbix_host_obj['proxy_hostid'])
842
843        if state == "absent":
844            # remove host
845            host.delete_host(host_id, host_name)
846            module.exit_json(changed=True, result="Successfully delete host %s" % host_name)
847        else:
848            if not host_groups:
849                # if host_groups have not been specified when updating an existing host, just
850                # get the group_ids from the existing host without updating them.
851                host_groups = host.get_host_groups_by_host_id(host_id)
852                group_ids = host.get_group_ids_by_group_names(host_groups)
853
854            # get existing host's interfaces
855            exist_interfaces = host._zapi.hostinterface.get({'output': 'extend', 'hostids': host_id})
856
857            # if no interfaces were specified with the module, start with an empty list
858            if not interfaces:
859                interfaces = []
860
861            # When force=no is specified, append existing interfaces to interfaces to update. When
862            # no interfaces have been specified, copy existing interfaces as specified from the API.
863            # Do the same with templates and host groups.
864            if not force or not interfaces:
865                for interface in copy.deepcopy(exist_interfaces):
866                    # remove values not used during hostinterface.add/update calls
867                    for key in tuple(interface.keys()):
868                        if key in ['interfaceid', 'hostid', 'bulk']:
869                            interface.pop(key, None)
870
871                    for index in interface.keys():
872                        if index in ['useip', 'main', 'type', 'port']:
873                            interface[index] = int(interface[index])
874
875                    if interface not in interfaces:
876                        interfaces.append(interface)
877
878            if not force or link_templates is None:
879                template_ids = list(set(template_ids + host.get_host_templates_by_host_id(host_id)))
880
881            if not force:
882                for group_id in host.get_group_ids_by_group_names(host.get_host_groups_by_host_id(host_id)):
883                    if group_id not in group_ids:
884                        group_ids.append(group_id)
885
886            # update host
887            if host.check_all_properties(host_id, host_groups, status, interfaces, template_ids,
888                                         exist_interfaces, zabbix_host_obj, proxy_id, visible_name,
889                                         description, host_name, inventory_mode, inventory_zabbix,
890                                         tls_accept, tls_psk_identity, tls_psk, tls_issuer, tls_subject, tls_connect,
891                                         ipmi_authtype, ipmi_privilege, ipmi_username, ipmi_password):
892                host.update_host(host_name, group_ids, status, host_id,
893                                 interfaces, exist_interfaces, proxy_id, visible_name, description, tls_connect, tls_accept,
894                                 tls_psk_identity, tls_psk, tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege, ipmi_username, ipmi_password)
895                host.link_or_clear_template(host_id, template_ids, tls_connect, tls_accept, tls_psk_identity,
896                                            tls_psk, tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege,
897                                            ipmi_username, ipmi_password)
898                host.update_inventory_mode(host_id, inventory_mode)
899                host.update_inventory_zabbix(host_id, inventory_zabbix)
900
901                module.exit_json(changed=True,
902                                 result="Successfully update host %s (%s) and linked with template '%s'"
903                                        % (host_name, ip, link_templates))
904            else:
905                module.exit_json(changed=False)
906
907    else:
908        if state == "absent":
909            # the host is already deleted.
910            module.exit_json(changed=False)
911
912        if not group_ids:
913            module.fail_json(msg="Specify at least one group for creating host '%s'." % host_name)
914
915        if not interfaces or (interfaces and len(interfaces) == 0):
916            module.fail_json(msg="Specify at least one interface for creating host '%s'." % host_name)
917
918        # create host
919        host_id = host.add_host(host_name, group_ids, status, interfaces, proxy_id, visible_name, description, tls_connect,
920                                tls_accept, tls_psk_identity, tls_psk, tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege,
921                                ipmi_username, ipmi_password)
922        host.link_or_clear_template(host_id, template_ids, tls_connect, tls_accept, tls_psk_identity,
923                                    tls_psk, tls_issuer, tls_subject, ipmi_authtype, ipmi_privilege, ipmi_username, ipmi_password)
924        host.update_inventory_mode(host_id, inventory_mode)
925        host.update_inventory_zabbix(host_id, inventory_zabbix)
926        module.exit_json(changed=True, result="Successfully added host %s (%s) and linked with template '%s'" % (
927            host_name, ip, link_templates))
928
929
930if __name__ == '__main__':
931    main()
932