1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = { 12 'metadata_version': '1.1', 13 'status': ['preview'], 14 'supported_by': 'community' 15} 16 17DOCUMENTATION = ''' 18--- 19module: zabbix_action 20 21short_description: Create/Delete/Update Zabbix actions 22 23version_added: "2.8" 24 25description: 26 - This module allows you to create, modify and delete Zabbix actions. 27 28author: 29 - Ruben Tsirunyan (@rubentsirunyan) 30 - Ruben Harutyunov (@K-DOT) 31 32requirements: 33 - "zabbix-api >= 0.5.4" 34 35options: 36 name: 37 description: 38 - Name of the action 39 required: true 40 event_source: 41 description: 42 - Type of events that the action will handle. 43 - Required when C(state=present). 44 required: false 45 choices: ['trigger', 'discovery', 'auto_registration', 'internal'] 46 state: 47 description: 48 - State of the action. 49 - On C(present), it will create an action if it does not exist or update the action if the associated data is different. 50 - On C(absent), it will remove the action if it exists. 51 choices: ['present', 'absent'] 52 default: 'present' 53 status: 54 description: 55 - Status of the action. 56 choices: ['enabled', 'disabled'] 57 default: 'enabled' 58 pause_in_maintenance: 59 description: 60 - Whether to pause escalation during maintenance periods or not. 61 - Can be used when I(event_source=trigger). 62 type: 'bool' 63 default: true 64 esc_period: 65 description: 66 - Default operation step duration. Must be greater than 60 seconds. Accepts seconds, time unit with suffix and user macro. 67 - Required when C(state=present). 68 required: false 69 conditions: 70 type: list 71 description: 72 - List of dictionaries of conditions to evaluate. 73 - For more information about suboptions of this option please 74 check out Zabbix API documentation U(https://www.zabbix.com/documentation/3.4/manual/api/reference/action/object#action_filter_condition) 75 suboptions: 76 type: 77 description: Type (label) of the condition. 78 choices: 79 # trigger 80 - host_group 81 - host 82 - trigger 83 - trigger_name 84 - trigger_severity 85 - time_period 86 - host_template 87 - application 88 - maintenance_status 89 - event_tag 90 - event_tag_value 91 # discovery 92 - host_IP 93 - discovered_service_type 94 - discovered_service_port 95 - discovery_status 96 - uptime_or_downtime_duration 97 - received_value 98 - discovery_rule 99 - discovery_check 100 - proxy 101 - discovery_object 102 # auto_registration 103 - proxy 104 - host_name 105 - host_metadata 106 # internal 107 - host_group 108 - host 109 - host_template 110 - application 111 - event_type 112 value: 113 description: 114 - Value to compare with. 115 - When I(type) is set to C(discovery_status), the choices 116 are C(up), C(down), C(discovered), C(lost). 117 - When I(type) is set to C(discovery_object), the choices 118 are C(host), C(service). 119 - When I(type) is set to C(event_type), the choices 120 are C(item in not supported state), C(item in normal state), 121 C(LLD rule in not supported state), 122 C(LLD rule in normal state), C(trigger in unknown state), C(trigger in normal state). 123 - When I(type) is set to C(trigger_severity), the choices 124 are (case-insensitive) C(not classified), C(information), C(warning), C(average), C(high), C(disaster) 125 irrespective of user-visible names being changed in Zabbix. Defaults to C(not classified) if omitted. 126 - Besides the above options, this is usually either the name 127 of the object or a string to compare with. 128 operator: 129 description: 130 - Condition operator. 131 - When I(type) is set to C(time_period), the choices are C(in), C(not in). 132 - C(matches), C(does not match), C(Yes) and C(No) condition operators work only with >= Zabbix 4.0 133 choices: 134 - '=' 135 - '<>' 136 - 'like' 137 - 'not like' 138 - 'in' 139 - '>=' 140 - '<=' 141 - 'not in' 142 - 'matches' 143 - 'does not match' 144 - 'Yes' 145 - 'No' 146 formulaid: 147 description: 148 - Arbitrary unique ID that is used to reference the condition from a custom expression. 149 - Can only contain upper-case letters. 150 - Required for custom expression filters. 151 eval_type: 152 description: 153 - Filter condition evaluation method. 154 - Defaults to C(andor) if conditions are less then 2 or if 155 I(formula) is not specified. 156 - Defaults to C(custom_expression) when formula is specified. 157 choices: 158 - 'andor' 159 - 'and' 160 - 'or' 161 - 'custom_expression' 162 formula: 163 description: 164 - User-defined expression to be used for evaluating conditions of filters with a custom expression. 165 - The expression must contain IDs that reference specific filter conditions by its formulaid. 166 - The IDs used in the expression must exactly match the ones 167 defined in the filter conditions. No condition can remain unused or omitted. 168 - Required for custom expression filters. 169 - Use sequential IDs that start at "A". If non-sequential IDs are used, Zabbix re-indexes them. 170 This makes each module run notice the difference in IDs and update the action. 171 default_message: 172 description: 173 - Problem message default text. 174 default_subject: 175 description: 176 - Problem message default subject. 177 recovery_default_message: 178 description: 179 - Recovery message text. 180 - Works only with >= Zabbix 3.2 181 recovery_default_subject: 182 description: 183 - Recovery message subject. 184 - Works only with >= Zabbix 3.2 185 acknowledge_default_message: 186 description: 187 - Update operation (known as "Acknowledge operation" before Zabbix 4.0) message text. 188 - Works only with >= Zabbix 3.4 189 acknowledge_default_subject: 190 description: 191 - Update operation (known as "Acknowledge operation" before Zabbix 4.0) message subject. 192 - Works only with >= Zabbix 3.4 193 operations: 194 type: list 195 description: 196 - List of action operations 197 suboptions: 198 type: 199 description: 200 - Type of operation. 201 choices: 202 - send_message 203 - remote_command 204 - add_host 205 - remove_host 206 - add_to_host_group 207 - remove_from_host_group 208 - link_to_template 209 - unlink_from_template 210 - enable_host 211 - disable_host 212 - set_host_inventory_mode 213 esc_period: 214 description: 215 - Duration of an escalation step in seconds. 216 - Must be greater than 60 seconds. 217 - Accepts seconds, time unit with suffix and user macro. 218 - If set to 0 or 0s, the default action escalation period will be used. 219 default: 0s 220 esc_step_from: 221 description: 222 - Step to start escalation from. 223 default: 1 224 esc_step_to: 225 description: 226 - Step to end escalation at. 227 default: 1 228 send_to_groups: 229 type: list 230 description: 231 - User groups to send messages to. 232 send_to_users: 233 type: list 234 description: 235 - Users (usernames or aliases) to send messages to. 236 message: 237 description: 238 - Operation message text. 239 - Will check the 'default message' and use the text from I(default_message) if this and I(default_subject) are not specified 240 subject: 241 description: 242 - Operation message subject. 243 - Will check the 'default message' and use the text from I(default_subject) if this and I(default_subject) are not specified 244 media_type: 245 description: 246 - Media type that will be used to send the message. 247 - Set to C(all) for all media types 248 default: 'all' 249 operation_condition: 250 type: 'str' 251 description: 252 - The action operation condition object defines a condition that must be met to perform the current operation. 253 choices: 254 - acknowledged 255 - not_acknowledged 256 host_groups: 257 type: list 258 description: 259 - List of host groups host should be added to. 260 - Required when I(type=add_to_host_group) or I(type=remove_from_host_group). 261 templates: 262 type: list 263 description: 264 - List of templates host should be linked to. 265 - Required when I(type=link_to_template) or I(type=unlink_from_template). 266 inventory: 267 description: 268 - Host inventory mode. 269 - Required when I(type=set_host_inventory_mode). 270 command_type: 271 description: 272 - Type of operation command. 273 - Required when I(type=remote_command). 274 choices: 275 - custom_script 276 - ipmi 277 - ssh 278 - telnet 279 - global_script 280 command: 281 description: 282 - Command to run. 283 - Required when I(type=remote_command) and I(command_type!=global_script). 284 execute_on: 285 description: 286 - Target on which the custom script operation command will be executed. 287 - Required when I(type=remote_command) and I(command_type=custom_script). 288 choices: 289 - agent 290 - server 291 - proxy 292 run_on_groups: 293 description: 294 - Host groups to run remote commands on. 295 - Required when I(type=remote_command) if I(run_on_hosts) is not set. 296 run_on_hosts: 297 description: 298 - Hosts to run remote commands on. 299 - Required when I(type=remote_command) if I(run_on_groups) is not set. 300 - If set to 0 the command will be run on the current host. 301 ssh_auth_type: 302 description: 303 - Authentication method used for SSH commands. 304 - Required when I(type=remote_command) and I(command_type=ssh). 305 choices: 306 - password 307 - public_key 308 ssh_privatekey_file: 309 description: 310 - Name of the private key file used for SSH commands with public key authentication. 311 - Required when I(type=remote_command) and I(command_type=ssh). 312 ssh_publickey_file: 313 description: 314 - Name of the public key file used for SSH commands with public key authentication. 315 - Required when I(type=remote_command) and I(command_type=ssh). 316 username: 317 description: 318 - User name used for authentication. 319 - Required when I(type=remote_command) and I(command_type in [ssh, telnet]). 320 password: 321 description: 322 - Password used for authentication. 323 - Required when I(type=remote_command) and I(command_type in [ssh, telnet]). 324 port: 325 description: 326 - Port number used for authentication. 327 - Required when I(type=remote_command) and I(command_type in [ssh, telnet]). 328 script_name: 329 description: 330 - The name of script used for global script commands. 331 - Required when I(type=remote_command) and I(command_type=global_script). 332 recovery_operations: 333 type: list 334 description: 335 - List of recovery operations. 336 - C(Suboptions) are the same as for I(operations). 337 - Works only with >= Zabbix 3.2 338 acknowledge_operations: 339 type: list 340 description: 341 - List of acknowledge operations. 342 - C(Suboptions) are the same as for I(operations). 343 - Works only with >= Zabbix 3.4 344 345notes: 346 - Only Zabbix >= 3.0 is supported. 347 348 349extends_documentation_fragment: 350 - zabbix 351''' 352 353EXAMPLES = ''' 354# Trigger action with only one condition 355- name: Deploy trigger action 356 zabbix_action: 357 server_url: "http://zabbix.example.com/zabbix/" 358 login_user: Admin 359 login_password: secret 360 name: "Send alerts to Admin" 361 event_source: 'trigger' 362 state: present 363 status: enabled 364 esc_period: 60 365 conditions: 366 - type: 'trigger_severity' 367 operator: '>=' 368 value: 'Information' 369 operations: 370 - type: send_message 371 subject: "Something bad is happening" 372 message: "Come on, guys do something" 373 media_type: 'Email' 374 send_to_users: 375 - 'Admin' 376 377# Trigger action with multiple conditions and operations 378- name: Deploy trigger action 379 zabbix_action: 380 server_url: "http://zabbix.example.com/zabbix/" 381 login_user: Admin 382 login_password: secret 383 name: "Send alerts to Admin" 384 event_source: 'trigger' 385 state: present 386 status: enabled 387 esc_period: 60 388 conditions: 389 - type: 'trigger_name' 390 operator: 'like' 391 value: 'Zabbix agent is unreachable' 392 formulaid: A 393 - type: 'trigger_severity' 394 operator: '>=' 395 value: 'disaster' 396 formulaid: B 397 formula: A or B 398 operations: 399 - type: send_message 400 media_type: 'Email' 401 send_to_users: 402 - 'Admin' 403 - type: remote_command 404 command: 'systemctl restart zabbix-agent' 405 command_type: custom_script 406 execute_on: server 407 run_on_hosts: 408 - 0 409 410# Trigger action with recovery and acknowledge operations 411- name: Deploy trigger action 412 zabbix_action: 413 server_url: "http://zabbix.example.com/zabbix/" 414 login_user: Admin 415 login_password: secret 416 name: "Send alerts to Admin" 417 event_source: 'trigger' 418 state: present 419 status: enabled 420 esc_period: 60 421 conditions: 422 - type: 'trigger_severity' 423 operator: '>=' 424 value: 'Information' 425 operations: 426 - type: send_message 427 subject: "Something bad is happening" 428 message: "Come on, guys do something" 429 media_type: 'Email' 430 send_to_users: 431 - 'Admin' 432 recovery_operations: 433 - type: send_message 434 subject: "Host is down" 435 message: "Come on, guys do something" 436 media_type: 'Email' 437 send_to_users: 438 - 'Admin' 439 acknowledge_operations: 440 - type: send_message 441 media_type: 'Email' 442 send_to_users: 443 - 'Admin' 444''' 445 446RETURN = ''' 447msg: 448 description: The result of the operation 449 returned: success 450 type: str 451 sample: 'Action Deleted: Register webservers, ID: 0001' 452''' 453 454 455import atexit 456import traceback 457 458try: 459 from zabbix_api import ZabbixAPI 460 HAS_ZABBIX_API = True 461except ImportError: 462 ZBX_IMP_ERR = traceback.format_exc() 463 HAS_ZABBIX_API = False 464 465from ansible.module_utils.basic import AnsibleModule, missing_required_lib 466 467 468class Zapi(object): 469 """ 470 A simple wrapper over the Zabbix API 471 """ 472 def __init__(self, module, zbx): 473 self._module = module 474 self._zapi = zbx 475 476 def check_if_action_exists(self, name): 477 """Check if action exists. 478 479 Args: 480 name: Name of the action. 481 482 Returns: 483 The return value. True for success, False otherwise. 484 485 """ 486 try: 487 _action = self._zapi.action.get({ 488 "selectOperations": "extend", 489 "selectRecoveryOperations": "extend", 490 "selectAcknowledgeOperations": "extend", 491 "selectFilter": "extend", 492 'selectInventory': 'extend', 493 'filter': {'name': [name]} 494 }) 495 if len(_action) > 0: 496 _action[0]['recovery_operations'] = _action[0].pop('recoveryOperations', []) 497 _action[0]['acknowledge_operations'] = _action[0].pop('acknowledgeOperations', []) 498 return _action 499 except Exception as e: 500 self._module.fail_json(msg="Failed to check if action '%s' exists: %s" % (name, e)) 501 502 def get_action_by_name(self, name): 503 """Get action by name 504 505 Args: 506 name: Name of the action. 507 508 Returns: 509 dict: Zabbix action 510 511 """ 512 try: 513 action_list = self._zapi.action.get({ 514 'output': 'extend', 515 'selectInventory': 'extend', 516 'filter': {'name': [name]} 517 }) 518 if len(action_list) < 1: 519 self._module.fail_json(msg="Action not found: " % name) 520 else: 521 return action_list[0] 522 except Exception as e: 523 self._module.fail_json(msg="Failed to get ID of '%s': %s" % (name, e)) 524 525 def get_host_by_host_name(self, host_name): 526 """Get host by host name 527 528 Args: 529 host_name: host name. 530 531 Returns: 532 host matching host name 533 534 """ 535 try: 536 host_list = self._zapi.host.get({ 537 'output': 'extend', 538 'selectInventory': 'extend', 539 'filter': {'host': [host_name]} 540 }) 541 if len(host_list) < 1: 542 self._module.fail_json(msg="Host not found: %s" % host_name) 543 else: 544 return host_list[0] 545 except Exception as e: 546 self._module.fail_json(msg="Failed to get host '%s': %s" % (host_name, e)) 547 548 def get_hostgroup_by_hostgroup_name(self, hostgroup_name): 549 """Get host group by host group name 550 551 Args: 552 hostgroup_name: host group name. 553 554 Returns: 555 host group matching host group name 556 557 """ 558 try: 559 hostgroup_list = self._zapi.hostgroup.get({ 560 'output': 'extend', 561 'selectInventory': 'extend', 562 'filter': {'name': [hostgroup_name]} 563 }) 564 if len(hostgroup_list) < 1: 565 self._module.fail_json(msg="Host group not found: %s" % hostgroup_name) 566 else: 567 return hostgroup_list[0] 568 except Exception as e: 569 self._module.fail_json(msg="Failed to get host group '%s': %s" % (hostgroup_name, e)) 570 571 def get_template_by_template_name(self, template_name): 572 """Get template by template name 573 574 Args: 575 template_name: template name. 576 577 Returns: 578 template matching template name 579 580 """ 581 try: 582 template_list = self._zapi.template.get({ 583 'output': 'extend', 584 'selectInventory': 'extend', 585 'filter': {'host': [template_name]} 586 }) 587 if len(template_list) < 1: 588 self._module.fail_json(msg="Template not found: %s" % template_name) 589 else: 590 return template_list[0] 591 except Exception as e: 592 self._module.fail_json(msg="Failed to get template '%s': %s" % (template_name, e)) 593 594 def get_trigger_by_trigger_name(self, trigger_name): 595 """Get trigger by trigger name 596 597 Args: 598 trigger_name: trigger name. 599 600 Returns: 601 trigger matching trigger name 602 603 """ 604 try: 605 trigger_list = self._zapi.trigger.get({ 606 'output': 'extend', 607 'selectInventory': 'extend', 608 'filter': {'description': [trigger_name]} 609 }) 610 if len(trigger_list) < 1: 611 self._module.fail_json(msg="Trigger not found: %s" % trigger_name) 612 else: 613 return trigger_list[0] 614 except Exception as e: 615 self._module.fail_json(msg="Failed to get trigger '%s': %s" % (trigger_name, e)) 616 617 def get_discovery_rule_by_discovery_rule_name(self, discovery_rule_name): 618 """Get discovery rule by discovery rule name 619 620 Args: 621 discovery_rule_name: discovery rule name. 622 623 Returns: 624 discovery rule matching discovery rule name 625 626 """ 627 try: 628 discovery_rule_list = self._zapi.drule.get({ 629 'output': 'extend', 630 'selectInventory': 'extend', 631 'filter': {'name': [discovery_rule_name]} 632 }) 633 if len(discovery_rule_list) < 1: 634 self._module.fail_json(msg="Discovery rule not found: %s" % discovery_rule_name) 635 else: 636 return discovery_rule_list[0] 637 except Exception as e: 638 self._module.fail_json(msg="Failed to get discovery rule '%s': %s" % (discovery_rule_name, e)) 639 640 def get_discovery_check_by_discovery_check_name(self, discovery_check_name): 641 """Get discovery check by discovery check name 642 643 Args: 644 discovery_check_name: discovery check name. 645 646 Returns: 647 discovery check matching discovery check name 648 649 """ 650 try: 651 discovery_check_list = self._zapi.dcheck.get({ 652 'output': 'extend', 653 'selectInventory': 'extend', 654 'filter': {'name': [discovery_check_name]} 655 }) 656 if len(discovery_check_list) < 1: 657 self._module.fail_json(msg="Discovery check not found: %s" % discovery_check_name) 658 else: 659 return discovery_check_list[0] 660 except Exception as e: 661 self._module.fail_json(msg="Failed to get discovery check '%s': %s" % (discovery_check_name, e)) 662 663 def get_proxy_by_proxy_name(self, proxy_name): 664 """Get proxy by proxy name 665 666 Args: 667 proxy_name: proxy name. 668 669 Returns: 670 proxy matching proxy name 671 672 """ 673 try: 674 proxy_list = self._zapi.proxy.get({ 675 'output': 'extend', 676 'selectInventory': 'extend', 677 'filter': {'host': [proxy_name]} 678 }) 679 if len(proxy_list) < 1: 680 self._module.fail_json(msg="Proxy not found: %s" % proxy_name) 681 else: 682 return proxy_list[0] 683 except Exception as e: 684 self._module.fail_json(msg="Failed to get proxy '%s': %s" % (proxy_name, e)) 685 686 def get_mediatype_by_mediatype_name(self, mediatype_name): 687 """Get mediatype by mediatype name 688 689 Args: 690 mediatype_name: mediatype name 691 692 Returns: 693 mediatype matching mediatype name 694 695 """ 696 try: 697 if str(mediatype_name).lower() == 'all': 698 return '0' 699 mediatype_list = self._zapi.mediatype.get({ 700 'output': 'extend', 701 'selectInventory': 'extend', 702 'filter': {'description': [mediatype_name]} 703 }) 704 if len(mediatype_list) < 1: 705 self._module.fail_json(msg="Media type not found: %s" % mediatype_name) 706 else: 707 return mediatype_list[0]['mediatypeid'] 708 except Exception as e: 709 self._module.fail_json(msg="Failed to get mediatype '%s': %s" % (mediatype_name, e)) 710 711 def get_user_by_user_name(self, user_name): 712 """Get user by user name 713 714 Args: 715 user_name: user name 716 717 Returns: 718 user matching user name 719 720 """ 721 try: 722 user_list = self._zapi.user.get({ 723 'output': 'extend', 724 'selectInventory': 725 'extend', 'filter': {'alias': [user_name]} 726 }) 727 if len(user_list) < 1: 728 self._module.fail_json(msg="User not found: %s" % user_name) 729 else: 730 return user_list[0] 731 except Exception as e: 732 self._module.fail_json(msg="Failed to get user '%s': %s" % (user_name, e)) 733 734 def get_usergroup_by_usergroup_name(self, usergroup_name): 735 """Get usergroup by usergroup name 736 737 Args: 738 usergroup_name: usergroup name 739 740 Returns: 741 usergroup matching usergroup name 742 743 """ 744 try: 745 usergroup_list = self._zapi.usergroup.get({ 746 'output': 'extend', 747 'selectInventory': 'extend', 748 'filter': {'name': [usergroup_name]} 749 }) 750 if len(usergroup_list) < 1: 751 self._module.fail_json(msg="User group not found: %s" % usergroup_name) 752 else: 753 return usergroup_list[0] 754 except Exception as e: 755 self._module.fail_json(msg="Failed to get user group '%s': %s" % (usergroup_name, e)) 756 757 # get script by script name 758 def get_script_by_script_name(self, script_name): 759 """Get script by script name 760 761 Args: 762 script_name: script name 763 764 Returns: 765 script matching script name 766 767 """ 768 try: 769 if script_name is None: 770 return {} 771 script_list = self._zapi.script.get({ 772 'output': 'extend', 773 'selectInventory': 'extend', 774 'filter': {'name': [script_name]} 775 }) 776 if len(script_list) < 1: 777 self._module.fail_json(msg="Script not found: %s" % script_name) 778 else: 779 return script_list[0] 780 except Exception as e: 781 self._module.fail_json(msg="Failed to get script '%s': %s" % (script_name, e)) 782 783 784class Action(object): 785 """ 786 Restructures the user defined action data to fit the Zabbix API requirements 787 """ 788 def __init__(self, module, zbx, zapi_wrapper): 789 self._module = module 790 self._zapi = zbx 791 self._zapi_wrapper = zapi_wrapper 792 793 def _construct_parameters(self, **kwargs): 794 """Construct parameters. 795 796 Args: 797 **kwargs: Arbitrary keyword parameters. 798 799 Returns: 800 dict: dictionary of specified parameters 801 """ 802 803 _params = { 804 'name': kwargs['name'], 805 'eventsource': to_numeric_value([ 806 'trigger', 807 'discovery', 808 'auto_registration', 809 'internal'], kwargs['event_source']), 810 'esc_period': kwargs.get('esc_period'), 811 'filter': kwargs['conditions'], 812 'def_longdata': kwargs['default_message'], 813 'def_shortdata': kwargs['default_subject'], 814 'r_longdata': kwargs['recovery_default_message'], 815 'r_shortdata': kwargs['recovery_default_subject'], 816 'ack_longdata': kwargs['acknowledge_default_message'], 817 'ack_shortdata': kwargs['acknowledge_default_subject'], 818 'operations': kwargs['operations'], 819 'recovery_operations': kwargs.get('recovery_operations'), 820 'acknowledge_operations': kwargs.get('acknowledge_operations'), 821 'status': to_numeric_value([ 822 'enabled', 823 'disabled'], kwargs['status']) 824 } 825 if kwargs['event_source'] == 'trigger': 826 if float(self._zapi.api_version().rsplit('.', 1)[0]) >= 4.0: 827 _params['pause_suppressed'] = '1' if kwargs['pause_in_maintenance'] else '0' 828 else: 829 _params['maintenance_mode'] = '1' if kwargs['pause_in_maintenance'] else '0' 830 831 return _params 832 833 def check_difference(self, **kwargs): 834 """Check difference between action and user specified parameters. 835 836 Args: 837 **kwargs: Arbitrary keyword parameters. 838 839 Returns: 840 dict: dictionary of differences 841 """ 842 existing_action = convert_unicode_to_str(self._zapi_wrapper.check_if_action_exists(kwargs['name'])[0]) 843 parameters = convert_unicode_to_str(self._construct_parameters(**kwargs)) 844 change_parameters = {} 845 _diff = cleanup_data(compare_dictionaries(parameters, existing_action, change_parameters)) 846 return _diff 847 848 def update_action(self, **kwargs): 849 """Update action. 850 851 Args: 852 **kwargs: Arbitrary keyword parameters. 853 854 Returns: 855 action: updated action 856 """ 857 try: 858 if self._module.check_mode: 859 self._module.exit_json(msg="Action would be updated if check mode was not specified: %s" % kwargs, changed=True) 860 kwargs['actionid'] = kwargs.pop('action_id') 861 return self._zapi.action.update(kwargs) 862 except Exception as e: 863 self._module.fail_json(msg="Failed to update action '%s': %s" % (kwargs['actionid'], e)) 864 865 def add_action(self, **kwargs): 866 """Add action. 867 868 Args: 869 **kwargs: Arbitrary keyword parameters. 870 871 Returns: 872 action: added action 873 """ 874 try: 875 if self._module.check_mode: 876 self._module.exit_json(msg="Action would be added if check mode was not specified", changed=True) 877 parameters = self._construct_parameters(**kwargs) 878 action_list = self._zapi.action.create(parameters) 879 return action_list['actionids'][0] 880 except Exception as e: 881 self._module.fail_json(msg="Failed to create action '%s': %s" % (kwargs['name'], e)) 882 883 def delete_action(self, action_id): 884 """Delete action. 885 886 Args: 887 action_id: Action id 888 889 Returns: 890 action: deleted action 891 """ 892 try: 893 if self._module.check_mode: 894 self._module.exit_json(msg="Action would be deleted if check mode was not specified", changed=True) 895 return self._zapi.action.delete([action_id]) 896 except Exception as e: 897 self._module.fail_json(msg="Failed to delete action '%s': %s" % (action_id, e)) 898 899 900class Operations(object): 901 """ 902 Restructures the user defined operation data to fit the Zabbix API requirements 903 """ 904 def __init__(self, module, zbx, zapi_wrapper): 905 self._module = module 906 # self._zapi = zbx 907 self._zapi_wrapper = zapi_wrapper 908 909 def _construct_operationtype(self, operation): 910 """Construct operation type. 911 912 Args: 913 operation: operation to construct 914 915 Returns: 916 str: constructed operation 917 """ 918 try: 919 return to_numeric_value([ 920 "send_message", 921 "remote_command", 922 "add_host", 923 "remove_host", 924 "add_to_host_group", 925 "remove_from_host_group", 926 "link_to_template", 927 "unlink_from_template", 928 "enable_host", 929 "disable_host", 930 "set_host_inventory_mode"], operation['type'] 931 ) 932 except Exception as e: 933 self._module.fail_json(msg="Unsupported value '%s' for operation type." % operation['type']) 934 935 def _construct_opmessage(self, operation): 936 """Construct operation message. 937 938 Args: 939 operation: operation to construct the message 940 941 Returns: 942 dict: constructed operation message 943 """ 944 try: 945 return { 946 'default_msg': '0' if operation.get('message') is not None or operation.get('subject')is not None else '1', 947 'mediatypeid': self._zapi_wrapper.get_mediatype_by_mediatype_name( 948 operation.get('media_type') 949 ) if operation.get('media_type') is not None else '0', 950 'message': operation.get('message'), 951 'subject': operation.get('subject'), 952 } 953 except Exception as e: 954 self._module.fail_json(msg="Failed to construct operation message. The error was: %s" % e) 955 956 def _construct_opmessage_usr(self, operation): 957 """Construct operation message user. 958 959 Args: 960 operation: operation to construct the message user 961 962 Returns: 963 list: constructed operation message user or None if operation not found 964 """ 965 if operation.get('send_to_users') is None: 966 return None 967 return [{ 968 'userid': self._zapi_wrapper.get_user_by_user_name(_user)['userid'] 969 } for _user in operation.get('send_to_users')] 970 971 def _construct_opmessage_grp(self, operation): 972 """Construct operation message group. 973 974 Args: 975 operation: operation to construct the message group 976 977 Returns: 978 list: constructed operation message group or None if operation not found 979 """ 980 if operation.get('send_to_groups') is None: 981 return None 982 return [{ 983 'usrgrpid': self._zapi_wrapper.get_usergroup_by_usergroup_name(_group)['usrgrpid'] 984 } for _group in operation.get('send_to_groups')] 985 986 def _construct_opcommand(self, operation): 987 """Construct operation command. 988 989 Args: 990 operation: operation to construct command 991 992 Returns: 993 list: constructed operation command 994 """ 995 try: 996 return { 997 'type': to_numeric_value([ 998 'custom_script', 999 'ipmi', 1000 'ssh', 1001 'telnet', 1002 'global_script'], operation.get('command_type', 'custom_script')), 1003 'command': operation.get('command'), 1004 'execute_on': to_numeric_value([ 1005 'agent', 1006 'server', 1007 'proxy'], operation.get('execute_on', 'server')), 1008 'scriptid': self._zapi_wrapper.get_script_by_script_name( 1009 operation.get('script_name') 1010 ).get('scriptid'), 1011 'authtype': to_numeric_value([ 1012 'password', 1013 'private_key' 1014 ], operation.get('ssh_auth_type', 'password')), 1015 'privatekey': operation.get('ssh_privatekey_file'), 1016 'publickey': operation.get('ssh_publickey_file'), 1017 'username': operation.get('username'), 1018 'password': operation.get('password'), 1019 'port': operation.get('port') 1020 } 1021 except Exception as e: 1022 self._module.fail_json(msg="Failed to construct operation command. The error was: %s" % e) 1023 1024 def _construct_opcommand_hst(self, operation): 1025 """Construct operation command host. 1026 1027 Args: 1028 operation: operation to construct command host 1029 1030 Returns: 1031 list: constructed operation command host 1032 """ 1033 if operation.get('run_on_hosts') is None: 1034 return None 1035 return [{ 1036 'hostid': self._zapi_wrapper.get_host_by_host_name(_host)['hostid'] 1037 } if str(_host) != '0' else {'hostid': '0'} for _host in operation.get('run_on_hosts')] 1038 1039 def _construct_opcommand_grp(self, operation): 1040 """Construct operation command group. 1041 1042 Args: 1043 operation: operation to construct command group 1044 1045 Returns: 1046 list: constructed operation command group 1047 """ 1048 if operation.get('run_on_groups') is None: 1049 return None 1050 return [{ 1051 'groupid': self._zapi_wrapper.get_hostgroup_by_hostgroup_name(_group)['hostid'] 1052 } for _group in operation.get('run_on_groups')] 1053 1054 def _construct_opgroup(self, operation): 1055 """Construct operation group. 1056 1057 Args: 1058 operation: operation to construct group 1059 1060 Returns: 1061 list: constructed operation group 1062 """ 1063 return [{ 1064 'groupid': self._zapi_wrapper.get_hostgroup_by_hostgroup_name(_group)['groupid'] 1065 } for _group in operation.get('host_groups', [])] 1066 1067 def _construct_optemplate(self, operation): 1068 """Construct operation template. 1069 1070 Args: 1071 operation: operation to construct template 1072 1073 Returns: 1074 list: constructed operation template 1075 """ 1076 return [{ 1077 'templateid': self._zapi_wrapper.get_template_by_template_name(_template)['templateid'] 1078 } for _template in operation.get('templates', [])] 1079 1080 def _construct_opinventory(self, operation): 1081 """Construct operation inventory. 1082 1083 Args: 1084 operation: operation to construct inventory 1085 1086 Returns: 1087 dict: constructed operation inventory 1088 """ 1089 return {'inventory_mode': operation.get('inventory')} 1090 1091 def _construct_opconditions(self, operation): 1092 """Construct operation conditions. 1093 1094 Args: 1095 operation: operation to construct the conditions 1096 1097 Returns: 1098 list: constructed operation conditions 1099 """ 1100 _opcond = operation.get('operation_condition') 1101 if _opcond is not None: 1102 if _opcond == 'acknowledged': 1103 _value = '1' 1104 elif _opcond == 'not_acknowledged': 1105 _value = '0' 1106 return [{ 1107 'conditiontype': '14', 1108 'operator': '0', 1109 'value': _value 1110 }] 1111 return [] 1112 1113 def construct_the_data(self, operations): 1114 """Construct the operation data using helper methods. 1115 1116 Args: 1117 operation: operation to construct 1118 1119 Returns: 1120 list: constructed operation data 1121 """ 1122 constructed_data = [] 1123 for op in operations: 1124 operation_type = self._construct_operationtype(op) 1125 constructed_operation = { 1126 'operationtype': operation_type, 1127 'esc_period': op.get('esc_period'), 1128 'esc_step_from': op.get('esc_step_from'), 1129 'esc_step_to': op.get('esc_step_to') 1130 } 1131 # Send Message type 1132 if constructed_operation['operationtype'] == '0': 1133 constructed_operation['opmessage'] = self._construct_opmessage(op) 1134 constructed_operation['opmessage_usr'] = self._construct_opmessage_usr(op) 1135 constructed_operation['opmessage_grp'] = self._construct_opmessage_grp(op) 1136 constructed_operation['opconditions'] = self._construct_opconditions(op) 1137 1138 # Send Command type 1139 if constructed_operation['operationtype'] == '1': 1140 constructed_operation['opcommand'] = self._construct_opcommand(op) 1141 constructed_operation['opcommand_hst'] = self._construct_opcommand_hst(op) 1142 constructed_operation['opcommand_grp'] = self._construct_opcommand_grp(op) 1143 constructed_operation['opconditions'] = self._construct_opconditions(op) 1144 1145 # Add to/Remove from host group 1146 if constructed_operation['operationtype'] in ('4', '5'): 1147 constructed_operation['opgroup'] = self._construct_opgroup(op) 1148 1149 # Link/Unlink template 1150 if constructed_operation['operationtype'] in ('6', '7'): 1151 constructed_operation['optemplate'] = self._construct_optemplate(op) 1152 1153 # Set inventory mode 1154 if constructed_operation['operationtype'] == '10': 1155 constructed_operation['opinventory'] = self._construct_opinventory(op) 1156 1157 constructed_data.append(constructed_operation) 1158 1159 return cleanup_data(constructed_data) 1160 1161 1162class RecoveryOperations(Operations): 1163 """ 1164 Restructures the user defined recovery operations data to fit the Zabbix API requirements 1165 """ 1166 def _construct_operationtype(self, operation): 1167 """Construct operation type. 1168 1169 Args: 1170 operation: operation to construct type 1171 1172 Returns: 1173 str: constructed operation type 1174 """ 1175 try: 1176 return to_numeric_value([ 1177 "send_message", 1178 "remote_command", 1179 None, 1180 None, 1181 None, 1182 None, 1183 None, 1184 None, 1185 None, 1186 None, 1187 None, 1188 "notify_all_involved"], operation['type'] 1189 ) 1190 except Exception as e: 1191 self._module.fail_json(msg="Unsupported value '%s' for recovery operation type." % operation['type']) 1192 1193 def construct_the_data(self, operations): 1194 """Construct the recovery operations data using helper methods. 1195 1196 Args: 1197 operation: operation to construct 1198 1199 Returns: 1200 list: constructed recovery operations data 1201 """ 1202 constructed_data = [] 1203 for op in operations: 1204 operation_type = self._construct_operationtype(op) 1205 constructed_operation = { 1206 'operationtype': operation_type, 1207 } 1208 1209 # Send Message type 1210 if constructed_operation['operationtype'] in ('0', '11'): 1211 constructed_operation['opmessage'] = self._construct_opmessage(op) 1212 constructed_operation['opmessage_usr'] = self._construct_opmessage_usr(op) 1213 constructed_operation['opmessage_grp'] = self._construct_opmessage_grp(op) 1214 1215 # Send Command type 1216 if constructed_operation['operationtype'] == '1': 1217 constructed_operation['opcommand'] = self._construct_opcommand(op) 1218 constructed_operation['opcommand_hst'] = self._construct_opcommand_hst(op) 1219 constructed_operation['opcommand_grp'] = self._construct_opcommand_grp(op) 1220 1221 constructed_data.append(constructed_operation) 1222 1223 return cleanup_data(constructed_data) 1224 1225 1226class AcknowledgeOperations(Operations): 1227 """ 1228 Restructures the user defined acknowledge operations data to fit the Zabbix API requirements 1229 """ 1230 def _construct_operationtype(self, operation): 1231 """Construct operation type. 1232 1233 Args: 1234 operation: operation to construct type 1235 1236 Returns: 1237 str: constructed operation type 1238 """ 1239 try: 1240 return to_numeric_value([ 1241 "send_message", 1242 "remote_command", 1243 None, 1244 None, 1245 None, 1246 None, 1247 None, 1248 None, 1249 None, 1250 None, 1251 None, 1252 None, 1253 "notify_all_involved"], operation['type'] 1254 ) 1255 except Exception as e: 1256 self._module.fail_json(msg="Unsupported value '%s' for acknowledge operation type." % operation['type']) 1257 1258 def construct_the_data(self, operations): 1259 """Construct the acknowledge operations data using helper methods. 1260 1261 Args: 1262 operation: operation to construct 1263 1264 Returns: 1265 list: constructed acknowledge operations data 1266 """ 1267 constructed_data = [] 1268 for op in operations: 1269 operation_type = self._construct_operationtype(op) 1270 constructed_operation = { 1271 'operationtype': operation_type, 1272 } 1273 1274 # Send Message type 1275 if constructed_operation['operationtype'] in ('0', '11'): 1276 constructed_operation['opmessage'] = self._construct_opmessage(op) 1277 constructed_operation['opmessage_usr'] = self._construct_opmessage_usr(op) 1278 constructed_operation['opmessage_grp'] = self._construct_opmessage_grp(op) 1279 1280 # Send Command type 1281 if constructed_operation['operationtype'] == '1': 1282 constructed_operation['opcommand'] = self._construct_opcommand(op) 1283 constructed_operation['opcommand_hst'] = self._construct_opcommand_hst(op) 1284 constructed_operation['opcommand_grp'] = self._construct_opcommand_grp(op) 1285 1286 constructed_data.append(constructed_operation) 1287 1288 return cleanup_data(constructed_data) 1289 1290 1291class Filter(object): 1292 """ 1293 Restructures the user defined filter conditions to fit the Zabbix API requirements 1294 """ 1295 def __init__(self, module, zbx, zapi_wrapper): 1296 self._module = module 1297 self._zapi = zbx 1298 self._zapi_wrapper = zapi_wrapper 1299 1300 def _construct_evaltype(self, _eval_type, _formula, _conditions): 1301 """Construct the eval type 1302 1303 Args: 1304 _formula: zabbix condition evaluation formula 1305 _conditions: list of conditions to check 1306 1307 Returns: 1308 dict: constructed acknowledge operations data 1309 """ 1310 if len(_conditions) <= 1: 1311 return { 1312 'evaltype': '0', 1313 'formula': None 1314 } 1315 if _eval_type == 'andor': 1316 return { 1317 'evaltype': '0', 1318 'formula': None 1319 } 1320 if _eval_type == 'and': 1321 return { 1322 'evaltype': '1', 1323 'formula': None 1324 } 1325 if _eval_type == 'or': 1326 return { 1327 'evaltype': '2', 1328 'formula': None 1329 } 1330 if _eval_type == 'custom_expression': 1331 if _formula is not None: 1332 return { 1333 'evaltype': '3', 1334 'formula': _formula 1335 } 1336 else: 1337 self._module.fail_json(msg="'formula' is required when 'eval_type' is set to 'custom_expression'") 1338 if _formula is not None: 1339 return { 1340 'evaltype': '3', 1341 'formula': _formula 1342 } 1343 return { 1344 'evaltype': '0', 1345 'formula': None 1346 } 1347 1348 def _construct_conditiontype(self, _condition): 1349 """Construct the condition type 1350 1351 Args: 1352 _condition: condition to check 1353 1354 Returns: 1355 str: constructed condition type data 1356 """ 1357 try: 1358 return to_numeric_value([ 1359 "host_group", 1360 "host", 1361 "trigger", 1362 "trigger_name", 1363 "trigger_severity", 1364 "trigger_value", 1365 "time_period", 1366 "host_ip", 1367 "discovered_service_type", 1368 "discovered_service_port", 1369 "discovery_status", 1370 "uptime_or_downtime_duration", 1371 "received_value", 1372 "host_template", 1373 None, 1374 "application", 1375 "maintenance_status", 1376 None, 1377 "discovery_rule", 1378 "discovery_check", 1379 "proxy", 1380 "discovery_object", 1381 "host_name", 1382 "event_type", 1383 "host_metadata", 1384 "event_tag", 1385 "event_tag_value"], _condition['type'] 1386 ) 1387 except Exception as e: 1388 self._module.fail_json(msg="Unsupported value '%s' for condition type." % _condition['type']) 1389 1390 def _construct_operator(self, _condition): 1391 """Construct operator 1392 1393 Args: 1394 _condition: condition to construct 1395 1396 Returns: 1397 str: constructed operator 1398 """ 1399 try: 1400 return to_numeric_value([ 1401 "=", 1402 "<>", 1403 "like", 1404 "not like", 1405 "in", 1406 ">=", 1407 "<=", 1408 "not in", 1409 "matches", 1410 "does not match", 1411 "Yes", 1412 "No"], _condition['operator'] 1413 ) 1414 except Exception as e: 1415 self._module.fail_json(msg="Unsupported value '%s' for operator." % _condition['operator']) 1416 1417 def _construct_value(self, conditiontype, value): 1418 """Construct operator 1419 1420 Args: 1421 conditiontype: type of condition to construct 1422 value: value to construct 1423 1424 Returns: 1425 str: constructed value 1426 """ 1427 try: 1428 # Host group 1429 if conditiontype == '0': 1430 return self._zapi_wrapper.get_hostgroup_by_hostgroup_name(value)['groupid'] 1431 # Host 1432 if conditiontype == '1': 1433 return self._zapi_wrapper.get_host_by_host_name(value)['hostid'] 1434 # Trigger 1435 if conditiontype == '2': 1436 return self._zapi_wrapper.get_trigger_by_trigger_name(value)['triggerid'] 1437 # Trigger name: return as is 1438 # Trigger severity 1439 if conditiontype == '4': 1440 return to_numeric_value([ 1441 "not classified", 1442 "information", 1443 "warning", 1444 "average", 1445 "high", 1446 "disaster"], value or "not classified" 1447 ) 1448 1449 # Trigger value 1450 if conditiontype == '5': 1451 return to_numeric_value([ 1452 "ok", 1453 "problem"], value or "ok" 1454 ) 1455 # Time period: return as is 1456 # Host IP: return as is 1457 # Discovered service type 1458 if conditiontype == '8': 1459 return to_numeric_value([ 1460 "SSH", 1461 "LDAP", 1462 "SMTP", 1463 "FTP", 1464 "HTTP", 1465 "POP", 1466 "NNTP", 1467 "IMAP", 1468 "TCP", 1469 "Zabbix agent", 1470 "SNMPv1 agent", 1471 "SNMPv2 agent", 1472 "ICMP ping", 1473 "SNMPv3 agent", 1474 "HTTPS", 1475 "Telnet"], value 1476 ) 1477 # Discovered service port: return as is 1478 # Discovery status 1479 if conditiontype == '10': 1480 return to_numeric_value([ 1481 "up", 1482 "down", 1483 "discovered", 1484 "lost"], value 1485 ) 1486 if conditiontype == '13': 1487 return self._zapi_wrapper.get_template_by_template_name(value)['templateid'] 1488 if conditiontype == '18': 1489 return self._zapi_wrapper.get_discovery_rule_by_discovery_rule_name(value)['druleid'] 1490 if conditiontype == '19': 1491 return self._zapi_wrapper.get_discovery_check_by_discovery_check_name(value)['dcheckid'] 1492 if conditiontype == '20': 1493 return self._zapi_wrapper.get_proxy_by_proxy_name(value)['proxyid'] 1494 if conditiontype == '21': 1495 return to_numeric_value([ 1496 "pchldrfor0", 1497 "host", 1498 "service"], value 1499 ) 1500 if conditiontype == '23': 1501 return to_numeric_value([ 1502 "item in not supported state", 1503 "item in normal state", 1504 "LLD rule in not supported state", 1505 "LLD rule in normal state", 1506 "trigger in unknown state", 1507 "trigger in normal state"], value 1508 ) 1509 return value 1510 except Exception as e: 1511 self._module.fail_json( 1512 msg="""Unsupported value '%s' for specified condition type. 1513 Check out Zabbix API documentation for supported values for 1514 condition type '%s' at 1515 https://www.zabbix.com/documentation/3.4/manual/api/reference/action/object#action_filter_condition""" % (value, conditiontype) 1516 ) 1517 1518 def construct_the_data(self, _eval_type, _formula, _conditions): 1519 """Construct the user defined filter conditions to fit the Zabbix API 1520 requirements operations data using helper methods. 1521 1522 Args: 1523 _formula: zabbix condition evaluation formula 1524 _conditions: conditions to construct 1525 1526 Returns: 1527 dict: user defined filter conditions 1528 """ 1529 if _conditions is None: 1530 return None 1531 constructed_data = {} 1532 constructed_data['conditions'] = [] 1533 for cond in _conditions: 1534 condition_type = self._construct_conditiontype(cond) 1535 constructed_data['conditions'].append({ 1536 "conditiontype": condition_type, 1537 "value": self._construct_value(condition_type, cond.get("value")), 1538 "value2": cond.get("value2"), 1539 "formulaid": cond.get("formulaid"), 1540 "operator": self._construct_operator(cond) 1541 }) 1542 _constructed_evaltype = self._construct_evaltype( 1543 _eval_type, 1544 _formula, 1545 constructed_data['conditions'] 1546 ) 1547 constructed_data['evaltype'] = _constructed_evaltype['evaltype'] 1548 constructed_data['formula'] = _constructed_evaltype['formula'] 1549 return cleanup_data(constructed_data) 1550 1551 1552def convert_unicode_to_str(data): 1553 """Converts unicode objects to strings in dictionary 1554 args: 1555 data: unicode object 1556 1557 Returns: 1558 dict: strings in dictionary 1559 """ 1560 if isinstance(data, dict): 1561 return dict(map(convert_unicode_to_str, data.items())) 1562 elif isinstance(data, (list, tuple, set)): 1563 return type(data)(map(convert_unicode_to_str, data)) 1564 elif data is None: 1565 return data 1566 else: 1567 return str(data) 1568 1569 1570def to_numeric_value(strs, value): 1571 """Converts string values to integers 1572 Args: 1573 value: string value 1574 1575 Returns: 1576 int: converted integer 1577 """ 1578 strs = [s.lower() if isinstance(s, str) else s for s in strs] 1579 value = value.lower() 1580 tmp_dict = dict(zip(strs, list(range(len(strs))))) 1581 return str(tmp_dict[value]) 1582 1583 1584def compare_lists(l1, l2, diff_dict): 1585 """ 1586 Compares l1 and l2 lists and adds the items that are different 1587 to the diff_dict dictionary. 1588 Used in recursion with compare_dictionaries() function. 1589 Args: 1590 l1: first list to compare 1591 l2: second list to compare 1592 diff_dict: dictionary to store the difference 1593 1594 Returns: 1595 dict: items that are different 1596 """ 1597 if len(l1) != len(l2): 1598 diff_dict.append(l1) 1599 return diff_dict 1600 for i, item in enumerate(l1): 1601 if isinstance(item, dict): 1602 diff_dict.insert(i, {}) 1603 diff_dict[i] = compare_dictionaries(item, l2[i], diff_dict[i]) 1604 else: 1605 if item != l2[i]: 1606 diff_dict.append(item) 1607 while {} in diff_dict: 1608 diff_dict.remove({}) 1609 return diff_dict 1610 1611 1612def compare_dictionaries(d1, d2, diff_dict): 1613 """ 1614 Compares d1 and d2 dictionaries and adds the items that are different 1615 to the diff_dict dictionary. 1616 Used in recursion with compare_lists() function. 1617 Args: 1618 d1: first dictionary to compare 1619 d2: second dictionary to compare 1620 diff_dict: dictionary to store the difference 1621 1622 Returns: 1623 dict: items that are different 1624 """ 1625 for k, v in d1.items(): 1626 if k not in d2: 1627 diff_dict[k] = v 1628 continue 1629 if isinstance(v, dict): 1630 diff_dict[k] = {} 1631 compare_dictionaries(v, d2[k], diff_dict[k]) 1632 if diff_dict[k] == {}: 1633 del diff_dict[k] 1634 else: 1635 diff_dict[k] = v 1636 elif isinstance(v, list): 1637 diff_dict[k] = [] 1638 compare_lists(v, d2[k], diff_dict[k]) 1639 if diff_dict[k] == []: 1640 del diff_dict[k] 1641 else: 1642 diff_dict[k] = v 1643 else: 1644 if v != d2[k]: 1645 diff_dict[k] = v 1646 return diff_dict 1647 1648 1649def cleanup_data(obj): 1650 """Removes the None values from the object and returns the object 1651 Args: 1652 obj: object to cleanup 1653 1654 Returns: 1655 object: cleaned object 1656 """ 1657 if isinstance(obj, (list, tuple, set)): 1658 return type(obj)(cleanup_data(x) for x in obj if x is not None) 1659 elif isinstance(obj, dict): 1660 return type(obj)((cleanup_data(k), cleanup_data(v)) 1661 for k, v in obj.items() if k is not None and v is not None) 1662 else: 1663 return obj 1664 1665 1666def main(): 1667 """Main ansible module function 1668 """ 1669 1670 module = AnsibleModule( 1671 argument_spec=dict( 1672 server_url=dict(type='str', required=True, aliases=['url']), 1673 login_user=dict(type='str', required=True), 1674 login_password=dict(type='str', required=True, no_log=True), 1675 http_login_user=dict(type='str', required=False, default=None), 1676 http_login_password=dict(type='str', required=False, default=None, no_log=True), 1677 validate_certs=dict(type='bool', required=False, default=True), 1678 esc_period=dict(type='int', required=False), 1679 timeout=dict(type='int', default=10), 1680 name=dict(type='str', required=True), 1681 event_source=dict(type='str', required=False, choices=['trigger', 'discovery', 'auto_registration', 'internal']), 1682 state=dict(type='str', required=False, default='present', choices=['present', 'absent']), 1683 status=dict(type='str', required=False, default='enabled', choices=['enabled', 'disabled']), 1684 pause_in_maintenance=dict(type='bool', required=False, default=True), 1685 default_message=dict(type='str', required=False, default=''), 1686 default_subject=dict(type='str', required=False, default=''), 1687 recovery_default_message=dict(type='str', required=False, default=''), 1688 recovery_default_subject=dict(type='str', required=False, default=''), 1689 acknowledge_default_message=dict(type='str', required=False, default=''), 1690 acknowledge_default_subject=dict(type='str', required=False, default=''), 1691 conditions=dict( 1692 type='list', 1693 required=False, 1694 default=[], 1695 elements='dict', 1696 options=dict( 1697 formulaid=dict(type='str', required=False), 1698 operator=dict(type='str', required=True), 1699 type=dict(type='str', required=True), 1700 value=dict(type='str', required=True), 1701 value2=dict(type='str', required=False) 1702 ) 1703 ), 1704 formula=dict(type='str', required=False, default=None), 1705 eval_type=dict(type='str', required=False, default=None, choices=['andor', 'and', 'or', 'custom_expression']), 1706 operations=dict( 1707 type='list', 1708 required=False, 1709 default=[], 1710 elements='dict', 1711 options=dict( 1712 type=dict( 1713 type='str', 1714 required=True, 1715 choices=[ 1716 'send_message', 1717 'remote_command', 1718 'add_host', 1719 'remove_host', 1720 'add_to_host_group', 1721 'remove_from_host_group', 1722 'link_to_template', 1723 'unlink_from_template', 1724 'enable_host', 1725 'disable_host', 1726 'set_host_inventory_mode', 1727 ] 1728 ), 1729 esc_period=dict(type='int', required=False), 1730 esc_step_from=dict(type='int', required=False, default=1), 1731 esc_step_to=dict(type='int', required=False, default=1), 1732 operation_condition=dict( 1733 type='str', 1734 required=False, 1735 default=None, 1736 choices=['acknowledged', 'not_acknowledged'] 1737 ), 1738 # when type is remote_command 1739 command_type=dict( 1740 type='str', 1741 required=False, 1742 choices=[ 1743 'custom_script', 1744 'ipmi', 1745 'ssh', 1746 'telnet', 1747 'global_script' 1748 ] 1749 ), 1750 command=dict(type='str', required=False), 1751 execute_on=dict( 1752 type='str', 1753 required=False, 1754 choices=['agent', 'server', 'proxy'] 1755 ), 1756 password=dict(type='str', required=False, no_log=True), 1757 port=dict(type='int', required=False), 1758 run_on_groups=dict(type='list', required=False), 1759 run_on_hosts=dict(type='list', required=False), 1760 script_name=dict(type='str', required=False), 1761 ssh_auth_type=dict( 1762 type='str', 1763 required=False, 1764 default='password', 1765 choices=['password', 'public_key'] 1766 ), 1767 ssh_privatekey_file=dict(type='str', required=False), 1768 ssh_publickey_file=dict(type='str', required=False), 1769 username=dict(type='str', required=False), 1770 # when type is send_message 1771 media_type=dict(type='str', required=False), 1772 subject=dict(type='str', required=False), 1773 message=dict(type='str', required=False), 1774 send_to_groups=dict(type='list', required=False), 1775 send_to_users=dict(type='list', required=False), 1776 # when type is add_to_host_group or remove_from_host_group 1777 host_groups=dict(type='list', required=False), 1778 # when type is set_host_inventory_mode 1779 inventory=dict(type='str', required=False), 1780 # when type is link_to_template or unlink_from_template 1781 templates=dict(type='list', required=False) 1782 ), 1783 required_if=[ 1784 ['type', 'remote_command', ['command_type']], 1785 ['type', 'remote_command', ['run_on_groups', 'run_on_hosts'], True], 1786 ['command_type', 'custom_script', [ 1787 'command', 1788 'execute_on' 1789 ]], 1790 ['command_type', 'ipmi', ['command']], 1791 ['command_type', 'ssh', [ 1792 'command', 1793 'password', 1794 'username', 1795 'port', 1796 'ssh_auth_type', 1797 'ssh_privatekey_file', 1798 'ssh_publickey_file' 1799 ]], 1800 ['command_type', 'telnet', [ 1801 'command', 1802 'password', 1803 'username', 1804 'port' 1805 ]], 1806 ['command_type', 'global_script', ['script_name']], 1807 ['type', 'add_to_host_group', ['host_groups']], 1808 ['type', 'remove_from_host_group', ['host_groups']], 1809 ['type', 'link_to_template', ['templates']], 1810 ['type', 'unlink_from_template', ['templates']], 1811 ['type', 'set_host_inventory_mode', ['inventory']], 1812 ['type', 'send_message', ['send_to_users', 'send_to_groups'], True] 1813 ] 1814 ), 1815 recovery_operations=dict( 1816 type='list', 1817 required=False, 1818 default=[], 1819 elements='dict', 1820 options=dict( 1821 type=dict( 1822 type='str', 1823 required=True, 1824 choices=[ 1825 'send_message', 1826 'remote_command', 1827 'notify_all_involved' 1828 ] 1829 ), 1830 # when type is remote_command 1831 command_type=dict( 1832 type='str', 1833 required=False, 1834 choices=[ 1835 'custom_script', 1836 'ipmi', 1837 'ssh', 1838 'telnet', 1839 'global_script' 1840 ] 1841 ), 1842 command=dict(type='str', required=False), 1843 execute_on=dict( 1844 type='str', 1845 required=False, 1846 choices=['agent', 'server', 'proxy'] 1847 ), 1848 password=dict(type='str', required=False, no_log=True), 1849 port=dict(type='int', required=False), 1850 run_on_groups=dict(type='list', required=False), 1851 run_on_hosts=dict(type='list', required=False), 1852 script_name=dict(type='str', required=False), 1853 ssh_auth_type=dict( 1854 type='str', 1855 required=False, 1856 default='password', 1857 choices=['password', 'public_key'] 1858 ), 1859 ssh_privatekey_file=dict(type='str', required=False), 1860 ssh_publickey_file=dict(type='str', required=False), 1861 username=dict(type='str', required=False), 1862 # when type is send_message 1863 media_type=dict(type='str', required=False), 1864 subject=dict(type='str', required=False), 1865 message=dict(type='str', required=False), 1866 send_to_groups=dict(type='list', required=False), 1867 send_to_users=dict(type='list', required=False), 1868 ), 1869 required_if=[ 1870 ['type', 'remote_command', ['command_type']], 1871 ['type', 'remote_command', [ 1872 'run_on_groups', 1873 'run_on_hosts' 1874 ], True], 1875 ['command_type', 'custom_script', [ 1876 'command', 1877 'execute_on' 1878 ]], 1879 ['command_type', 'ipmi', ['command']], 1880 ['command_type', 'ssh', [ 1881 'command', 1882 'password', 1883 'username', 1884 'port', 1885 'ssh_auth_type', 1886 'ssh_privatekey_file', 1887 'ssh_publickey_file' 1888 ]], 1889 ['command_type', 'telnet', [ 1890 'command', 1891 'password', 1892 'username', 1893 'port' 1894 ]], 1895 ['command_type', 'global_script', ['script_name']], 1896 ['type', 'send_message', ['send_to_users', 'send_to_groups'], True] 1897 ] 1898 ), 1899 acknowledge_operations=dict( 1900 type='list', 1901 required=False, 1902 default=[], 1903 elements='dict', 1904 options=dict( 1905 type=dict( 1906 type='str', 1907 required=True, 1908 choices=[ 1909 'send_message', 1910 'remote_command', 1911 'notify_all_involved' 1912 ] 1913 ), 1914 # when type is remote_command 1915 command_type=dict( 1916 type='str', 1917 required=False, 1918 choices=[ 1919 'custom_script', 1920 'ipmi', 1921 'ssh', 1922 'telnet', 1923 'global_script' 1924 ] 1925 ), 1926 command=dict(type='str', required=False), 1927 execute_on=dict( 1928 type='str', 1929 required=False, 1930 choices=['agent', 'server', 'proxy'] 1931 ), 1932 password=dict(type='str', required=False, no_log=True), 1933 port=dict(type='int', required=False), 1934 run_on_groups=dict(type='list', required=False), 1935 run_on_hosts=dict(type='list', required=False), 1936 script_name=dict(type='str', required=False), 1937 ssh_auth_type=dict( 1938 type='str', 1939 required=False, 1940 default='password', 1941 choices=['password', 'public_key'] 1942 ), 1943 ssh_privatekey_file=dict(type='str', required=False), 1944 ssh_publickey_file=dict(type='str', required=False), 1945 username=dict(type='str', required=False), 1946 # when type is send_message 1947 media_type=dict(type='str', required=False), 1948 subject=dict(type='str', required=False), 1949 message=dict(type='str', required=False), 1950 send_to_groups=dict(type='list', required=False), 1951 send_to_users=dict(type='list', required=False), 1952 ), 1953 required_if=[ 1954 ['type', 'remote_command', ['command_type']], 1955 ['type', 'remote_command', [ 1956 'run_on_groups', 1957 'run_on_hosts' 1958 ], True], 1959 ['command_type', 'custom_script', [ 1960 'command', 1961 'execute_on' 1962 ]], 1963 ['command_type', 'ipmi', ['command']], 1964 ['command_type', 'ssh', [ 1965 'command', 1966 'password', 1967 'username', 1968 'port', 1969 'ssh_auth_type', 1970 'ssh_privatekey_file', 1971 'ssh_publickey_file' 1972 ]], 1973 ['command_type', 'telnet', [ 1974 'command', 1975 'password', 1976 'username', 1977 'port' 1978 ]], 1979 ['command_type', 'global_script', ['script_name']], 1980 ['type', 'send_message', ['send_to_users', 'send_to_groups'], True] 1981 ] 1982 ) 1983 ), 1984 required_if=[ 1985 ['state', 'present', [ 1986 'esc_period', 1987 'event_source' 1988 ]] 1989 ], 1990 supports_check_mode=True 1991 ) 1992 1993 if not HAS_ZABBIX_API: 1994 module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR) 1995 1996 server_url = module.params['server_url'] 1997 login_user = module.params['login_user'] 1998 login_password = module.params['login_password'] 1999 http_login_user = module.params['http_login_user'] 2000 http_login_password = module.params['http_login_password'] 2001 validate_certs = module.params['validate_certs'] 2002 timeout = module.params['timeout'] 2003 name = module.params['name'] 2004 esc_period = module.params['esc_period'] 2005 event_source = module.params['event_source'] 2006 state = module.params['state'] 2007 status = module.params['status'] 2008 pause_in_maintenance = module.params['pause_in_maintenance'] 2009 default_message = module.params['default_message'] 2010 default_subject = module.params['default_subject'] 2011 recovery_default_message = module.params['recovery_default_message'] 2012 recovery_default_subject = module.params['recovery_default_subject'] 2013 acknowledge_default_message = module.params['acknowledge_default_message'] 2014 acknowledge_default_subject = module.params['acknowledge_default_subject'] 2015 conditions = module.params['conditions'] 2016 formula = module.params['formula'] 2017 eval_type = module.params['eval_type'] 2018 operations = module.params['operations'] 2019 recovery_operations = module.params['recovery_operations'] 2020 acknowledge_operations = module.params['acknowledge_operations'] 2021 2022 try: 2023 zbx = ZabbixAPI(server_url, timeout=timeout, user=http_login_user, 2024 passwd=http_login_password, validate_certs=validate_certs) 2025 zbx.login(login_user, login_password) 2026 atexit.register(zbx.logout) 2027 except Exception as e: 2028 module.fail_json(msg="Failed to connect to Zabbix server: %s" % e) 2029 2030 zapi_wrapper = Zapi(module, zbx) 2031 2032 action = Action(module, zbx, zapi_wrapper) 2033 2034 action_exists = zapi_wrapper.check_if_action_exists(name) 2035 ops = Operations(module, zbx, zapi_wrapper) 2036 recovery_ops = RecoveryOperations(module, zbx, zapi_wrapper) 2037 acknowledge_ops = AcknowledgeOperations(module, zbx, zapi_wrapper) 2038 fltr = Filter(module, zbx, zapi_wrapper) 2039 2040 if action_exists: 2041 action_id = zapi_wrapper.get_action_by_name(name)['actionid'] 2042 if state == "absent": 2043 result = action.delete_action(action_id) 2044 module.exit_json(changed=True, msg="Action Deleted: %s, ID: %s" % (name, result)) 2045 else: 2046 difference = action.check_difference( 2047 action_id=action_id, 2048 name=name, 2049 event_source=event_source, 2050 esc_period=esc_period, 2051 status=status, 2052 pause_in_maintenance=pause_in_maintenance, 2053 default_message=default_message, 2054 default_subject=default_subject, 2055 recovery_default_message=recovery_default_message, 2056 recovery_default_subject=recovery_default_subject, 2057 acknowledge_default_message=acknowledge_default_message, 2058 acknowledge_default_subject=acknowledge_default_subject, 2059 operations=ops.construct_the_data(operations), 2060 recovery_operations=recovery_ops.construct_the_data(recovery_operations), 2061 acknowledge_operations=acknowledge_ops.construct_the_data(acknowledge_operations), 2062 conditions=fltr.construct_the_data(eval_type, formula, conditions) 2063 ) 2064 2065 if difference == {}: 2066 module.exit_json(changed=False, msg="Action is up to date: %s" % (name)) 2067 else: 2068 result = action.update_action( 2069 action_id=action_id, 2070 **difference 2071 ) 2072 module.exit_json(changed=True, msg="Action Updated: %s, ID: %s" % (name, result)) 2073 else: 2074 if state == "absent": 2075 module.exit_json(changed=False) 2076 else: 2077 action_id = action.add_action( 2078 name=name, 2079 event_source=event_source, 2080 esc_period=esc_period, 2081 status=status, 2082 pause_in_maintenance=pause_in_maintenance, 2083 default_message=default_message, 2084 default_subject=default_subject, 2085 recovery_default_message=recovery_default_message, 2086 recovery_default_subject=recovery_default_subject, 2087 acknowledge_default_message=acknowledge_default_message, 2088 acknowledge_default_subject=acknowledge_default_subject, 2089 operations=ops.construct_the_data(operations), 2090 recovery_operations=recovery_ops.construct_the_data(recovery_operations), 2091 acknowledge_operations=acknowledge_ops.construct_the_data(acknowledge_operations), 2092 conditions=fltr.construct_the_data(eval_type, formula, conditions) 2093 ) 2094 module.exit_json(changed=True, msg="Action created: %s, ID: %s" % (name, action_id)) 2095 2096 2097if __name__ == '__main__': 2098 main() 2099