Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r"""
options:
persistent_connect_timeout:
type: int
description:
- Configures, in seconds, the amount of time to wait when trying to initially
establish a persistent connection. If this value expires before the connection
to the remote device is completed, the connection will fail.
default: 30
ini:
- section: persistent_connection
key: connect_timeout
env:
- name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT
vars:
- name: ansible_connect_timeout
persistent_command_timeout:
type: int
description:
- Configures, in seconds, the amount of time to wait for a command to
return from the remote device. If this timer is exceeded before the
command returns, the connection plugin will raise an exception and
close.
default: 30
ini:
- section: persistent_connection
key: command_timeout
env:
- name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
vars:
- name: ansible_command_timeout
persistent_log_messages:
type: boolean
description:
- This flag will enable logging the command executed and response received from
target device in the ansible log file. For this option to work 'log_path' ansible
configuration option is required to be set to a file path with write access.
- Be sure to fully understand the security implications of enabling this
option as it could create a security vulnerability by logging sensitive information in log file.
default: False
ini:
- section: persistent_connection
key: log_messages
env:
- name: ANSIBLE_PERSISTENT_LOG_MESSAGES
vars:
- name: ansible_persistent_log_messages
"""

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
# Standard documentation fragment
DOCUMENTATION = r'''
options:
server_url:
description:
- URL of Zabbix server, with protocol (http or https).
C(url) is an alias for C(server_url).
- If not set the environment variable C(ZABBIX_SERVER) will be used.
- This option is deprecated with the move to httpapi connection and will be removed in the next release
required: false
type: str
aliases: [ url ]
login_user:
description:
- Zabbix user name.
- If not set the environment variable C(ZABBIX_USERNAME) will be used.
- This option is deprecated with the move to httpapi connection and will be removed in the next release
type: str
required: false
login_password:
description:
- Zabbix user password.
- If not set the environment variable C(ZABBIX_PASSWORD) will be used.
- This option is deprecated with the move to httpapi connection and will be removed in the next release
type: str
required: false
http_login_user:
description:
- Basic Auth login
type: str
required: false
http_login_password:
description:
- Basic Auth password
type: str
required: false
timeout:
description:
- The timeout of API request (seconds).
- This option is deprecated with the move to httpapi connection and will be removed in the next release
- The default value is C(10)
type: int
validate_certs:
description:
- If set to False, SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
- If not set the environment variable C(ZABBIX_VALIDATE_CERTS) will be used.
- This option is deprecated with the move to httpapi connection and will be removed in the next release
- The default value is C(true)
type: bool
notes:
- If you use I(login_password=zabbix), the word "zabbix" is replaced by "********" in all module output, because I(login_password) uses C(no_log).
See L(this FAQ,https://docs.ansible.com/ansible/latest/network/user_guide/faq.html#why-is-my-output-sometimes-replaced-with) for more information.
'''

View File

@@ -0,0 +1,209 @@
# (c) 2021, Markus Fischbacher (fischbacher.markus@gmail.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Quick Link to Zabbix API docs: https://www.zabbix.com/documentation/current/manual/api
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
name: zabbix
author:
- Markus Fischbacher (@rockaut)
- Evgeny Yurchenko (@BGmot)
short_description: HttpApi Plugin for Zabbix
description:
- This HttpApi plugin provides methods to connect to Zabbix over their HTTP(S)-based api.
version_added: 1.8.0
options:
zabbix_auth_key:
type: str
description:
- Specifies API authentication key
env:
- name: ANSIBLE_ZABBIX_AUTH_KEY
vars:
- name: ansible_zabbix_auth_key
zabbix_url_path:
type: str
description:
- Specifies path portion in Zabbix WebUI URL, e.g. for https://myzabbixfarm.com/zabbixeu zabbix_url_path=zabbixeu
default: zabbix
env:
- name: ANSIBLE_ZABBIX_URL_PATH
vars:
- name: ansible_zabbix_url_path
http_login_user:
type: str
description:
- The http user to access zabbix url with Basic Auth
vars:
- name: http_login_user
http_login_password:
type: str
description:
- The http password to access zabbix url with Basic Auth
vars:
- name: http_login_password
"""
import json
import base64
from uuid import uuid4
from ansible.module_utils.basic import to_text
from ansible.errors import AnsibleConnectionFailure
from ansible.plugins.httpapi import HttpApiBase
from ansible.module_utils.connection import ConnectionError
class HttpApi(HttpApiBase):
zbx_api_version = None
auth_key = None
url_path = '/zabbix' # By default Zabbix WebUI is on http(s)://FQDN/zabbix
def set_become(self, become_context):
"""As this is an http rpc call there is no elevation available
"""
pass
def update_auth(self, response, response_text):
return None
def login(self, username, password):
self.auth_key = self.get_option('zabbix_auth_key')
if self.auth_key:
self.connection._auth = {'auth': self.auth_key}
return
http_login_user = self.get_option('http_login_user')
if http_login_user and http_login_user != '-42':
# Provide "fake" auth so netcommon.connection does not replace our headers
self.connection._auth = {'auth': 'fake'}
payload = self.payload_builder("user.login", user=username, password=password)
code, response = self.send_request(data=payload)
if code == 200 and response != '':
# Replace auth with real api_key we got from Zabbix after successful login
self.connection._auth = {'auth': response}
def logout(self):
if self.connection._auth and not self.auth_key:
payload = self.payload_builder("user.logout")
self.send_request(data=payload)
def api_version(self):
url_path = self.get_option('zabbix_url_path')
if isinstance(url_path, str):
# zabbix_url_path provided (even if it is an empty string)
if url_path == '':
self.url_path = ''
else:
self.url_path = '/' + url_path
if not self.zbx_api_version:
if not hasattr(self.connection, 'zbx_api_version'):
code, version = self.send_request(data=self.payload_builder('apiinfo.version'))
if code == 200 and version != '':
self.connection.zbx_api_version = version
self.zbx_api_version = self.connection.zbx_api_version
return self.zbx_api_version
def send_request(self, data=None, request_method="POST", path="/api_jsonrpc.php"):
path = self.url_path + path
if not data:
data = {}
if self.connection._auth:
data['auth'] = self.connection._auth['auth']
hdrs = {
'Content-Type': 'application/json-rpc',
'Accept': 'application/json',
}
http_login_user = self.get_option('http_login_user')
http_login_password = self.get_option('http_login_password')
if http_login_user and http_login_user != '-42':
# Need to add Basic auth header
credentials = (http_login_user + ':' + http_login_password).encode('ascii')
hdrs['Authorization'] = 'Basic ' + base64.b64encode(credentials).decode("ascii")
if data['method'] in ['user.login', 'apiinfo.version']:
# user.login and apiinfo.version do not need "auth" in data
# we provided fake one in login() method to correctly handle HTTP basic auth header
data.pop('auth', None)
data = json.dumps(data)
try:
self._display_request(request_method, path)
response, response_data = self.connection.send(
path,
data,
method=request_method,
headers=hdrs
)
value = to_text(response_data.getvalue())
try:
json_data = json.loads(value) if value else {}
if "result" in json_data:
json_data = json_data["result"]
# JSONDecodeError only available on Python 3.5+
except ValueError:
raise ConnectionError("Invalid JSON response: %s" % value)
try:
# Some methods return bool not a dict in "result"
iter(json_data)
except TypeError:
# Do not try to find "error" if it is not a dict
return response.getcode(), json_data
if "error" in json_data:
raise ConnectionError("REST API returned %s when sending %s" % (json_data["error"], data))
return response.getcode(), json_data
except AnsibleConnectionFailure as e:
self.connection.queue_message("vvv", "AnsibleConnectionFailure: %s" % e)
if to_text("Could not connect to") in to_text(e):
raise
if to_text("401") in to_text(e):
return 401, "Authentication failure"
else:
return 404, "Object not found"
except Exception as e:
raise e
def _display_request(self, request_method, path):
self.connection.queue_message(
"vvvv",
"Web Services: %s %s/%s" % (request_method, self.connection._url, path),
)
def _get_response_value(self, response_data):
return to_text(response_data.getvalue())
def _response_to_json(self, response_text):
try:
return json.loads(response_text) if response_text else {}
# JSONDecodeError only available on Python 3.5+
except ValueError:
raise ConnectionError("Invalid JSON response: %s" % response_text)
@staticmethod
def payload_builder(method_, auth_=None, **kwargs):
reqid = str(uuid4())
req = {'jsonrpc': '2.0', 'method': method_, 'id': reqid}
req['params'] = (kwargs)
return req
def handle_httperror(self, exc):
# The method defined in ansible.plugins.httpapi
# We need to override it to avoid endless re-tries if HTTP authentication fails
if exc.code == 401:
return False
return exc

View File

@@ -0,0 +1,387 @@
#
# Copyright: (c), Ansible Project
#
# (c) 2013, Greg Buehler
# (c) 2018, Filippo Ferrazini
# (c) 2021, Timothy Test
# Modified from ServiceNow Inventory Plugin and Zabbix inventory Script
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
name: zabbix_inventory
author:
- Timothy Test (@ttestscripting)
short_description: Zabbix Inventory Plugin
version_added: 1.4.0
description:
- Zabbix Inventory plugin
- All vars from zabbix are prefixed with zbx_
requirements:
- "python >= 2.6"
- "zabbix-api >= 0.5.4"
options:
server_url:
description:
- URL of Zabbix server, with protocol (http or https).
C(url) is an alias for C(server_url).
required: true
type: str
aliases: [ url ]
env:
- name: ZABBIX_SERVER
proxy:
description: Proxy server to use for reaching zabbix API
type: string
default: ''
host_zapi_query:
description:
- API query for hosts - see zabbix documentation for more details U(https://www.zabbix.com/documentation/current/manual/api/reference/host/get)
type: dict
default: {}
suboptions:
selectApplications:
type: str
description:
- query
- Return an applications property with host applications.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/application/object) for more details on field names
selectDiscoveries:
type: str
description:
- query
- Return a discoveries property with host low-level discovery rules.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/discoveryrule/object) for more details on field names
selectDiscoveryRule:
type: str
description:
- query
- Return a discoveryRule property with the low-level discovery rule that created the host (from host prototype in VMware monitoring).
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- please see U(https://www.zabbix.com/documentation/current/manual/api/reference/discoveryrule/object) for more details on field names
selectGraphs:
type: str
description:
- query
- Return a discoveries property with host low-level discovery rules.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/graph/object) for more details on field names
selectGroups:
type: str
description:
- query
- Return a groups property with host groups data that the host belongs to.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/hostgroup/object) for more details on field names
selectHostDiscovery:
type: str
description:
- query
- Return a hostDiscovery property with host discovery object data.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/host/get) for more details on field names
selectHttpTests:
type: str
description:
- query
- Return an httpTests property with host web scenarios.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/httptest/object) for more details on field names
selectInterfaces:
type: str
description:
- query
- Return an interfaces property with host interfaces.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/hostinterface/object) for more details on field names
selectInventory:
type: str
description:
- query
- Return an inventory property with host inventory data.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/host/object#host_inventory) for more details on field names
selectItems:
type: str
description:
- query
- Return an items property with host items.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/item/object) for more details on field names
selectMacros:
type: str
description:
- query
- Return a macros property with host macros.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/usermacro/object) for more details on field names
selectParentTemplates:
type: str
description:
- query
- Return a parentTemplates property with templates that the host is linked to
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/template/object) for more details on field names
selectDashboards:
type: str
description:
- query
- Return a dashboards property.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/templatedashboard/object) for more details on field names
selectTags:
type: str
description:
- query
- Return a tags property with host tags.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/host/object#host_tag) for more details on field names
selectInheritedTags:
type: str
description:
- query
- Return an inheritedTags property with tags that are on all templates which are linked to host.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/host/object#host_tag) for more details on field names
selectTriggers:
type: str
description:
- query
- Return a triggers property with host triggers.
- To return all values specify 'extend'
- Can be limited to different fields for example setting the vaule to ['name'] will only return the name
- Additional fields can be specified by comma seperated value ['name', 'field2']
- Please see U(https://www.zabbix.com/documentation/current/manual/api/reference/host/object#host_tag) for more details on field names
login_user:
description:
- Zabbix user name.
type: str
required: true
env:
- name: ZABBIX_USERNAME
login_password:
description:
- Zabbix user password.
type: str
required: true
env:
- name: ZABBIX_PASSWORD
http_login_user:
description:
- Basic Auth login
type: str
http_login_password:
description:
- Basic Auth password
type: str
timeout:
description:
- The timeout of API request (seconds).
type: int
default: 10
validate_certs:
description:
- If set to False, SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
type: bool
default: true
env:
- name: ZABBIX_VALIDATE_CERTS
add_zabbix_groups:
description:
- If set to True, hosts will be added to groups based on their zabbix groups
type: bool
default: false
extends_documentation_fragment:
- constructed
- inventory_cache
'''
EXAMPLES = r'''
# Simple Inventory Plugin example
# This will create an inventory with details from zabbix such as applications name, applicaitonids, Parent Template Name, and group membership name
#It will also create 2 ansible inventory groups for enabled and disabled hosts in zabbix based on the status field.
plugin: community.zabbix.zabbix_inventory
server_url: https://zabbix.com
login_user: Admin
login_password: password
host_zapi_query:
selectApplications: ['name', 'applicationid']
selectParentTemplates: ['name']
selectGroups: ['name']
validate_certs: false
groups:
enabled: zbx_status == "0"
disabled: zbx_status == "1"
#Using Keyed Groups
plugin: community.zabbix.zabbix_inventory
server_url: https://zabbix.com
login_user: Admin
login_password: password
validate_certs: false
keyed_groups:
- key: zbx_status | lower
prefix: 'env'
- key: zbx_description | lower
prefix: 'test'
separator: ''
#Using proxy format of proxy is 'http://<user>:<pass>@<proxy>:<port>' or 'http://<proxy>:<port>'
plugin: community.zabbix.zabbix_inventory
server_url: https://zabbix.com
proxy: http://someproxy:8080
login_user: Admin
login_password: password
validate_certs: false
#Organize inventory groups based on zabbix host groups
plugin: community.zabbix.zabbix_inventory
server_url: https://zabbix.com
add_zabbix_groups: true
login_user: Admin
login_password: password
validate_certs: false
#Using compose to modify vars
plugin: community.zabbix.zabbix_inventory
server_url: https://zabbix.com
login_user: Admin
login_password: password
validate_certs: false
compose:
zbx_testvar: zbx_status.replace("1", "Disabled")
'''
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable, to_safe_group_name
import os
import atexit
import traceback
try:
from zabbix_api import ZabbixAPI
HAS_ZABBIX_API = True
except ImportError:
ZBX_IMP_ERR = traceback.format_exc()
HAS_ZABBIX_API = False
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = 'community.zabbix.zabbix_inventory'
def login_zabbix(self):
# set proxy information if required
proxy = self.get_option('proxy')
os.environ['http_proxy'] = proxy
os.environ['HTTP_PROXY'] = proxy
os.environ['https_proxy'] = proxy
os.environ['HTTPS_PROXY'] = proxy
server_url = self.get_option('server_url')
http_login_user = self.get_option('login_user')
http_login_password = self.get_option('login_password')
validate_certs = self.get_option('validate_certs')
timeout = self.get_option('timeout')
self._zapi = ZabbixAPI(server_url, timeout=timeout, user=http_login_user, passwd=http_login_password, validate_certs=validate_certs)
self.login()
self._zbx_api_version = self._zapi.api_version()[:5]
def login(self):
# check if api already logged in
if not self._zapi.auth != '':
try:
login_user = self.get_option('login_user')
login_password = self.get_option('login_password')
self._zapi.login(login_user, login_password)
atexit.register(self._zapi.logout)
except Exception as e:
self.display.vvv(msg="Failed to connect to Zabbix server: %s" % e)
def verify_file(self, path):
valid = False
if super(InventoryModule, self).verify_file(path):
if path.endswith(('zabbix_inventory.yaml', 'zabbix_inventory.yml')):
valid = True
else:
self.display.vvv(
'Skipping due to inventory source not ending in "zabbix_inventory.yaml" nor "zabbix_inventory.yml"')
return valid
def parse(self, inventory, loader, path,
cache=True): # Plugin interface (2)
super(InventoryModule, self).parse(inventory, loader, path)
self._read_config_data(path)
self.cache_key = self.get_cache_key(path)
self.use_cache = self.get_option('cache') and cache
self.update_cache = self.get_option('cache') and not cache
self.login_zabbix()
zapi_query = self.get_option('host_zapi_query')
content = self._zapi.host.get(zapi_query)
strict = self.get_option('strict')
for record in content:
# add host to inventory
host_name = self.inventory.add_host(record['host'])
# set variables for host
for k in record.keys():
self.inventory.set_variable(host_name, 'zbx_%s' % k, record[k])
# added for compose vars and keyed groups
self._set_composite_vars(
self.get_option('compose'),
self.inventory.get_host(host_name).get_vars(), host_name, strict)
self._add_host_to_composed_groups(self.get_option('groups'), dict(), host_name, strict)
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), dict(), host_name, strict)
# organize inventory by zabbix groups
if self.get_option('add_zabbix_groups'):
content = self._zapi.host.get({'selectGroups': ['name']})
for record in content:
host_name = record['host']
if len(record['groups']) >= 1:
for group in record['groups']:
group_name = to_safe_group_name(group['name'])
self.inventory.add_group(group_name)
self.inventory.add_child(group_name, host_name)

View File

@@ -0,0 +1,343 @@
# Vendored copy of distutils/version.py from CPython 3.9.5
#
# Implements multiple version numbering conventions for the
# Python Module Distribution Utilities.
#
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
#
"""Provides classes to represent module version numbers (one class for
each style of version numbering). There are currently two such classes
implemented: StrictVersion and LooseVersion.
Every version number class implements the following interface:
* the 'parse' method takes a string and parses it to some internal
representation; if the string is an invalid version number,
'parse' raises a ValueError exception
* the class constructor takes an optional string argument which,
if supplied, is passed to 'parse'
* __str__ reconstructs the string that was passed to 'parse' (or
an equivalent string -- ie. one that will generate an equivalent
version number instance)
* __repr__ generates Python code to recreate the version number instance
* _cmp compares the current instance with either another instance
of the same class or a string (which will be parsed to an instance
of the same class, thus must follow the same rules)
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
try:
RE_FLAGS = re.VERBOSE | re.ASCII
except AttributeError:
RE_FLAGS = re.VERBOSE
class Version:
"""Abstract base class for version numbering classes. Just provides
constructor (__init__) and reproducer (__repr__), because those
seem to be the same for all version numbering classes; and route
rich comparisons to _cmp.
"""
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __repr__(self):
return "%s ('%s')" % (self.__class__.__name__, str(self))
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c >= 0
# Interface for version-number classes -- must be implemented
# by the following classes (the concrete ones -- Version should
# be treated as an abstract class).
# __init__ (string) - create and take same action as 'parse'
# (string parameter is optional)
# parse (string) - convert a string representation to whatever
# internal representation is appropriate for
# this style of version numbering
# __str__ (self) - convert back to a string; should be very similar
# (if not identical to) the string supplied to parse
# __repr__ (self) - generate Python code to recreate
# the instance
# _cmp (self, other) - compare two version numbers ('other' may
# be an unparsed version string, or another
# instance of your version class)
class StrictVersion(Version):
"""Version numbering for anal retentives and software idealists.
Implements the standard interface for version number classes as
described above. A version number consists of two or three
dot-separated numeric components, with an optional "pre-release" tag
on the end. The pre-release tag consists of the letter 'a' or 'b'
followed by a number. If the numeric components of two version
numbers are equal, then one with a pre-release tag will always
be deemed earlier (lesser) than one without.
The following are valid version numbers (shown in the order that
would be obtained by sorting according to the supplied cmp function):
0.4 0.4.0 (these two are equivalent)
0.4.1
0.5a1
0.5b3
0.5
0.9.6
1.0
1.0.4a3
1.0.4b1
1.0.4
The following are examples of invalid version numbers:
1
2.7.2.2
1.3.a4
1.3pl1
1.3c4
The rationale for this version numbering system will be explained
in the distutils documentation.
"""
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
RE_FLAGS)
def parse(self, vstring):
match = self.version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease, prerelease_num) = \
match.group(1, 2, 4, 5, 6)
if patch:
self.version = tuple(map(int, [major, minor, patch]))
else:
self.version = tuple(map(int, [major, minor])) + (0,)
if prerelease:
self.prerelease = (prerelease[0], int(prerelease_num))
else:
self.prerelease = None
def __str__(self):
if self.version[2] == 0:
vstring = '.'.join(map(str, self.version[0:2]))
else:
vstring = '.'.join(map(str, self.version))
if self.prerelease:
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
return vstring
def _cmp(self, other):
if isinstance(other, str):
other = StrictVersion(other)
elif not isinstance(other, StrictVersion):
return NotImplemented
if self.version != other.version:
# numeric versions don't match
# prerelease stuff doesn't matter
if self.version < other.version:
return -1
else:
return 1
# have to compare prerelease
# case 1: neither has prerelease; they're equal
# case 2: self has prerelease, other doesn't; other is greater
# case 3: self doesn't have prerelease, other does: self is greater
# case 4: both have prerelease: must compare them!
if (not self.prerelease and not other.prerelease):
return 0
elif (self.prerelease and not other.prerelease):
return -1
elif (not self.prerelease and other.prerelease):
return 1
elif (self.prerelease and other.prerelease):
if self.prerelease == other.prerelease:
return 0
elif self.prerelease < other.prerelease:
return -1
else:
return 1
else:
raise AssertionError("never get here")
# end class StrictVersion
# The rules according to Greg Stein:
# 1) a version number has 1 or more numbers separated by a period or by
# sequences of letters. If only periods, then these are compared
# left-to-right to determine an ordering.
# 2) sequences of letters are part of the tuple for comparison and are
# compared lexicographically
# 3) recognize the numeric components may have leading zeroes
#
# The LooseVersion class below implements these rules: a version number
# string is split up into a tuple of integer and string components, and
# comparison is a simple tuple comparison. This means that version
# numbers behave in a predictable and obvious way, but a way that might
# not necessarily be how people *want* version numbers to behave. There
# wouldn't be a problem if people could stick to purely numeric version
# numbers: just split on period and compare the numbers as tuples.
# However, people insist on putting letters into their version numbers;
# the most common purpose seems to be:
# - indicating a "pre-release" version
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
# - indicating a post-release patch ('p', 'pl', 'patch')
# but of course this can't cover all version number schemes, and there's
# no way to know what a programmer means without asking him.
#
# The problem is what to do with letters (and other non-numeric
# characters) in a version number. The current implementation does the
# obvious and predictable thing: keep them as strings and compare
# lexically within a tuple comparison. This has the desired effect if
# an appended letter sequence implies something "post-release":
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
#
# However, if letters in a version number imply a pre-release version,
# the "obvious" thing isn't correct. Eg. you would expect that
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
# implemented here, this just isn't so.
#
# Two possible solutions come to mind. The first is to tie the
# comparison algorithm to a particular set of semantic rules, as has
# been done in the StrictVersion class above. This works great as long
# as everyone can go along with bondage and discipline. Hopefully a
# (large) subset of Python module programmers will agree that the
# particular flavour of bondage and discipline provided by StrictVersion
# provides enough benefit to be worth using, and will submit their
# version numbering scheme to its domination. The free-thinking
# anarchists in the lot will never give in, though, and something needs
# to be done to accommodate them.
#
# Perhaps a "moderately strict" version class could be implemented that
# lets almost anything slide (syntactically), and makes some heuristic
# assumptions about non-digits in version number strings. This could
# sink into special-case-hell, though; if I was as talented and
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
# just as happy dealing with things like "2g6" and "1.13++". I don't
# think I'm smart enough to do it right though.
#
# In any case, I've coded the test suite for this module (see
# ../test/test_version.py) specifically to fail on things like comparing
# "1.2a2" and "1.2". That's not because the *code* is doing anything
# wrong, it's because the simple, obvious design doesn't match my
# complicated, hairy expectations for real-world version numbers. It
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
# the Right Thing" (ie. the code matches the conception). But I'd rather
# have a conception that matches common notions about version numbers.
class LooseVersion(Version):
"""Version numbering for anarchists and software realists.
Implements the standard interface for version number classes as
described above. A version number consists of a series of numbers,
separated by either periods or strings of letters. When comparing
version numbers, the numeric components will be compared
numerically, and the alphabetic components lexically. The following
are all valid version numbers, in no particular order:
1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0
In fact, there is no such thing as an invalid version number under
this scheme; the rules for comparison are simple and predictable,
but may not always give the results you want (for some definition
of "want").
"""
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def parse(self, vstring):
# I've given up on thinking I can reconstruct the version string
# from the parsed tuple -- so I just store the string here for
# use by __str__
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != '.']
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
if isinstance(other, str):
other = LooseVersion(other)
elif not isinstance(other, LooseVersion):
return NotImplemented
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
# end class LooseVersion

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# (c) 2021, Markus Fischbacher (fischbacher.markus@gmail.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Quick Link to Zabbix API docs: https://www.zabbix.com/documentation/current/manual/api
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from uuid import uuid4
from ansible.module_utils.urls import CertificateError
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.connection import Connection
from ansible.module_utils._text import to_text
class ZabbixApiRequest(object):
def __init__(self, module):
self.module = module
self.connection = Connection(self.module._socket_path)
def _httpapi_error_handle(self, payload=None):
try:
code, response = self.connection.send_request(data=payload)
except ConnectionError as e:
self.module.fail_json(msg="connection error occurred: {0}".format(e))
except CertificateError as e:
self.module.fail_json(msg="certificate error occurred: {0}".format(e))
except ValueError as e:
self.module.fail_json(msg="certificate not found: {0}".format(e))
if code == 404:
if to_text(u"Object not found") in to_text(response) or to_text(
u"Could not find object"
) in to_text(response):
return {}
if not (code >= 200 and code < 300):
self.module.fail_json(
msg="Zabbix httpapi returned error {0} with message {1}".format(
code, response
)
)
return response
def api_version(self):
return self.connection.api_version()
@staticmethod
def payload_builder(method_, params, jsonrpc_version='2.0', reqid=str(uuid4()), **kwargs):
req = {'jsonrpc': jsonrpc_version, 'method': method_, 'id': reqid}
req['params'] = params
return req
def __getattr__(self, name):
return ZabbixApiSection(self, name)
class ZabbixApiSection(object):
parent = None
name = None
def __init__(self, parent, name):
self.name = name
self.parent = parent
def __getattr__(self, name):
def method(opts=None):
if self.name == "configuration" and name == "import_":
_method = "configuration.import"
else:
_method = "%s.%s" % (self.name, name)
if not opts:
opts = {}
payload = ZabbixApiRequest.payload_builder(_method, opts)
return self.parent._httpapi_error_handle(payload=payload)
return method

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.community.zabbix.plugins.module_utils.wrappers import ZapiWrapper
from ansible_collections.community.zabbix.plugins.module_utils.api_request import ZabbixApiRequest
class ZabbixBase(object):
"""
The base class for deriving off module classes
"""
def __init__(self, module, zbx=None, zapi_wrapper=None):
self._module = module
if module._socket_path is None:
# ansible_connection = local
if zapi_wrapper is None:
self._zapi_wrapper = ZapiWrapper(module, zbx)
else:
self._zapi_wrapper = zapi_wrapper
self._zapi = self._zapi_wrapper._zapi
self._zbx_api_version = self._zapi_wrapper._zbx_api_version
else:
# ansible_connection = httpapi
self._zapi = ZabbixApiRequest(module)
self._zbx_api_version = self._zapi.api_version()

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import env_fallback
def require_creds_params(module):
if module._socket_path is None:
# ansible_connection = local
if ((not module.params.get('server_url', None)) or (not module.params.get('login_user', None)) or (not module.params.get('login_password', None))):
module.fail_json(msg="server_url, login_user, login_password are mandatory parameters when httpapi connection is not used")
def zabbix_common_argument_spec():
"""
Return a dictionary with connection options.
The options are commonly used by most of Zabbix modules.
"""
return dict(
server_url=dict(
type='str',
required=False,
aliases=['url'],
fallback=(env_fallback, ['ZABBIX_SERVER'])
),
login_user=dict(
type='str', required=False,
fallback=(env_fallback, ['ZABBIX_USERNAME'])
),
login_password=dict(
type='str',
required=False,
no_log=True,
fallback=(env_fallback, ['ZABBIX_PASSWORD'])
),
http_login_user=dict(
type='str',
required=False,
default=None
),
http_login_password=dict(
type='str',
required=False,
default=None,
no_log=True
),
timeout=dict(
type='int'
),
validate_certs=dict(
type='bool',
required=False,
fallback=(env_fallback, ['ZABBIX_VALIDATE_CERTS'])
),
)
def helper_cleanup_data(obj):
"""
Removes the None values from the object and returns the object
Args:
obj: object to cleanup
Returns:
object: cleaned object
"""
if isinstance(obj, (list, tuple, set)):
return type(obj)(helper_cleanup_data(x) for x in obj if x is not None)
elif isinstance(obj, dict):
return type(obj)((helper_cleanup_data(k), helper_cleanup_data(v))
for k, v in obj.items() if k is not None and v is not None)
else:
return obj
def helper_to_numeric_value(elements, value):
"""Converts string values to integers
Parameters:
elements: list of elements to enumerate
value: string value
Returns:
int: converted integer
"""
if value is None:
return None
for index, element in enumerate(elements):
if isinstance(element, str) and element.lower() == value.lower():
return index
if isinstance(element, list):
for deep_element in element:
if isinstance(deep_element, str) and deep_element.lower() == value.lower():
return index
def helper_convert_unicode_to_str(data):
"""Converts unicode objects to strings in dictionary
Parameters:
data: unicode object
Returns:
dict: strings in dictionary
"""
if isinstance(data, dict):
return dict(map(helper_convert_unicode_to_str, data.items()))
elif isinstance(data, (list, tuple, set)):
return type(data)(map(helper_convert_unicode_to_str, data))
elif data is None:
return data
else:
return str(data)
def helper_compare_lists(l1, l2, diff_dict):
"""
Compares l1 and l2 lists and adds the items that are different
to the diff_dict dictionary.
Used in recursion with helper_compare_dictionaries() function.
Parameters:
l1: first list to compare
l2: second list to compare
diff_dict: dictionary to store the difference
Returns:
dict: items that are different
"""
if len(l1) != len(l2):
diff_dict.append(l1)
return diff_dict
for i, item in enumerate(l1):
if isinstance(item, dict):
for item2 in l2:
diff_dict2 = {}
diff_dict2 = helper_compare_dictionaries(item, item2, diff_dict2)
if len(diff_dict2) == 0:
break
if len(diff_dict2) != 0:
diff_dict.insert(i, item)
else:
if item != l2[i]:
diff_dict.append(item)
while {} in diff_dict:
diff_dict.remove({})
return diff_dict
def helper_compare_dictionaries(d1, d2, diff_dict):
"""
Compares d1 and d2 dictionaries and adds the items that are different
to the diff_dict dictionary.
Used in recursion with helper_compare_lists() function.
Parameters:
d1: first dictionary to compare
d2: second dictionary to compare
diff_dict: dictionary to store the difference
Returns:
dict: items that are different
"""
for k, v in d1.items():
if k not in d2:
diff_dict[k] = v
continue
if isinstance(v, dict):
diff_dict[k] = {}
helper_compare_dictionaries(v, d2[k], diff_dict[k])
if diff_dict[k] == {}:
del diff_dict[k]
else:
diff_dict[k] = v
elif isinstance(v, list):
diff_dict[k] = []
helper_compare_lists(v, d2[k], diff_dict[k])
if diff_dict[k] == []:
del diff_dict[k]
else:
diff_dict[k] = v
else:
if v != d2[k]:
diff_dict[k] = v
return diff_dict
def helper_normalize_data(data, del_keys=None):
"""
Delete None parameter or specified keys from data.
Parameters:
data: dictionary
Returns:
data: falsene parameter removed data
del_keys: deleted keys
"""
if del_keys is None:
del_keys = []
for key, value in data.items():
if value is None:
del_keys.append(key)
for key in del_keys:
if key in data.keys():
del data[key]
return data, del_keys

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
# remove the _version.py file, and replace the following import by
#
# from ansible.module_utils.compat.version import LooseVersion
from ._version import LooseVersion

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import atexit
import traceback
from ansible.module_utils.basic import missing_required_lib
try:
from zabbix_api import ZabbixAPI, Already_Exists, ZabbixAPIException
HAS_ZABBIX_API = True
ZBX_IMP_ERR = Exception()
except ImportError:
ZBX_IMP_ERR = traceback.format_exc()
HAS_ZABBIX_API = False
class ZapiWrapper(object):
"""
A simple wrapper over the Zabbix API
"""
def __init__(self, module, zbx=None):
self._module = module
if not HAS_ZABBIX_API:
module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR)
# check if zbx is already instantiated or not
if zbx is not None and isinstance(zbx, ZabbixAPI):
self._zapi = zbx
else:
server_url = module.params['server_url']
if module.params['validate_certs'] is None:
validate_certs = True
else:
validate_certs = module.params['validate_certs']
if module.params['timeout'] is None:
timeout = 10
else:
timeout = module.params['timeout']
self._zapi = ZabbixAPI(server_url, timeout=timeout, validate_certs=validate_certs)
self.login()
self._zbx_api_version = self._zapi.api_version()
def login(self):
# check if api already logged in
if not self._zapi.auth != '':
try:
login_user = self._module.params['login_user']
login_password = self._module.params['login_password']
self._zapi.login(login_user, login_password)
atexit.register(self._zapi.logout)
except Exception as e:
self._module.fail_json(msg="Failed to connect to Zabbix server: %s" % e)
class ScreenItem(object):
@staticmethod
def create(zapi_wrapper, data, ignoreExists=False):
try:
zapi_wrapper._zapi.screenitem.create(data)
except Already_Exists as ex:
if not ignoreExists:
raise ex
@staticmethod
def delete(zapi_wrapper, id_list=None):
try:
if id_list is None:
id_list = []
zapi_wrapper._zapi.screenitem.delete(id_list)
except ZabbixAPIException as ex:
raise ex

View File

@@ -0,0 +1,638 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2022, ONODERA Masaru <masaru-onodera@ieee.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: zabbix_authentication
short_description: Update Zabbix authentication
description:
- This module allows you to modify Zabbix authentication setting.
author:
- ONODERA Masaru(@masa-orca)
requirements:
- "python >= 2.6"
version_added: 1.6.0
options:
authentication_type:
description:
- Choose default authentication type.
required: false
type: str
choices: [ "internal", "ldap" ]
http_auth_enabled:
description:
- HTTP authentication will be enabled if C(true).
required: false
type: bool
http_login_form:
description:
- Choose default login form.
required: false
type: str
choices: [ "zabbix_login_form", "http_login_form" ]
http_strip_domains:
description:
- A list of domain names that should be removed from the username.
required: false
type: list
elements: str
http_case_sensitive:
description:
- Case sensitive login for HTTP authentication will be enabled if C(true).
required: false
type: bool
ldap_configured:
description:
- LDAP authentication will be enabled if C(true).
required: false
type: bool
ldap_host:
description:
- LDAP server name.
- e.g. C(ldap://ldap.zabbix.com)
- This setting is required if current value of I(ldap_configured) is C(false).
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: str
ldap_port:
description:
- A port number of LDAP server.
- This setting is required if current value of I(ldap_configured) is C(false).
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: int
ldap_base_dn:
description:
- Base DN of LDAP.
- This setting is required if current value of I(ldap_configured) is C(false).
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: str
ldap_search_attribute:
description:
- Search attribute of LDAP.
- This setting is required if current value of I(ldap_configured) is C(false).
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: str
ldap_bind_dn:
description:
- Bind DN of LDAP.
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: str
ldap_case_sensitive:
description:
- case sensitive login for LDAP authentication will be enabled if C(true).
required: false
type: bool
ldap_bind_password:
description:
- Bind password of LDAP.
- Works only with Zabbix <= 6.0 and is silently ignored in higher versions.
required: false
type: str
ldap_userdirectory:
description:
- LDAP authentication default user directory name for user groups with gui_access set to LDAP or System default.
- Required to be set when C(ldap_configured) is set to 1.
required: false
type: str
saml_auth_enabled:
description:
- SAML authentication will be enabled if C(true).
required: false
type: bool
saml_idp_entityid:
description:
- SAML identify provider's entity ID.
- This setting is required if current value of I(saml_auth_enabled) is C(false).
required: false
type: str
saml_sso_url:
description:
- URL for single sign on service of SAML.
- This setting is required if current value of I(saml_auth_enabled) is C(false).
required: false
type: str
saml_slo_url:
description:
- URL for SAML single logout service.
required: false
type: str
saml_username_attribute:
description:
- User name attribute of SAML.
- This setting is required if current value of I(saml_auth_enabled) is C(false).
required: false
type: str
saml_sp_entityid:
description:
- Entity ID of SAML service provider.
- This setting is required if current value of I(saml_auth_enabled) is C(false).
required: false
type: str
saml_nameid_format:
description:
- Name identifier format of SAML service provider.
required: false
type: str
saml_sign_messages:
description:
- SAML sign messages will be enabled if C(true).
required: false
type: bool
saml_sign_assertions:
description:
- SAML sign assertions will be enabled if C(true).
required: false
type: bool
saml_sign_authn_requests:
description:
- SAML sign AuthN requests will be enabled if C(true).
required: false
type: bool
saml_sign_logout_requests:
description:
- SAML sign logout requests will be enabled if C(true).
required: false
type: bool
saml_sign_logout_responses:
description:
- SAML sign logout responses will be enabled if C(true).
required: false
type: bool
saml_encrypt_nameid:
description:
- SAML encrypt name ID will be enabled if C(true).
required: false
type: bool
saml_encrypt_assertions:
description:
- SAML encrypt assertions will be enabled if C(true).
required: false
type: bool
saml_case_sensitive:
description:
- Case sensitive login for SAML authentication will be enabled if C(true).
required: false
type: bool
passwd_min_length:
description:
- Minimal length of password.
- Choose from 1-70.
- This parameter is available since Zabbix 6.0.
required: false
type: int
passwd_check_rules:
description:
- Checking password rules.
- Select multiple from C(contain_uppercase_and_lowercase_letters),
C(contain_digits). C(contain_special_characters) and C(avoid_easy_to_guess).
- This parameter is available since Zabbix 6.0.
required: false
type: list
elements: str
notes:
- Zabbix 5.4 version and higher are supported.
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Update all authentication setting
zabbix_authentication:
authentication_type: internal
http_auth_enabled: true
http_login_form: zabbix_login_form
http_strip_domains:
- comp
- any
http_case_sensitive: true
ldap_configured: true
ldap_host: 'ldap://localhost'
ldap_port: 389
ldap_base_dn: 'ou=Users,ou=system'
ldap_search_attribute: 'uid'
ldap_bind_dn: 'uid=ldap_search,ou=system'
ldap_case_sensitive: true
ldap_bind_password: 'password'
saml_auth_enabled: true
saml_idp_entityid: ''
saml_sso_url: 'https://localhost/SAML2/SSO'
saml_slo_url: 'https://localhost/SAML2/SLO'
saml_username_attribute: 'uid'
saml_sp_entityid: 'https://localhost'
saml_nameid_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
saml_sign_messages: true
saml_sign_assertions: true
saml_sign_authn_requests: true
saml_sign_logout_requests: true
saml_sign_logout_responses: true
saml_encrypt_nameid: true
saml_encrypt_assertions: true
saml_case_sensitive: true
passwd_min_length: 70
passwd_check_rules:
- contain_uppercase_and_lowercase_letters
- contain_digits
- contain_special_characters
- avoid_easy_to_guess
'''
RETURN = '''
msg:
description: The result of the operation
returned: success
type: str
sample: 'Successfully update authentication setting'
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Authentication(ZabbixBase):
def __init__(self, module, zbx=None, zapi_wrapper=None):
super(Authentication, self).__init__(module, zbx, zapi_wrapper)
if LooseVersion(self._zbx_api_version) < LooseVersion('5.4.0'):
module.fail_json(msg="This module doesn't support Zabbix versions lower than 5.4.0")
# get authentication setting
def get_authentication(self):
try:
return self._zapi.authentication.get({'output': 'extend'})
except Exception as e:
self._module.fail_json(msg="Failed to get authentication setting: %s" % e)
# update authentication setting
def update_authentication(
self,
current_authentication,
authentication_type,
http_auth_enabled,
http_login_form,
http_strip_domains,
http_case_sensitive,
ldap_configured,
ldap_host,
ldap_port,
ldap_base_dn,
ldap_search_attribute,
ldap_bind_dn,
ldap_case_sensitive,
ldap_bind_password,
ldap_userdirectory,
saml_auth_enabled,
saml_idp_entityid,
saml_sso_url,
saml_slo_url,
saml_username_attribute,
saml_sp_entityid,
saml_nameid_format,
saml_sign_messages,
saml_sign_assertions,
saml_sign_authn_requests,
saml_sign_logout_requests,
saml_sign_logout_responses,
saml_encrypt_nameid,
saml_encrypt_assertions,
saml_case_sensitive,
passwd_min_length,
passwd_check_rules):
try:
params = {}
if authentication_type:
params['authentication_type'] = str(zabbix_utils.helper_to_numeric_value(
['internal', 'ldap'],
authentication_type
))
if isinstance(http_auth_enabled, bool):
params['http_auth_enabled'] = str(int(http_auth_enabled))
if http_login_form:
params['http_login_form'] = str(zabbix_utils.helper_to_numeric_value(
['zabbix_login_form', 'http_login_form'],
http_login_form
))
if http_strip_domains:
params['http_strip_domains'] = ','.join(http_strip_domains)
if isinstance(http_case_sensitive, bool):
params['http_case_sensitive'] = str(int(http_case_sensitive))
if isinstance(ldap_configured, bool):
params['ldap_configured'] = str(int(ldap_configured))
if LooseVersion(self._zbx_api_version) < LooseVersion('6.2.0'):
if ldap_host:
params['ldap_host'] = ldap_host
if ldap_port:
params['ldap_port'] = str(ldap_port)
if ldap_base_dn:
params['ldap_base_dn'] = ldap_base_dn
if ldap_search_attribute:
params['ldap_search_attribute'] = ldap_search_attribute
if ldap_bind_dn:
params['ldap_bind_dn'] = ldap_bind_dn
if ldap_bind_password:
params['ldap_bind_password'] = ldap_bind_password
else:
if ldap_userdirectory:
directory = self._zapi.userdirectory.get({'filter': {'name': ldap_userdirectory}})
if not directory:
self._module.fail_json(msg="Canot find user directory with name: %s" % ldap_userdirectory)
params['ldap_userdirectoryid'] = directory[0]['userdirectoryid']
if isinstance(ldap_case_sensitive, bool):
params['ldap_case_sensitive'] = str(int(ldap_case_sensitive))
if isinstance(saml_auth_enabled, bool):
params['saml_auth_enabled'] = str(int(saml_auth_enabled))
if saml_idp_entityid:
params['saml_idp_entityid'] = saml_idp_entityid
if saml_sso_url:
params['saml_sso_url'] = saml_sso_url
if saml_slo_url:
params['saml_slo_url'] = saml_slo_url
if saml_username_attribute:
params['saml_username_attribute'] = saml_username_attribute
if saml_sp_entityid:
params['saml_sp_entityid'] = saml_sp_entityid
if saml_nameid_format:
params['saml_nameid_format'] = saml_nameid_format
if isinstance(saml_sign_messages, bool):
params['saml_sign_messages'] = str(int(saml_sign_messages))
if isinstance(saml_sign_assertions, bool):
params['saml_sign_assertions'] = str(int(saml_sign_assertions))
if isinstance(saml_sign_authn_requests, bool):
params['saml_sign_authn_requests'] = str(int(saml_sign_authn_requests))
if isinstance(saml_sign_logout_requests, bool):
params['saml_sign_logout_requests'] = str(int(saml_sign_logout_requests))
if isinstance(saml_sign_logout_responses, bool):
params['saml_sign_logout_responses'] = str(int(saml_sign_logout_responses))
if isinstance(saml_encrypt_nameid, bool):
params['saml_encrypt_nameid'] = str(int(saml_encrypt_nameid))
if isinstance(saml_encrypt_assertions, bool):
params['saml_encrypt_assertions'] = str(int(saml_encrypt_assertions))
if isinstance(saml_case_sensitive, bool):
params['saml_case_sensitive'] = str(int(saml_case_sensitive))
if passwd_min_length:
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
self._module.warn('passwd_min_length is ignored with Zabbix 5.4.')
elif passwd_min_length < 1 or passwd_min_length > 70:
self._module.fail_json(msg="Please set 0-70 to passwd_min_length.")
else:
params['passwd_min_length'] = str(passwd_min_length)
if passwd_check_rules:
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
self._module.warn('passwd_check_rules is ignored with Zabbix 5.4.')
else:
passwd_check_rules_values = [
'contain_uppercase_and_lowercase_letters',
'contain_digits',
'contain_special_characters',
'avoid_easy_to_guess'
]
params['passwd_check_rules'] = 0
if isinstance(passwd_check_rules, str):
if passwd_check_rules not in passwd_check_rules_values:
self._module.fail_json(msg="%s is invalid value for passwd_check_rules." % passwd_check_rules)
params['passwd_check_rules'] += 2 ** zabbix_utils.helper_to_numeric_value(
passwd_check_rules_values, passwd_check_rules
)
elif isinstance(passwd_check_rules, list):
for _passwd_check_rules_value in passwd_check_rules:
if _passwd_check_rules_value not in passwd_check_rules_values:
self._module.fail_json(msg="%s is invalid value for passwd_check_rules." % _passwd_check_rules_value)
params['passwd_check_rules'] += 2 ** zabbix_utils.helper_to_numeric_value(
passwd_check_rules_values, _passwd_check_rules_value
)
params['passwd_check_rules'] = str(params['passwd_check_rules'])
future_authentication = current_authentication.copy()
future_authentication.update(params)
if (current_authentication['ldap_configured'] == '0'
and future_authentication['ldap_configured'] == '1'):
if LooseVersion(self._zbx_api_version) < LooseVersion('6.2.0'):
if (not ldap_host
or not ldap_port
or not ldap_search_attribute
or not ldap_base_dn):
self._module.fail_json(
msg="Please set ldap_host, ldap_search_attribute and ldap_base_dn when you change a value of ldap_configured to true."
)
else:
if not ldap_userdirectory:
self._module.fail_json(msg="Please set ldap_userdirectory when you change a value of ldap_configured to true.")
if (current_authentication['saml_auth_enabled'] == '0'
and future_authentication['saml_auth_enabled'] == '1'
and not saml_idp_entityid
and not saml_sso_url
and not saml_username_attribute
and not saml_sp_entityid):
self._module.fail_json(
msg=' '.join([
"Please set saml_idp_entityid, saml_sso_url, saml_username_attribute and saml_sp_entityid",
"when you change a value of saml_auth_enabled to true."
])
)
if future_authentication != current_authentication:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.authentication.update(params)
self._module.exit_json(changed=True, result="Successfully update authentication setting")
else:
self._module.exit_json(changed=False, result="Authentication setting is already up to date")
except Exception as e:
self._module.fail_json(msg="Failed to update authentication setting, Exception: %s" % e)
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
authentication_type=dict(type='str', choices=['internal', 'ldap']),
http_auth_enabled=dict(type='bool'),
http_login_form=dict(type='str', choices=['zabbix_login_form', 'http_login_form']),
http_strip_domains=dict(type='list', elements='str'),
http_case_sensitive=dict(type='bool'),
ldap_configured=dict(type='bool'),
ldap_host=dict(type='str'),
ldap_port=dict(type='int'),
ldap_base_dn=dict(type='str'),
ldap_search_attribute=dict(type='str'),
ldap_bind_dn=dict(type='str'),
ldap_case_sensitive=dict(type='bool'),
ldap_bind_password=dict(type='str', no_log=True),
ldap_userdirectory=dict(type='str'),
saml_auth_enabled=dict(type='bool'),
saml_idp_entityid=dict(type='str'),
saml_sso_url=dict(type='str'),
saml_slo_url=dict(type='str'),
saml_username_attribute=dict(type='str'),
saml_sp_entityid=dict(type='str'),
saml_nameid_format=dict(type='str'),
saml_sign_messages=dict(type='bool'),
saml_sign_assertions=dict(type='bool'),
saml_sign_authn_requests=dict(type='bool'),
saml_sign_logout_requests=dict(type='bool'),
saml_sign_logout_responses=dict(type='bool'),
saml_encrypt_nameid=dict(type='bool'),
saml_encrypt_assertions=dict(type='bool'),
saml_case_sensitive=dict(type='bool'),
passwd_min_length=dict(type='int', no_log=False),
passwd_check_rules=dict(type='list', elements='str', no_log=False)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
authentication_type = module.params['authentication_type']
http_auth_enabled = module.params['http_auth_enabled']
http_login_form = module.params['http_login_form']
http_strip_domains = module.params['http_strip_domains']
http_case_sensitive = module.params['http_case_sensitive']
ldap_configured = module.params['ldap_configured']
ldap_host = module.params['ldap_host']
ldap_port = module.params['ldap_port']
ldap_base_dn = module.params['ldap_base_dn']
ldap_search_attribute = module.params['ldap_search_attribute']
ldap_bind_dn = module.params['ldap_bind_dn']
ldap_case_sensitive = module.params['ldap_case_sensitive']
ldap_bind_password = module.params['ldap_bind_password']
ldap_userdirectory = module.params['ldap_userdirectory']
saml_auth_enabled = module.params['saml_auth_enabled']
saml_idp_entityid = module.params['saml_idp_entityid']
saml_sso_url = module.params['saml_sso_url']
saml_slo_url = module.params['saml_slo_url']
saml_username_attribute = module.params['saml_username_attribute']
saml_sp_entityid = module.params['saml_sp_entityid']
saml_nameid_format = module.params['saml_nameid_format']
saml_sign_messages = module.params['saml_sign_messages']
saml_sign_assertions = module.params['saml_sign_assertions']
saml_sign_authn_requests = module.params['saml_sign_authn_requests']
saml_sign_logout_requests = module.params['saml_sign_logout_requests']
saml_sign_logout_responses = module.params['saml_sign_logout_responses']
saml_encrypt_nameid = module.params['saml_encrypt_nameid']
saml_encrypt_assertions = module.params['saml_encrypt_assertions']
saml_case_sensitive = module.params['saml_case_sensitive']
passwd_min_length = module.params['passwd_min_length']
passwd_check_rules = module.params['passwd_check_rules']
authentication = Authentication(module)
current_authentication = authentication.get_authentication()
authentication.update_authentication(
current_authentication,
authentication_type,
http_auth_enabled,
http_login_form,
http_strip_domains,
http_case_sensitive,
ldap_configured,
ldap_host,
ldap_port,
ldap_base_dn,
ldap_search_attribute,
ldap_bind_dn,
ldap_case_sensitive,
ldap_bind_password,
ldap_userdirectory,
saml_auth_enabled,
saml_idp_entityid,
saml_sso_url,
saml_slo_url,
saml_username_attribute,
saml_sp_entityid,
saml_nameid_format,
saml_sign_messages,
saml_sign_assertions,
saml_sign_authn_requests,
saml_sign_logout_requests,
saml_sign_logout_responses,
saml_encrypt_nameid,
saml_encrypt_assertions,
saml_case_sensitive,
passwd_min_length,
passwd_check_rules
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,215 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: zabbix_autoregister
short_description: Update Zabbix autoregistration
description:
- This module allows you to modify Zabbix autoregistration.
author:
- ONODERA Masaru(@masa-orca)
requirements:
- "python >= 2.6"
version_added: 1.6.0
options:
tls_accept:
description:
- Type of allowed incoming connections for autoregistration.
- Choose from C(unsecure), C(tls_with_psk) or both.
type: list
elements: str
required: true
tls_psk_identity:
description:
- TLS connection uses this PSK identity string.
- The PSK identity string will be transmitted unencrypted over the network. Therefore, you should not put any sensitive information here.
- This setting requires I(tls_accept=tls_with_psk) if current value of I(tls_accept) is C(unsecure).
type: str
tls_psk:
description:
- TLS connection uses this PSK value.
- This setting requires I(tls_accept=tls_with_psk) if current value of I(tls_accept) is C(unsecure).
type: str
notes:
- Only Zabbix >= 4.4 is supported.
- This module returns changed=true when any value is set in I(tls_psk_identity) or I(tls_psk) as Zabbix API
will not return any sensitive information back for module to compare.
- Please note that this module configures B(global Zabbix Server settings).
If you want to create autoregistration action so your hosts can automatically add themselves
to the monitoring have a look at M(community.zabbix.zabbix_action).
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Update autoregistration
community.zabbix.zabbix_autoregister:
server_url: "http://zabbix.example.com/zabbix/"
login_user: Admin
login_password: secret
tls_accept:
- unsecure
- tls_with_psk
tls_psk_identity: 'PSK 001'
tls_psk: "11111595725ac58dd977beef14b97461a7c1045b9a1c923453302c5473193478"
- name: Set unsecure to tls_accept
community.zabbix.zabbix_autoregister:
server_url: "http://zabbix.example.com/zabbix/"
login_user: Admin
login_password: secret
tls_accept: unsecure
'''
RETURN = '''
msg:
description: The result of the operation
returned: success
type: str
sample: 'Successfully updated global autoregistration setting'
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Autoregistration(ZabbixBase):
def __init__(self, module, zbx=None, zapi_wrapper=None):
super(Autoregistration, self).__init__(module, zbx, zapi_wrapper)
if LooseVersion(self._zbx_api_version) < LooseVersion('4.4.0'):
module.fail_json(msg="This module doesn't support Zabbix versions lower than 4.4.0")
# get autoregistration
def get_autoregistration(self):
try:
return self._zapi.autoregistration.get({"output": 'extend'})
except Exception as e:
self._module.fail_json(msg="Failed to get autoregistration: %s" % e)
# update autoregistration
def update_autoregistration(self, current_setting, tls_accept, tls_psk_identity, tls_psk):
tls_accept_values = [
None,
'unsecure',
'tls_with_psk'
]
params = {}
try:
if isinstance(tls_accept, str):
params['tls_accept'] = zabbix_utils.helper_to_numeric_value(
tls_accept_values, tls_accept
)
elif isinstance(tls_accept, list):
params['tls_accept'] = 0
for _tls_accept_value in tls_accept:
params['tls_accept'] += zabbix_utils.helper_to_numeric_value(
tls_accept_values, _tls_accept_value
)
else:
self._module.fail_json(msg="Value of tls_accept must be list or string.")
if tls_psk_identity:
params['tls_psk_identity'] = tls_psk_identity
if tls_psk:
params['tls_psk'] = tls_psk
current_tls_accept = int(current_setting['tls_accept'])
if (current_tls_accept == tls_accept_values.index('unsecure')
and params['tls_accept'] >= tls_accept_values.index('tls_with_psk')):
if not tls_psk_identity or not tls_psk:
self._module.fail_json(msg="Please set tls_psk_identity and tls_psk.")
if (not tls_psk_identity and not tls_psk
and params['tls_accept'] == current_tls_accept):
self._module.exit_json(changed=False, result="Autoregistration is already up to date")
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.autoregistration.update(params)
self._module.exit_json(changed=True, result="Successfully updated global autoregistration setting")
except Exception as e:
self._module.fail_json(msg="Failed to update autoregistration: %s" % e)
def main():
"""Main ansible module function
"""
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
tls_accept=dict(
type='list',
elements='str',
required=True
),
tls_psk_identity=dict(type='str', required=False, no_log=True),
tls_psk=dict(type='str', required=False, no_log=True),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
tls_accept = module.params['tls_accept']
tls_psk_identity = module.params['tls_psk_identity']
tls_psk = module.params['tls_psk']
autoregistration_class_obj = Autoregistration(module)
current_setting = autoregistration_class_obj.get_autoregistration()
autoregistration_class_obj.update_autoregistration(current_setting, tls_accept, tls_psk_identity, tls_psk)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,710 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Tobias Birkefeld (@tcraxs) <t@craxs.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_discovery_rule
short_description: Create/delete/update Zabbix discovery rules
description:
- Create discovery rule.
- Delete existing discovery rule.
- Update existing discovery rule with new options.
author:
- "Tobias Birkefeld (@tcraxs)"
requirements:
- "python >= 2.6"
options:
state:
description:
- Create or delete discovery rules.
type: str
default: "present"
choices: [ "present", "absent" ]
name:
description:
- Name of the discovery rule.
required: true
type: str
iprange:
description:
- One or several IP ranges to check separated by commas.
type: list
elements: str
dchecks:
description:
- List of dictionaries of discovery check objects.
- For more information, review discovery check object documentation at
U(https://www.zabbix.com/documentation/current/manual/api/reference/dcheck/object)
suboptions:
type:
description:
- Type of check.
type: str
choices: ['SSH',
'LDAP',
'SMTP',
'FTP',
'HTTP',
'POP',
'NNTP',
'IMAP',
'TCP',
'Zabbix',
'SNMPv1',
'SNMPv2',
'ICMP',
'SNMPv3',
'HTTPS',
'Telnet']
ports:
description:
- One or several port ranges to check separated by commas. Used for all checks except for ICMP.
type: str
key:
description:
- "The value of this property differs depending on the type of the check:"
- "- key to query for Zabbix agent checks"
- "- SNMP OID for SNMPv1, SNMPv2 and SNMPv3 checks"
type: str
snmp_community:
description:
- SNMP community.
- Required for SNMPv1 and SNMPv2 agent checks.
type: str
snmpv3_authpassphrase:
description:
- Authentication passphrase used for SNMPv3 agent checks with security level set to authNoPriv or authPriv.
type: str
snmpv3_authprotocol:
description:
- Authentication protocol used for SNMPv3 agent checks with security level set to authNoPriv or authPriv.
- "Possible values:"
- MD5
- SHA
type: str
choices: ["MD5", "SHA"]
snmpv3_contextname:
description:
- SNMPv3 context name. Used only by SNMPv3 checks.
type: str
snmpv3_privpassphrase:
description:
- Privacy passphrase used for SNMPv3 agent checks with security level set to authPriv.
type: str
snmpv3_privprotocol:
description:
- Privacy protocol used for SNMPv3 agent checks with security level set to authPriv.
- "Possible values:"
- DES
- AES
type: str
choices: ["DES", "AES"]
snmpv3_securitylevel:
description:
- Security level used for SNMPv3 agent checks.
- "Possible values:"
- noAuthNoPriv
- authNoPriv
- authPriv
type: str
choices: ["noAuthNoPriv", "authNoPriv", "authPriv"]
snmpv3_securityname:
description:
- Security name used for SNMPv3 agent checks.
type: str
uniq:
description:
- Whether to use this check as a device uniqueness criteria.
- Only a single unique check can be configured for a discovery rule.
- Used for Zabbix agent, SNMPv1, SNMPv2 and SNMPv3 agent checks.
- "Possible values:"
- "no - (default) do not use this check as a uniqueness criteria"
- "yes - use this check as a uniqueness criteria"
type: bool
default: no
host_source:
description:
- Source for host name.
- "Possible values:"
- "DNS (default)"
- "IP"
- "discovery - discovery value of this check"
- Options is available since Zabbix 4.4
type: str
default: "DNS"
choices: ["DNS", "IP", "discovery"]
name_source:
description:
- Source for visible name.
- "Possible values:"
- "none - (default) not specified"
- "DNS"
- "IP"
- "discovery - discovery value of this check"
- Options is available since Zabbix 4.4
type: str
default: "None"
choices: ["None", "DNS", "IP", "discovery"]
type: list
elements: dict
aliases: [ "dcheck" ]
delay:
description:
- Execution interval of the discovery rule.
- Accepts seconds, time unit with suffix and user macro.
type: str
default: "1h"
proxy:
description:
- Name of the proxy used for discovery.
type: str
status:
description:
- Whether the discovery rule is enabled.
- "Possible values:"
- enabled (default)
- disabled
type: str
default: "enabled"
choices: ["enabled", "disabled"]
notes:
- Only Zabbix >= 4.0 is supported.
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Base create discovery rule example
- name: Create discovery rule with ICMP and zabbix agent checks
community.zabbix.zabbix_discovery_rule:
name: ACME
state: present
iprange: 192.168.1.1-255
dchecks:
- type: ICMP
- type: Zabbix
key: "system.hostname"
ports: 10050
uniq: yes
host_source: "discovery"
# Base update (add new dcheck) discovery rule example
- name: Create discovery rule with ICMP and zabbix agent checks
community.zabbix.zabbix_discovery_rule:
name: ACME
state: present
iprange: 192.168.1.1-255
dchecks:
- type: SNMPv3
snmp_community: CUSTOMER@snmp3-readonly
ports: "161"
key: iso.3.6.1.2.1.1.1.0
snmpv3_contextname: "ContextName"
snmpv3_securityname: "SecurityName"
snmpv3_securitylevel: "authPriv"
snmpv3_authprotocol: "SHA"
snmpv3_authpassphrase: "SeCrEt"
snmpv3_privprotocol: "AES"
snmpv3_privpassphrase: "TopSecret"
uniq: no
host_source: "DNS"
name_source: "None"
# Base delete discovery rule example
- name: Delete discovery rule
community.zabbix.zabbix_discovery_rule:
name: ACME
state: absent
'''
RETURN = r'''
state:
description: Discovery rule state at the end of execution.
returned: on success
type: str
sample: 'present'
drule:
description: Discovery rule name.
returned: on success
type: str
sample: 'ACME'
druleid:
description: Discovery rule id.
returned: on success
type: str
sample: '42'
msg:
description: The result of the operation
returned: always
type: str
sample: 'Discovery rule created: ACME, ID: 42'
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Dchecks(ZabbixBase):
"""
Restructures the user defined discovery checks to fit the Zabbix API requirements
"""
def construct_the_data(self, _dchecks):
"""Construct the user defined discovery check to fit the Zabbix API
requirements
Args:
_dchecks: discovery checks to construct
Returns:
dict: user defined discovery checks
"""
if _dchecks is None:
return None
constructed_data = []
for check in _dchecks:
constructed_check = {
'type': zabbix_utils.helper_to_numeric_value([
'SSH',
'LDAP',
'SMTP',
'FTP',
'HTTP',
'POP',
'NNTP',
'IMAP',
'TCP',
'Zabbix',
'SNMPv1',
'SNMPv2',
'ICMP',
'SNMPv3',
'HTTPS',
'Telnet'], check.get('type')
),
'uniq': int(check.get('uniq'))
}
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.4'):
constructed_check.update({
'host_source': zabbix_utils.helper_to_numeric_value([
'None',
'DNS',
'IP',
'discovery'], check.get('host_source')
),
'name_source': zabbix_utils.helper_to_numeric_value([
'None',
'DNS',
'IP',
'discovery'], check.get('name_source')
)
})
if constructed_check['type'] in (0, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15):
constructed_check['ports'] = check.get('ports')
if constructed_check['type'] == 9:
constructed_check['ports'] = check.get('ports')
constructed_check['key_'] = check.get('key')
if constructed_check['type'] in (10, 11):
constructed_check['ports'] = check.get('ports')
constructed_check['snmp_community'] = check.get('snmp_community')
constructed_check['key_'] = check.get('key')
if constructed_check['type'] == 13:
constructed_check['ports'] = check.get('ports')
constructed_check['key_'] = check.get('key')
constructed_check['snmpv3_contextname'] = check.get('snmpv3_contextname')
constructed_check['snmpv3_securityname'] = check.get('snmpv3_securityname')
constructed_check['snmpv3_securitylevel'] = zabbix_utils.helper_to_numeric_value([
'noAuthNoPriv',
'authNoPriv',
'authPriv'], check.get('snmpv3_securitylevel')
)
if constructed_check['snmpv3_securitylevel'] in (1, 2):
constructed_check['snmpv3_authprotocol'] = zabbix_utils.helper_to_numeric_value([
'MD5',
'SHA'], check.get('snmpv3_authprotocol')
)
constructed_check['snmpv3_authpassphrase'] = check.get('snmpv3_authpassphrase')
if constructed_check['snmpv3_securitylevel'] == 2:
constructed_check['snmpv3_privprotocol'] = zabbix_utils.helper_to_numeric_value([
'DES',
'AES'], check.get('snmpv3_privprotocol')
)
constructed_check['snmpv3_privpassphrase'] = check.get('snmpv3_privpassphrase')
constructed_data.append(constructed_check)
return zabbix_utils.helper_cleanup_data(constructed_data)
class DiscoveryRule(ZabbixBase):
def check_if_drule_exists(self, name):
"""Check if discovery rule exists.
Args:
name: Name of the discovery rule.
Returns:
The return value. True for success, False otherwise.
"""
try:
_drule = self._zapi.drule.get({
'output': 'extend',
'selectDChecks': 'extend',
'filter': {'name': [name]}
})
if len(_drule) > 0:
return _drule
except Exception as e:
self._module.fail_json(msg="Failed to check if discovery rule '%s' exists: %s"
% (name, e))
def get_drule_by_drule_name(self, name):
"""Get discovery rule by discovery rule name
Args:
name: discovery rule name.
Returns:
discovery rule matching discovery rule name
"""
try:
drule_list = self._zapi.drule.get({
'output': 'extend',
'selectDChecks': 'extend',
'filter': {'name': [name]}
})
if len(drule_list) < 1:
self._module.fail_json(msg="Discovery rule not found: %s" % name)
else:
return drule_list[0]
except Exception as e:
self._module.fail_json(msg="Failed to get discovery rule '%s': %s" % (name, e))
def get_proxy_by_proxy_name(self, proxy_name):
"""Get proxy by proxy name
Args:
proxy_name: proxy name.
Returns:
proxy matching proxy name
"""
try:
proxy_list = self._zapi.proxy.get({
'output': 'extend',
'selectInterface': 'extend',
'filter': {'host': [proxy_name]}
})
if len(proxy_list) < 1:
self._module.fail_json(msg="Proxy not found: %s" % proxy_name)
else:
return proxy_list[0]
except Exception as e:
self._module.fail_json(msg="Failed to get proxy '%s': %s" % (proxy_name, e))
def _construct_parameters(self, **kwargs):
"""Construct parameters.
Args:
**kwargs: Arbitrary keyword parameters.
Returns:
dict: dictionary of specified parameters
"""
_params = {
'name': kwargs['name'],
'iprange': ','.join(kwargs['iprange']),
'delay': kwargs['delay'],
'status': zabbix_utils.helper_to_numeric_value([
'enabled',
'disabled'], kwargs['status']
),
'dchecks': kwargs['dchecks']
}
if kwargs['proxy']:
_params['proxy_hostid'] = self.get_proxy_by_proxy_name(kwargs['proxy'])['proxyid']
return _params
def check_difference(self, **kwargs):
"""Check difference between discovery rule and user specified parameters.
Args:
**kwargs: Arbitrary keyword parameters.
Returns:
dict: dictionary of differences
"""
existing_drule = zabbix_utils.helper_convert_unicode_to_str(self.check_if_drule_exists(kwargs['name'])[0])
parameters = zabbix_utils.helper_convert_unicode_to_str(self._construct_parameters(**kwargs))
change_parameters = {}
if existing_drule['nextcheck']:
existing_drule.pop('nextcheck')
_diff = zabbix_utils.helper_cleanup_data(compare_dictionaries(parameters, existing_drule, change_parameters))
return _diff
def update_drule(self, **kwargs):
"""Update discovery rule.
Args:
**kwargs: Arbitrary keyword parameters.
Returns:
drule: updated discovery rule
"""
try:
if self._module.check_mode:
self._module.exit_json(msg="Discovery rule would be updated if check mode was not specified: ID %s" % kwargs['drule_id'], changed=True)
kwargs['druleid'] = kwargs.pop('drule_id')
return self._zapi.drule.update(kwargs)
except Exception as e:
self._module.fail_json(msg="Failed to update discovery rule ID '%s': %s" % (kwargs['drule_id'], e))
def add_drule(self, **kwargs):
"""Add discovery rule
Args:
**kwargs: Arbitrary keyword parameters
Returns:
drule: created discovery rule
"""
try:
if self._module.check_mode:
self._module.exit_json(msg="Discovery rule would be added if check mode was not specified", changed=True)
parameters = self._construct_parameters(**kwargs)
drule_list = self._zapi.drule.create(parameters)
return drule_list['druleids'][0]
except Exception as e:
self._module.fail_json(msg="Failed to create discovery rule %s: %s" % (kwargs['name'], e))
def delete_drule(self, drule_id):
"""Delete discovery rule.
Args:
drule_id: Discovery rule id
Returns:
drule: deleted discovery rule
"""
try:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Discovery rule would be deleted if check mode was not specified")
return self._zapi.drule.delete([drule_id])
except Exception as e:
self._module.fail_json(msg="Failed to delete discovery rule '%s': %s" % (drule_id, e))
def compare_lists(l1, l2, diff_dict):
"""
Compares l1 and l2 lists and adds the items that are different
to the diff_dict dictionary.
Used in recursion with compare_dictionaries() function.
Args:
l1: first list to compare
l2: second list to compare
diff_dict: dictionary to store the difference
Returns:
dict: items that are different
"""
if len(l1) != len(l2):
diff_dict.append(l1)
return diff_dict
for i, item in enumerate(l1):
if isinstance(item, dict):
diff_dict.insert(i, {})
diff_dict[i] = compare_dictionaries(item, l2[i], diff_dict[i])
else:
if item != l2[i]:
diff_dict.append(item)
while {} in diff_dict:
diff_dict.remove({})
return diff_dict
def compare_dictionaries(d1, d2, diff_dict):
"""
Compares d1 and d2 dictionaries and adds the items that are different
to the diff_dict dictionary.
Used in recursion with compare_lists() function.
Args:
d1: first dictionary to compare
d2: second dictionary to compare
diff_dict: dictionary to store the difference
Returns:
dict: items that are different
"""
for k, v in d1.items():
if k not in d2:
diff_dict[k] = v
continue
if isinstance(v, dict):
diff_dict[k] = {}
compare_dictionaries(v, d2[k], diff_dict[k])
if diff_dict[k] == {}:
del diff_dict[k]
else:
diff_dict[k] = v
elif isinstance(v, list):
diff_dict[k] = []
compare_lists(v, d2[k], diff_dict[k])
if diff_dict[k] == []:
del diff_dict[k]
else:
diff_dict[k] = v
else:
if v != d2[k]:
diff_dict[k] = v
return diff_dict
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(
name=dict(type='str', required=True),
iprange=dict(type='list', required=False, elements='str'),
dchecks=dict(
type='list',
required=False,
aliases=['dcheck'],
elements='dict',
options=dict(
type=dict(type='str', choices=[
'SSH',
'LDAP',
'SMTP',
'FTP',
'HTTP',
'POP',
'NNTP',
'IMAP',
'TCP',
'Zabbix',
'SNMPv1',
'SNMPv2',
'ICMP',
'SNMPv3',
'HTTPS',
'Telnet']
),
ports=dict(type='str'),
key=dict(type='str', no_log=False),
snmp_community=dict(type='str'),
snmpv3_authpassphrase=dict(type='str', no_log=True),
snmpv3_authprotocol=dict(type='str', choices=['MD5', 'SHA']),
snmpv3_contextname=dict(type='str'),
snmpv3_privpassphrase=dict(type='str', no_log=True),
snmpv3_privprotocol=dict(type='str', choices=['DES', 'AES']),
snmpv3_securitylevel=dict(type='str', choices=['noAuthNoPriv', 'authNoPriv', 'authPriv']),
snmpv3_securityname=dict(type='str'),
uniq=dict(type='bool', default=False),
host_source=dict(type='str', choices=['DNS', 'IP', 'discovery'], default='DNS'),
name_source=dict(type='str', choices=['None', 'DNS', 'IP', 'discovery'], default='None')
),
required_if=[
['type', 'SSH', ['ports']],
['type', 'LDAP', ['ports']],
['type', 'SMTP', ['ports']],
['type', 'FTP', ['ports']],
['type', 'HTTP', ['ports']],
['type', 'POP', ['ports']],
['type', 'NNTP', ['ports']],
['type', 'IMAP', ['ports']],
['type', 'TCP', ['ports']],
['type', 'Zabbix', ['ports', 'key']],
['type', 'SNMPv1', ['ports', 'key', 'snmp_community']],
['type', 'SNMPv2', ['ports', 'key', 'snmp_community']],
['type', 'SNMPv3', ['ports', 'key']],
['type', 'HTTPS', ['ports']],
['type', 'Telnet', ['ports']],
['snmpv3_securitylevel', 'authPriv', ['snmpv3_privpassphrase']]
]
),
delay=dict(type='str', required=False, default='1h'),
proxy=dict(type='str', required=False, default=None),
status=dict(type='str', default="enabled", choices=["enabled", "disabled"]),
state=dict(type='str', default='present', choices=['present', 'absent'])
)
module = AnsibleModule(
argument_spec=argument_spec,
required_if=[
['state', 'present', ['name', 'iprange', 'dchecks']],
['state', 'absent', ['name']],
],
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
state = module.params['state']
name = module.params['name']
iprange = module.params['iprange']
dchecks = module.params['dchecks']
delay = module.params['delay']
proxy = module.params['proxy']
status = module.params['status']
drule = DiscoveryRule(module)
zbx = drule._zapi
dcks = Dchecks(module, zbx)
drule_exists = drule.check_if_drule_exists(name)
if drule_exists:
drule_id = drule.get_drule_by_drule_name(name)['druleid']
if state == "absent":
drule.delete_drule(drule_id)
module.exit_json(changed=True, state=state, drule=name, druleid=drule_id, msg="Discovery Rule deleted: %s, ID: %s" % (name, drule_id))
else:
difference = drule.check_difference(
drule_id=drule_id,
name=name,
iprange=iprange,
dchecks=dcks.construct_the_data(dchecks),
delay=delay,
proxy=proxy,
status=status
)
if difference == {}:
module.exit_json(changed=False, state=state, drule=name, druleid=drule_id, msg="Discovery Rule is up to date: %s" % name)
else:
drule_id = drule.update_drule(
drule_id=drule_id,
**difference
)
module.exit_json(changed=True, state=state, drule=name, druleid=drule_id, msg="Discovery Rule updated: %s, ID: %s" % (name, drule_id))
else:
if state == "absent":
module.exit_json(changed=False, state=state, drule=name, msg="Discovery rule %s does not exist, nothing to delete" % name)
else:
drule_id = drule.add_drule(
name=name,
iprange=iprange,
dchecks=dcks.construct_the_data(dchecks),
delay=delay,
proxy=proxy,
status=status
)
module.exit_json(changed=True, state=state, drule=name, druleid=drule_id, msg="Discovery Rule created: %s, ID: %s" % (name, drule_id))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,289 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013-2014, Epic Games, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_globalmacro
short_description: Create/update/delete Zabbix Global macros
version_added: 1.4.0
description:
- manages Zabbix Global macros, it can create, update or delete them.
- For macro_type Secret the value field cannot be validated and will always be overwritten due to the secret nature of the Text.
author:
- "Cove (@cove)"
- Dean Hailin Song (!UNKNOWN)
- Timothy Test (@ttestscripting)
requirements:
- "python >= 2.6"
options:
macro_name:
description:
- Name of the global macro in zabbix native format C({$MACRO}) or simple format C(MACRO).
required: true
type: str
macro_value:
description:
- Value of the global macro.
- Required if I(state=present).
type: str
macro_type:
description:
- Type of the global macro Text or Secret Text.
- Required if I(state=present).
- text
- secret - Secret Text Works only with Zabbix >= 5.0 and will default to Text in lower versions
- vault - Vault Secret Works only with Zabbix >= 5.2 and will default to Text in lower versions
type: str
choices: [text, secret, vault]
default: text
macro_description:
description:
- Text Description of the global macro.
- Works only with Zabbix >= 4.4 and is silently ignored in lower versions
type: str
default: ''
state:
description:
- State of the macro.
- On C(present), it will create if macro does not exist or update the macro if the associated data is different.
- On C(absent) will remove a macro if it exists.
required: false
choices: ['present', 'absent']
type: str
default: "present"
force:
description:
- Only updates an existing macro if set to C(yes).
default: 'yes'
type: bool
notes:
- This module returns changed=true when I(macro_type=secret) with Zabbix >= 5.0.
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create new global macro or update an existing macro's value
community.zabbix.zabbix_globalmacro:
macro_name: EXAMPLE.MACRO
macro_value: Example value
macro_type: 0
macro_description: Example description
state: present
# Values with curly brackets need to be quoted otherwise they will be interpreted as a dictionary
- name: Create new global macro in Zabbix native format with Secret Type
community.zabbix.zabbix_globalmacro:
macro_name: "{$EXAMPLE.MACRO}"
macro_value: Example value
macro_type: 1
macro_description: Example description
state: present
- name: Delete existing global macro
community.zabbix.zabbix_globalmacro:
macro_name: "{$EXAMPLE.MACRO}"
state: absent
'''
RETURN = r"""
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class GlobalMacro(ZabbixBase):
# get global macro
def get_global_macro(self, macro_name):
try:
all_global_macro_list = self._zapi.usermacro.get({"globalmacro": 'true'})
global_macro_list = [d for d in all_global_macro_list if d['macro'] == macro_name]
if len(global_macro_list) > 0:
return global_macro_list[0]
return None
except Exception as e:
self._module.fail_json(msg="Failed to get global macro %s: %s" % (macro_name, e))
# create global macro
def create_global_macro(self, macro_name, macro_value, macro_type, macro_description):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
if LooseVersion(self._zbx_api_version) < LooseVersion('4.4.0'):
self._zapi.usermacro.createglobal({'macro': macro_name, 'value': macro_value})
self._module.exit_json(changed=True, result="Successfully added global macro %s" % macro_name)
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.4.0'):
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.0.0'):
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2.0'):
if macro_type == '2':
macro_type = '0'
self._zapi.usermacro.createglobal({'macro': macro_name, 'value': macro_value, 'type': macro_type, 'description': macro_description})
self._module.exit_json(changed=True, result="Successfully added global macro %s" % macro_name)
else:
self._zapi.usermacro.createglobal({'macro': macro_name, 'value': macro_value, 'description': macro_description})
self._module.exit_json(changed=True, result="Successfully added global macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to create global macro %s: %s" % (macro_name, e))
# update global macro
def update_global_macro(self, global_macro_obj, macro_name, macro_value, macro_type, macro_description):
global_macro_id = global_macro_obj['globalmacroid']
try:
if LooseVersion(self._zbx_api_version) < LooseVersion('4.4.0'):
if global_macro_obj['macro'] == macro_name and global_macro_obj['value'] == macro_value:
self._module.exit_json(changed=False, result="Global macro %s already up to date" % macro_name)
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.usermacro.updateglobal({'globalmacroid': global_macro_id, 'macro': macro_name, 'value': macro_value})
self._module.exit_json(changed=True, result="Successfully updated global macro %s" % macro_name)
elif LooseVersion(self._zbx_api_version) >= LooseVersion('5.0.0'):
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2.0'):
if macro_type == '2':
macro_type = '0'
if global_macro_obj['type'] == '0' or global_macro_obj['type'] == '2':
if (global_macro_obj['macro'] == macro_name and global_macro_obj['value'] == macro_value
and global_macro_obj['type'] == macro_type and global_macro_obj['description'] == macro_description):
self._module.exit_json(changed=False, result="Global macro %s already up to date" % macro_name)
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.usermacro.updateglobal({'globalmacroid': global_macro_id, 'macro': macro_name,
'value': macro_value, 'type': macro_type, 'description': macro_description})
self._module.exit_json(changed=True, result="Successfully updated global macro %s" % macro_name)
else:
if (global_macro_obj['macro'] == macro_name and global_macro_obj['value'] == macro_value
and global_macro_obj['description'] == macro_description):
self._module.exit_json(changed=False, result="Global macro %s already up to date" % macro_name)
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.usermacro.updateglobal({'globalmacroid': global_macro_id, 'macro': macro_name,
'value': macro_value, 'description': macro_description})
self._module.exit_json(changed=True, result="Successfully updated global macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to update global macro %s: %s" % (macro_name, e))
# delete global macro
def delete_global_macro(self, global_macro_obj, macro_name):
global_macro_id = global_macro_obj['globalmacroid']
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.usermacro.deleteglobal([global_macro_id])
self._module.exit_json(changed=True, result="Successfully deleted global macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to delete global macro %s: %s" % (macro_name, e))
def normalize_macro_name(macro_name):
# Zabbix handles macro names in upper case characters
if ':' in macro_name:
macro_name = ':'.join([macro_name.split(':')[0].upper(), ':'.join(macro_name.split(':')[1:])])
else:
macro_name = macro_name.upper()
# Valid format for macro is {$MACRO}
if not macro_name.startswith('{$'):
macro_name = '{$' + macro_name
if not macro_name.endswith('}'):
macro_name = macro_name + '}'
return macro_name
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
macro_name=dict(type='str', required=True),
macro_value=dict(type='str', required=False, no_log=True),
macro_type=dict(type='str', default='text', choices=['text', 'secret', 'vault']),
macro_description=dict(type='str', default=''),
state=dict(type='str', default='present', choices=['present', 'absent']),
force=dict(type='bool', default=True)
))
module = AnsibleModule(
argument_spec=argument_spec,
required_if=[
['state', 'present', ['macro_value']]
],
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
macro_name = normalize_macro_name(module.params['macro_name'])
macro_value = module.params['macro_value']
macro_type = module.params['macro_type']
macro_value = module.params['macro_value']
macro_description = module.params['macro_description']
state = module.params['state']
force = module.params['force']
if macro_type == 'text':
macro_type = '0'
elif macro_type == 'secret':
macro_type = '1'
elif macro_type == 'vault':
macro_type = '2'
global_macro_class_obj = GlobalMacro(module)
if macro_name:
global_macro_obj = global_macro_class_obj.get_global_macro(macro_name)
if state == 'absent':
if not global_macro_obj:
module.exit_json(changed=False, msg="Global Macro %s does not exist" % macro_name)
else:
# delete a macro
global_macro_class_obj.delete_global_macro(global_macro_obj, macro_name)
else:
if not global_macro_obj:
# create global macro
global_macro_class_obj.create_global_macro(macro_name, macro_value, macro_type, macro_description)
elif force:
# update global macro
global_macro_class_obj.update_global_macro(global_macro_obj, macro_name, macro_value, macro_type, macro_description)
else:
module.exit_json(changed=False, result="Global macro %s already exists and force is set to no" % macro_name)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,193 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2013-2014, Epic Games, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_group
short_description: Create/delete Zabbix host groups
description:
- Create host groups if they do not exist.
- Delete existing host groups if they exist.
author:
- "Cove (@cove)"
- "Tony Minfei Ding (!UNKNOWN)"
- "Harrison Gu (@harrisongu)"
requirements:
- "python >= 2.6"
options:
state:
description:
- Create or delete host group.
required: false
type: str
default: "present"
choices: [ "present", "absent" ]
host_groups:
description:
- List of host groups to create or delete.
required: true
type: list
elements: str
aliases: [ "host_group" ]
extends_documentation_fragment:
- community.zabbix.zabbix
notes:
- Too many concurrent updates to the same group may cause Zabbix to return errors, see examples for a workaround if needed.
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Base create host groups example
- name: Create host groups
community.zabbix.zabbix_group:
state: present
host_groups:
- Example group1
- Example group2
# Limit the Zabbix group creations to one host since Zabbix can return an error when doing concurrent updates
- name: Create host groups
community.zabbix.zabbix_group:
state: present
host_groups:
- Example group1
- Example group2
when: inventory_hostname==groups['group_name'][0]
'''
import traceback
try:
from zabbix_api import Already_Exists
HAS_ZABBIX_API = True
ZBX_IMP_ERR = Exception()
except ImportError:
ZBX_IMP_ERR = traceback.format_exc()
HAS_ZABBIX_API = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class HostGroup(ZabbixBase):
# create host group(s) if not exists
def create_host_group(self, group_names):
try:
group_add_list = []
for group_name in group_names:
result = self._zapi.hostgroup.get({'filter': {'name': group_name}})
if not result:
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.hostgroup.create({'name': group_name})
group_add_list.append(group_name)
except Already_Exists:
return group_add_list
return group_add_list
except Exception as e:
self._module.fail_json(msg="Failed to create host group(s): %s" % e)
# delete host group(s)
def delete_host_group(self, group_ids):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.hostgroup.delete(group_ids)
except Exception as e:
self._module.fail_json(msg="Failed to delete host group(s), Exception: %s" % e)
# get group ids by name
def get_group_ids(self, host_groups):
group_ids = []
group_list = self._zapi.hostgroup.get({'output': 'extend', 'filter': {'name': host_groups}})
for group in group_list:
group_id = group['groupid']
group_ids.append(group_id)
return group_ids, group_list
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
host_groups=dict(type='list', required=True, aliases=['host_group']),
state=dict(type='str', default="present", choices=['present', 'absent']),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password']:
if p in module.params and module.params[p] and module.params[p]:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
host_groups = module.params['host_groups']
state = module.params['state']
hostGroup = HostGroup(module)
group_ids = []
group_list = []
if host_groups:
group_ids, group_list = hostGroup.get_group_ids(host_groups)
if state == "absent":
# delete host groups
if group_ids:
delete_group_names = []
hostGroup.delete_host_group(group_ids)
for group in group_list:
delete_group_names.append(group['name'])
module.exit_json(changed=True,
result="Successfully deleted host group(s): %s." % ",".join(delete_group_names))
else:
module.exit_json(changed=False, result="No host group(s) to delete.")
else:
# create host groups
group_add_list = hostGroup.create_host_group(host_groups)
if len(group_add_list) > 0:
module.exit_json(changed=True, result="Successfully created host group(s): %s" % group_add_list)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) me@mimiko.me
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
RETURN = r'''
---
host_groups:
description: List of Zabbix groups.
returned: success
type: dict
sample: [ { "flags": "0", "groupid": "33", "internal": "0", "name": "Hostgruup A" } ]
'''
DOCUMENTATION = r'''
---
module: zabbix_group_info
short_description: Gather information about Zabbix hostgroup
description:
- This module allows you to search for Zabbix hostgroup entries.
- This module was called C(zabbix_group_facts) before Ansible 2.9. The usage did not change.
author:
- "Michael Miko (@RedWhiteMiko)"
requirements:
- "python >= 2.6"
options:
hostgroup_name:
description:
- Name of the hostgroup in Zabbix.
- hostgroup is the unique identifier used and cannot be updated using this module.
required: true
type: list
elements: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get hostgroup info
community.zabbix.zabbix_group_info:
hostgroup_name:
- ExampleHostgroup
timeout: 10
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Host(ZabbixBase):
def get_group_ids_by_group_names(self, group_names):
group_list = self._zapi.hostgroup.get({'output': 'extend', 'filter': {'name': group_names}})
if len(group_list) < 1:
self._module.fail_json(msg="Hostgroup not found: %s" % group_names)
return group_list
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
hostgroup_name=dict(type='list', required=True),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
if module._name == 'zabbix_group_facts':
module.deprecate("The 'zabbix_group_facts' module has been renamed to 'zabbix_group_info'",
collection_name="community.zabbix", version='2.0.0') # was 2.13
hostgroup_name = module.params['hostgroup_name']
host = Host(module)
host_groups = host.get_group_ids_by_group_names(hostgroup_name)
module.exit_json(host_groups=host_groups)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) me@mimiko.me
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
RETURN = r'''
---
host_groups:
description: List of Zabbix groups.
returned: success
type: dict
sample: [ { "flags": "0", "groupid": "33", "internal": "0", "name": "Hostgruup A" } ]
'''
DOCUMENTATION = r'''
---
module: zabbix_group_info
short_description: Gather information about Zabbix hostgroup
description:
- This module allows you to search for Zabbix hostgroup entries.
- This module was called C(zabbix_group_facts) before Ansible 2.9. The usage did not change.
author:
- "Michael Miko (@RedWhiteMiko)"
requirements:
- "python >= 2.6"
options:
hostgroup_name:
description:
- Name of the hostgroup in Zabbix.
- hostgroup is the unique identifier used and cannot be updated using this module.
required: true
type: list
elements: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get hostgroup info
community.zabbix.zabbix_group_info:
hostgroup_name:
- ExampleHostgroup
timeout: 10
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Host(ZabbixBase):
def get_group_ids_by_group_names(self, group_names):
group_list = self._zapi.hostgroup.get({'output': 'extend', 'filter': {'name': group_names}})
if len(group_list) < 1:
self._module.fail_json(msg="Hostgroup not found: %s" % group_names)
return group_list
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
hostgroup_name=dict(type='list', required=True),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
if module._name == 'zabbix_group_facts':
module.deprecate("The 'zabbix_group_facts' module has been renamed to 'zabbix_group_info'",
collection_name="community.zabbix", version='2.0.0') # was 2.13
hostgroup_name = module.params['hostgroup_name']
host = Host(module)
host_groups = host.get_group_ids_by_group_names(hostgroup_name)
module.exit_json(host_groups=host_groups)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,320 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) stephane.travassac@fr.clara.net
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
RETURN = '''
---
triggers_ok:
description: Host Zabbix Triggers in OK state
returned: On success
type: complex
contains:
comments:
description: Additional description of the trigger
type: str
description:
description: Name of the trigger
type: str
error:
description: Error text if there have been any problems when updating the state of the trigger
type: str
expression:
description: Reduced trigger expression
type: str
flags:
description: Origin of the trigger
type: int
lastchange:
description: Time when the trigger last changed its state (timestamp)
type: int
priority:
description: Severity of the trigger
type: int
state:
description: State of the trigger
type: int
status:
description: Whether the trigger is enabled or disabled
type: int
templateid:
description: ID of the parent template trigger
type: int
triggerid:
description: ID of the trigger
type: int
type:
description: Whether the trigger can generate multiple problem events
type: int
url:
description: URL associated with the trigger
type: str
value:
description: Whether the trigger is in OK or problem state
type: int
triggers_problem:
description: Host Zabbix Triggers in problem state. See trigger and event objects in API documentation of your zabbix version for more
returned: On success
type: complex
contains:
comments:
description: Additional description of the trigger
type: str
description:
description: Name of the trigger
type: str
error:
description: Error text if there have been any problems when updating the state of the trigger
type: str
expression:
description: Reduced trigger expression
type: str
flags:
description: Origin of the trigger
type: int
last_event:
description: last event informations
type: complex
contains:
acknowledged:
description: If set to true return only acknowledged events
type: int
acknowledges:
description: acknowledges informations
type: complex
contains:
alias:
description: Account who acknowledge
type: str
clock:
description: Time when the event was created (timestamp)
type: int
message:
description: Text of the acknowledgement message
type: str
clock:
description: Time when the event was created (timestamp)
type: int
eventid:
description: ID of the event
type: int
value:
description: State of the related object
type: int
lastchange:
description: Time when the trigger last changed its state (timestamp)
type: int
priority:
description: Severity of the trigger
type: int
state:
description: State of the trigger
type: int
status:
description: Whether the trigger is enabled or disabled
type: int
templateid:
description: ID of the parent template trigger
type: int
triggerid:
description: ID of the trigger
type: int
type:
description: Whether the trigger can generate multiple problem events
type: int
url:
description: URL associated with the trigger
type: str
value:
description: Whether the trigger is in OK or problem state
type: int
'''
DOCUMENTATION = '''
---
module: zabbix_host_events_info
short_description: Get all triggers about a Zabbix host
description:
- This module allows you to see if a Zabbix host have no active alert to make actions on it.
For this case use module Ansible 'fail' to exclude host in trouble.
- Length of "triggers_ok" allow if template's triggers exist for Zabbix Host
author:
- "Stéphane Travassac (@stravassac)"
requirements:
- "python >= 2.7"
options:
host_identifier:
description:
- Identifier of Zabbix Host
required: true
type: str
host_id_type:
description:
- Type of host_identifier
choices:
- hostname
- visible_name
- hostid
required: false
default: hostname
type: str
trigger_severity:
description:
- Zabbix severity for search filter
default: average
required: false
choices:
- not_classified
- information
- warning
- average
- high
- disaster
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: exclude machine if alert active on it
community.zabbix.zabbix_host_events_info:
host_identifier: "{{inventory_hostname}}"
host_id_type: "hostname"
timeout: 120
register: zbx_host
delegate_to: localhost
- fail:
msg: "machine alert in zabbix"
when: zbx_host['triggers_problem']|length > 0
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Host(ZabbixBase):
def get_host(self, host_identifier, host_inventory, search_key):
""" Get host by hostname|visible_name|hostid """
host = self._zapi.host.get(
{'output': 'extend', 'selectParentTemplates': ['name'], 'filter': {search_key: host_identifier},
'selectInventory': host_inventory})
if len(host) < 1:
self._module.fail_json(msg="Host not found: %s" % host_identifier)
else:
return host[0]
def get_triggers_by_host_id_in_problem_state(self, host_id, trigger_severity):
""" Get triggers in problem state from a hostid"""
# https://www.zabbix.com/documentation/3.4/manual/api/reference/trigger/get
output = 'extend'
triggers_list = self._zapi.trigger.get({'output': output, 'hostids': host_id,
'min_severity': trigger_severity})
return triggers_list
def get_last_event_by_trigger_id(self, triggers_id):
""" Get the last event from triggerid"""
output = ['eventid', 'clock', 'acknowledged', 'value']
select_acknowledges = ['clock', 'alias', 'message']
event = self._zapi.event.get({'output': output, 'objectids': triggers_id,
'select_acknowledges': select_acknowledges, "limit": 1, "sortfield": "clock",
"sortorder": "DESC"})
return event[0]
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
host_identifier=dict(type='str', required=True),
host_id_type=dict(
default='hostname',
type='str',
choices=['hostname', 'visible_name', 'hostid']),
trigger_severity=dict(
type='str',
required=False,
default='average',
choices=['not_classified', 'information', 'warning', 'average', 'high', 'disaster']),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
trigger_severity_map = {'not_classified': 0, 'information': 1, 'warning': 2, 'average': 3, 'high': 4, 'disaster': 5}
host_id = module.params['host_identifier']
host_id_type = module.params['host_id_type']
trigger_severity = trigger_severity_map[module.params['trigger_severity']]
host_inventory = 'hostid'
host = Host(module)
if host_id_type == 'hostname':
zabbix_host = host.get_host(host_id, host_inventory, 'host')
host_id = zabbix_host['hostid']
elif host_id_type == 'visible_name':
zabbix_host = host.get_host(host_id, host_inventory, 'name')
host_id = zabbix_host['hostid']
elif host_id_type == 'hostid':
''' check hostid exist'''
zabbix_host = host.get_host(host_id, host_inventory, 'hostid')
triggers = host.get_triggers_by_host_id_in_problem_state(host_id, trigger_severity)
triggers_ok = []
triggers_problem = []
for trigger in triggers:
# tGet last event for trigger with problem value = 1
# https://www.zabbix.com/documentation/3.4/manual/api/reference/trigger/object
if int(trigger['value']) == 1:
event = host.get_last_event_by_trigger_id(trigger['triggerid'])
trigger['last_event'] = event
triggers_problem.append(trigger)
else:
triggers_ok.append(trigger)
module.exit_json(ok=True, triggers_ok=triggers_ok, triggers_problem=triggers_problem)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,231 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) me@mimiko.me
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
RETURN = r'''
---
hosts:
description: List of Zabbix hosts. See https://www.zabbix.com/documentation/4.0/manual/api/reference/host/get for list of host values.
returned: success
type: dict
sample: [ { "available": "1", "description": "", "disable_until": "0", "error": "", "flags": "0", "groups": ["1"], "host": "Host A", ... } ]
'''
DOCUMENTATION = r'''
---
module: zabbix_host_info
short_description: Gather information about Zabbix host
description:
- This module allows you to search for Zabbix host entries.
- This module was called C(zabbix_host_facts) before Ansible 2.9. The usage did not change.
author:
- "Michael Miko (@RedWhiteMiko)"
requirements:
- "python >= 2.6"
options:
host_name:
description:
- Name of the host in Zabbix.
- host_name is the unique identifier used and cannot be updated using this module.
- Required when I(host_ip) is not used.
required: false
type: str
default: ''
host_ip:
description:
- Host interface IP of the host in Zabbix.
- Required when I(host_name) is not used.
required: false
type: list
elements: str
default: []
exact_match:
description:
- Find the exact match
type: bool
default: no
remove_duplicate:
description:
- Remove duplicate host from host result
type: bool
default: yes
host_inventory:
description:
- List of host inventory keys to display in result.
- Whole host inventory is retrieved if keys are not specified.
type: list
elements: str
required: false
default: []
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get host info
community.zabbix.zabbix_host_info:
host_name: ExampleHost
host_ip: 127.0.0.1
timeout: 10
exact_match: no
remove_duplicate: yes
- name: Reduce host inventory information to provided keys
community.zabbix.zabbix_host_info:
host_name: ExampleHost
host_inventory:
- os
- tag
host_ip: 127.0.0.1
timeout: 10
exact_match: no
remove_duplicate: yes
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Host(ZabbixBase):
def get_hosts_by_host_name(self, host_name, exact_match, host_inventory):
""" Get host by host name """
search_key = 'search'
if exact_match:
search_key = 'filter'
host_list = self._zapi.host.get({
'output': 'extend',
'selectParentTemplates': ['name'],
search_key: {'host': [host_name]},
'selectInventory': host_inventory,
'selectGroups': 'extend',
'selectTags': 'extend',
'selectMacros': 'extend'
})
if len(host_list) < 1:
self._module.fail_json(msg="Host not found: %s" % host_name)
else:
return host_list
def get_hosts_by_ip(self, host_ips, host_inventory):
""" Get host by host ip(s) """
hostinterfaces = self._zapi.hostinterface.get({
'output': 'extend',
'filter': {
'ip': host_ips
}
})
if len(hostinterfaces) < 1:
self._module.fail_json(msg="Host not found: %s" % host_ips)
host_list = []
for hostinterface in hostinterfaces:
host = self._zapi.host.get({
'output': 'extend',
'selectGroups': 'extend',
'selectParentTemplates': ['name'],
'hostids': hostinterface['hostid'],
'selectInventory': host_inventory,
'selectTags': 'extend',
'selectMacros': 'extend'
})
host[0]['hostinterfaces'] = hostinterface
host_list.append(host[0])
return host_list
def delete_duplicate_hosts(self, hosts):
""" Delete duplicated hosts """
unique_hosts = []
listed_hostnames = []
for zabbix_host in hosts:
if zabbix_host['name'] in listed_hostnames:
continue
unique_hosts.append(zabbix_host)
listed_hostnames.append(zabbix_host['name'])
return unique_hosts
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
host_name=dict(type='str', default='', required=False),
host_ip=dict(type='list', default=[], required=False),
exact_match=dict(type='bool', required=False, default=False),
remove_duplicate=dict(type='bool', required=False, default=True),
host_inventory=dict(type='list', default=[], required=False)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
if module._name == 'zabbix_host_facts':
module.deprecate("The 'zabbix_host_facts' module has been renamed to 'zabbix_host_info'",
collection_name="community.zabbix", version='2.0.0') # was 2.13
zabbix_utils.require_creds_params(module)
host_name = module.params['host_name']
host_ips = module.params['host_ip']
exact_match = module.params['exact_match']
is_remove_duplicate = module.params['remove_duplicate']
host_inventory = module.params['host_inventory']
if not host_inventory:
host_inventory = 'extend'
host = Host(module)
if host_name:
hosts = host.get_hosts_by_host_name(host_name, exact_match, host_inventory)
if is_remove_duplicate:
hosts = host.delete_duplicate_hosts(hosts)
extended_hosts = []
for zabbix_host in hosts:
zabbix_host['hostinterfaces'] = host._zapi.hostinterface.get({
'output': 'extend', 'hostids': zabbix_host['hostid']
})
extended_hosts.append(zabbix_host)
module.exit_json(ok=True, hosts=extended_hosts)
elif host_ips:
extended_hosts = host.get_hosts_by_ip(host_ips, host_inventory)
if is_remove_duplicate:
hosts = host.delete_duplicate_hosts(extended_hosts)
module.exit_json(ok=True, hosts=extended_hosts)
else:
module.exit_json(ok=False, hosts=[], result="No Host present")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,231 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) me@mimiko.me
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
RETURN = r'''
---
hosts:
description: List of Zabbix hosts. See https://www.zabbix.com/documentation/4.0/manual/api/reference/host/get for list of host values.
returned: success
type: dict
sample: [ { "available": "1", "description": "", "disable_until": "0", "error": "", "flags": "0", "groups": ["1"], "host": "Host A", ... } ]
'''
DOCUMENTATION = r'''
---
module: zabbix_host_info
short_description: Gather information about Zabbix host
description:
- This module allows you to search for Zabbix host entries.
- This module was called C(zabbix_host_facts) before Ansible 2.9. The usage did not change.
author:
- "Michael Miko (@RedWhiteMiko)"
requirements:
- "python >= 2.6"
options:
host_name:
description:
- Name of the host in Zabbix.
- host_name is the unique identifier used and cannot be updated using this module.
- Required when I(host_ip) is not used.
required: false
type: str
default: ''
host_ip:
description:
- Host interface IP of the host in Zabbix.
- Required when I(host_name) is not used.
required: false
type: list
elements: str
default: []
exact_match:
description:
- Find the exact match
type: bool
default: no
remove_duplicate:
description:
- Remove duplicate host from host result
type: bool
default: yes
host_inventory:
description:
- List of host inventory keys to display in result.
- Whole host inventory is retrieved if keys are not specified.
type: list
elements: str
required: false
default: []
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get host info
community.zabbix.zabbix_host_info:
host_name: ExampleHost
host_ip: 127.0.0.1
timeout: 10
exact_match: no
remove_duplicate: yes
- name: Reduce host inventory information to provided keys
community.zabbix.zabbix_host_info:
host_name: ExampleHost
host_inventory:
- os
- tag
host_ip: 127.0.0.1
timeout: 10
exact_match: no
remove_duplicate: yes
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Host(ZabbixBase):
def get_hosts_by_host_name(self, host_name, exact_match, host_inventory):
""" Get host by host name """
search_key = 'search'
if exact_match:
search_key = 'filter'
host_list = self._zapi.host.get({
'output': 'extend',
'selectParentTemplates': ['name'],
search_key: {'host': [host_name]},
'selectInventory': host_inventory,
'selectGroups': 'extend',
'selectTags': 'extend',
'selectMacros': 'extend'
})
if len(host_list) < 1:
self._module.fail_json(msg="Host not found: %s" % host_name)
else:
return host_list
def get_hosts_by_ip(self, host_ips, host_inventory):
""" Get host by host ip(s) """
hostinterfaces = self._zapi.hostinterface.get({
'output': 'extend',
'filter': {
'ip': host_ips
}
})
if len(hostinterfaces) < 1:
self._module.fail_json(msg="Host not found: %s" % host_ips)
host_list = []
for hostinterface in hostinterfaces:
host = self._zapi.host.get({
'output': 'extend',
'selectGroups': 'extend',
'selectParentTemplates': ['name'],
'hostids': hostinterface['hostid'],
'selectInventory': host_inventory,
'selectTags': 'extend',
'selectMacros': 'extend'
})
host[0]['hostinterfaces'] = hostinterface
host_list.append(host[0])
return host_list
def delete_duplicate_hosts(self, hosts):
""" Delete duplicated hosts """
unique_hosts = []
listed_hostnames = []
for zabbix_host in hosts:
if zabbix_host['name'] in listed_hostnames:
continue
unique_hosts.append(zabbix_host)
listed_hostnames.append(zabbix_host['name'])
return unique_hosts
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
host_name=dict(type='str', default='', required=False),
host_ip=dict(type='list', default=[], required=False),
exact_match=dict(type='bool', required=False, default=False),
remove_duplicate=dict(type='bool', required=False, default=True),
host_inventory=dict(type='list', default=[], required=False)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
if module._name == 'zabbix_host_facts':
module.deprecate("The 'zabbix_host_facts' module has been renamed to 'zabbix_host_info'",
collection_name="community.zabbix", version='2.0.0') # was 2.13
zabbix_utils.require_creds_params(module)
host_name = module.params['host_name']
host_ips = module.params['host_ip']
exact_match = module.params['exact_match']
is_remove_duplicate = module.params['remove_duplicate']
host_inventory = module.params['host_inventory']
if not host_inventory:
host_inventory = 'extend'
host = Host(module)
if host_name:
hosts = host.get_hosts_by_host_name(host_name, exact_match, host_inventory)
if is_remove_duplicate:
hosts = host.delete_duplicate_hosts(hosts)
extended_hosts = []
for zabbix_host in hosts:
zabbix_host['hostinterfaces'] = host._zapi.hostinterface.get({
'output': 'extend', 'hostids': zabbix_host['hostid']
})
extended_hosts.append(zabbix_host)
module.exit_json(ok=True, hosts=extended_hosts)
elif host_ips:
extended_hosts = host.get_hosts_by_ip(host_ips, host_inventory)
if is_remove_duplicate:
hosts = host.delete_duplicate_hosts(extended_hosts)
module.exit_json(ok=True, hosts=extended_hosts)
else:
module.exit_json(ok=False, hosts=[], result="No Host present")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,269 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013-2014, Epic Games, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_hostmacro
short_description: Create/update/delete Zabbix host macros
description:
- manages Zabbix host macros, it can create, update or delete them.
author:
- "Cove (@cove)"
- Dean Hailin Song (!UNKNOWN)
requirements:
- "python >= 2.6"
options:
host_name:
description:
- Name of the host.
required: true
type: str
macro_name:
description:
- Name of the host macro in zabbix native format C({$MACRO}) or simple format C(MACRO).
required: true
type: str
macro_value:
description:
- Value of the host macro.
- Required if I(state=present).
type: str
macro_type:
type: str
description:
- Type of the host macro.
- text (default)
- secret (Works only with Zabbix >= 5.0)
- vault (Works only with Zabbix >= 5.2)
required: false
choices: ['text', 'secret', 'vault']
default: 'text'
state:
description:
- State of the macro.
- On C(present), it will create if macro does not exist or update the macro if the associated data is different.
- On C(absent) will remove a macro if it exists.
required: false
choices: ['present', 'absent']
type: str
default: "present"
force:
description:
- Only updates an existing macro if set to C(yes).
default: 'yes'
type: bool
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create new host macro or update an existing macro's value
community.zabbix.zabbix_hostmacro:
host_name: ExampleHost
macro_name: EXAMPLE.MACRO
macro_value: Example value
state: present
# Values with curly brackets need to be quoted otherwise they will be interpreted as a dictionary
- name: Create new host macro in Zabbix native format
community.zabbix.zabbix_hostmacro:
host_name: ExampleHost
macro_name: "{$EXAMPLE.MACRO}"
macro_value: Example value
state: present
- name: Delete existing host macro
community.zabbix.zabbix_hostmacro:
host_name: ExampleHost
macro_name: "{$EXAMPLE.MACRO}"
state: absent
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class HostMacro(ZabbixBase):
# get host id by host name
def get_host_id(self, host_name):
try:
host_list = self._zapi.host.get({'output': 'extend', 'filter': {'host': host_name}})
if len(host_list) < 1:
self._module.fail_json(msg="Host not found: %s" % host_name)
else:
host_id = host_list[0]['hostid']
return host_id
except Exception as e:
self._module.fail_json(msg="Failed to get the host %s id: %s." % (host_name, e))
# get host macro
def get_host_macro(self, macro_name, host_id):
try:
host_macro_list = self._zapi.usermacro.get(
{"output": "extend", "selectSteps": "extend", 'hostids': [host_id], 'filter': {'macro': macro_name}})
if len(host_macro_list) > 0:
return host_macro_list[0]
return None
except Exception as e:
self._module.fail_json(msg="Failed to get host macro %s: %s" % (macro_name, e))
# create host macro
def create_host_macro(self, macro_name, macro_value, macro_type, host_id):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.0'):
self._zapi.usermacro.create({'hostid': host_id, 'macro': macro_name, 'value': macro_value, 'type': macro_type})
else:
self._zapi.usermacro.create({'hostid': host_id, 'macro': macro_name, 'value': macro_value})
self._module.exit_json(changed=True, result="Successfully added host macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to create host macro %s: %s" % (macro_name, e))
# update host macro
def update_host_macro(self, host_macro_obj, macro_name, macro_value, macro_type):
host_macro_id = host_macro_obj['hostmacroid']
if host_macro_obj['macro'] == macro_name:
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.0'):
# no change only when macro type == 0. when type = 1 or 2 zabbix will not output value of it.
if host_macro_obj['type'] == '0' and macro_type == '0' and host_macro_obj['value'] == macro_value:
self._module.exit_json(changed=False, result="Host macro %s already up to date" % macro_name)
else:
if host_macro_obj['value'] == macro_value:
self._module.exit_json(changed=False, result="Host macro %s already up to date" % macro_name)
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.0'):
self._zapi.usermacro.update({'hostmacroid': host_macro_id, 'value': macro_value, 'type': macro_type})
else:
self._zapi.usermacro.update({'hostmacroid': host_macro_id, 'value': macro_value})
self._module.exit_json(changed=True, result="Successfully updated host macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to update host macro %s: %s" % (macro_name, e))
# delete host macro
def delete_host_macro(self, host_macro_obj, macro_name):
host_macro_id = host_macro_obj['hostmacroid']
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.usermacro.delete([host_macro_id])
self._module.exit_json(changed=True, result="Successfully deleted host macro %s" % macro_name)
except Exception as e:
self._module.fail_json(msg="Failed to delete host macro %s: %s" % (macro_name, e))
def normalize_macro_name(macro_name):
# Zabbix handles macro names in upper case characters
if ':' in macro_name:
macro_name = ':'.join([macro_name.split(':')[0].upper(), ':'.join(macro_name.split(':')[1:])])
else:
macro_name = macro_name.upper()
# Valid format for macro is {$MACRO}
if not macro_name.startswith('{$'):
macro_name = '{$' + macro_name
if not macro_name.endswith('}'):
macro_name = macro_name + '}'
return macro_name
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
host_name=dict(type='str', required=True),
macro_name=dict(type='str', required=True),
macro_value=dict(type='str', required=False),
macro_type=dict(type='str', default='text', choices=['text', 'secret', 'vault']),
state=dict(type='str', default='present', choices=['present', 'absent']),
force=dict(type='bool', default=True)
))
module = AnsibleModule(
argument_spec=argument_spec,
required_if=[
['state', 'present', ['macro_value']]
],
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
host_name = module.params['host_name']
macro_name = normalize_macro_name(module.params['macro_name'])
macro_value = module.params['macro_value']
state = module.params['state']
force = module.params['force']
if module.params['macro_type'] == 'secret':
macro_type = '1'
elif module.params['macro_type'] == 'vault':
macro_type = '2'
else:
macro_type = '0'
host_macro_class_obj = HostMacro(module)
if host_name:
host_id = host_macro_class_obj.get_host_id(host_name)
host_macro_obj = host_macro_class_obj.get_host_macro(macro_name, host_id)
if state == 'absent':
if not host_macro_obj:
module.exit_json(changed=False, msg="Host Macro %s does not exist" % macro_name)
else:
# delete a macro
host_macro_class_obj.delete_host_macro(host_macro_obj, macro_name)
else:
if not host_macro_obj:
# create host macro
host_macro_class_obj.create_host_macro(macro_name, macro_value, macro_type, host_id)
elif force:
# update host macro
host_macro_class_obj.update_host_macro(host_macro_obj, macro_name, macro_value, macro_type)
else:
module.exit_json(changed=False, result="Host macro %s already exists and force is set to no" % macro_name)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,424 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2022, ONODERA Masaru <masaru-onodera@ieee.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: zabbix_housekeeping
short_description: Update Zabbix housekeeping
description:
- This module allows you to modify Zabbix housekeeping setting.
author:
- ONODERA Masaru(@masa-orca)
requirements:
- "python >= 2.6"
version_added: 1.6.0
options:
hk_events_mode:
description:
- Internal housekeeping for events and alerts will be enabled if C(true).
required: false
type: bool
hk_events_trigger:
description:
- Storage period of trigger data (e.g. 365d).
required: false
type: str
hk_events_service:
description:
- Storage period of service data (e.g. 365d).
- This parameter is available since Zabbix 6.0.
required: false
type: str
hk_events_internal:
description:
- Storage period of internal data (e.g. 365d).
required: false
type: str
hk_events_discovery:
description:
- Storage period of network discovery (e.g. 365d).
required: false
type: str
hk_events_autoreg:
description:
- Storage period of autoregistration data (e.g. 365d).
required: false
type: str
hk_services_mode:
description:
- Internal housekeeping for services will be enabled if C(true).
required: false
type: bool
hk_services:
description:
- Storage period of services data (e.g. 365d).
required: false
type: str
hk_audit_mode:
description:
- Internal housekeeping for audit will be enabled if C(true).
required: false
type: bool
hk_audit:
description:
- Storage period of audit data (e.g. 365d).
required: false
type: str
hk_sessions_mode:
description:
- Internal housekeeping for sessions will be enabled if C(true).
required: false
type: bool
hk_sessions:
description:
- Storage period of sessions data (e.g. 365d).
required: false
type: str
hk_history_mode:
description:
- Internal housekeeping for history will be enabled if C(true).
required: false
type: bool
hk_history_global:
description:
- Overriding history period of each items will be enabled if C(true).
required: false
type: bool
hk_history:
description:
- Storage priod of history data (e.g. 365d).
required: false
type: str
hk_trends_mode:
description:
- Internal housekeeping for trends will be enabled if C(true).
required: false
type: bool
hk_trends_global:
description:
- Overriding trend period of each items will be enabled if C(true).
required: false
type: bool
hk_trends:
description:
- Storage priod of trends data (e.g. 365d).
required: false
type: str
compression_status:
description:
- TimescaleDB compression for history and trends will be enabled if C(true).
required: false
type: bool
compress_older:
description:
- Compress history and trends records older than this period if I(compression_status=true).
required: false
type: str
notes:
- Zabbix 5.2 version and higher are supported.
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Update housekeeping all parameter
community.zabbix.zabbix_housekeeping:
login_user: Admin
login_password: secret
hk_events_mode: yes
hk_events_trigger: 365d
hk_events_service: 365d
hk_events_internal: 365d
hk_events_discovery: 365d
hk_events_autoreg: 365d
hk_services_mode: yes
hk_services: 365d
hk_audit_mode: yes
hk_audit: 365d
hk_sessions_mode: yes
hk_sessions: 365d
hk_history_mode: yes
hk_history_global: yes
hk_history: 365d
hk_trends_mode: yes
hk_trends_global: yes
hk_trends: 365d
compression_status: off
compress_older: 7d
'''
RETURN = '''
msg:
description: The result of the operation
returned: success
type: str
sample: 'Successfully update housekeeping setting'
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Housekeeping(ZabbixBase):
def __init__(self, module, zbx=None, zapi_wrapper=None):
super(Housekeeping, self).__init__(module, zbx, zapi_wrapper)
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2.0'):
module.fail_json(msg="This module doesn't support Zabbix versions lower than 5.2.0")
# get housekeeping setting
def get_housekeeping(self):
try:
return self._zapi.housekeeping.get({'output': 'extend'})
except Exception as e:
self._module.fail_json(msg="Failed to get housekeeping setting: %s" % e)
# Check parameter about time is valid.
def check_time_parameter(self, key_name, value):
match_result = re.match("^[0-9]+[smhdw]$", value)
if not match_result:
self._module.fail_json(msg="Invalid value for %s! Please set value like 365d." % key_name)
# update housekeeping setting
def update_housekeeping(
self,
current_housekeeping,
hk_events_mode,
hk_events_trigger,
hk_events_service,
hk_events_internal,
hk_events_discovery,
hk_events_autoreg,
hk_services_mode,
hk_services,
hk_audit_mode,
hk_audit,
hk_sessions_mode,
hk_sessions,
hk_history_mode,
hk_history_global,
hk_history,
hk_trends_mode,
hk_trends_global,
hk_trends,
compression_status,
compress_older):
try:
params = {}
if isinstance(hk_events_mode, bool):
params['hk_events_mode'] = str(int(hk_events_mode))
if hk_events_trigger:
self.check_time_parameter('hk_events_trigger', hk_events_trigger)
params['hk_events_trigger'] = hk_events_trigger
if hk_events_service:
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
self._module.warn('hk_events_service is ignored with <= Zabbix 5.4.')
else:
self.check_time_parameter('hk_events_service', hk_events_service)
params['hk_events_service'] = hk_events_service
if hk_events_internal:
self.check_time_parameter('hk_events_internal', hk_events_internal)
params['hk_events_internal'] = hk_events_internal
if hk_events_discovery:
self.check_time_parameter('hk_events_discovery', hk_events_discovery)
params['hk_events_discovery'] = hk_events_discovery
if hk_events_autoreg:
self.check_time_parameter('hk_events_autoreg', hk_events_autoreg)
params['hk_events_autoreg'] = hk_events_autoreg
if isinstance(hk_services_mode, bool):
params['hk_services_mode'] = str(int(hk_services_mode))
if hk_services:
self.check_time_parameter('hk_services', hk_services)
params['hk_services'] = hk_services
if isinstance(hk_audit_mode, bool):
params['hk_audit_mode'] = str(int(hk_audit_mode))
if hk_audit:
self.check_time_parameter('hk_audit', hk_audit)
params['hk_audit'] = hk_audit
if isinstance(hk_sessions_mode, bool):
params['hk_sessions_mode'] = str(int(hk_sessions_mode))
if hk_sessions:
self.check_time_parameter('hk_sessions', hk_sessions)
params['hk_sessions'] = hk_sessions
if isinstance(hk_history_mode, bool):
params['hk_history_mode'] = str(int(hk_history_mode))
if isinstance(hk_history_global, bool):
params['hk_history_global'] = str(int(hk_history_global))
if hk_history:
self.check_time_parameter('hk_history', hk_history)
params['hk_history'] = hk_history
if isinstance(hk_trends_mode, bool):
params['hk_trends_mode'] = str(int(hk_trends_mode))
if isinstance(hk_trends_global, bool):
params['hk_trends_global'] = str(int(hk_trends_global))
if hk_trends:
self.check_time_parameter('hk_trends', hk_trends)
params['hk_trends'] = hk_trends
if isinstance(compression_status, bool):
params['compression_status'] = str(int(compression_status))
if compress_older:
self.check_time_parameter('compress_older', compress_older)
params['compress_older'] = compress_older
future_housekeeping = current_housekeeping.copy()
future_housekeeping.update(params)
if future_housekeeping != current_housekeeping:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.housekeeping.update(params)
self._module.exit_json(changed=True, result="Successfully update housekeeping setting")
else:
self._module.exit_json(changed=False, result="Housekeeping setting is already up to date")
except Exception as e:
self._module.fail_json(msg="Failed to update housekeeping setting, Exception: %s" % e)
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
hk_events_mode=dict(type='bool'),
hk_events_trigger=dict(type='str'),
hk_events_service=dict(type='str'),
hk_events_internal=dict(type='str'),
hk_events_discovery=dict(type='str'),
hk_events_autoreg=dict(type='str'),
hk_services_mode=dict(type='bool'),
hk_services=dict(type='str'),
hk_audit_mode=dict(type='bool'),
hk_audit=dict(type='str'),
hk_sessions_mode=dict(type='bool'),
hk_sessions=dict(type='str'),
hk_history_mode=dict(type='bool'),
hk_history_global=dict(type='bool'),
hk_history=dict(type='str'),
hk_trends_mode=dict(type='bool'),
hk_trends_global=dict(type='bool'),
hk_trends=dict(type='str'),
compression_status=dict(type='bool'),
compress_older=dict(type='str')
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
hk_events_mode = module.params['hk_events_mode']
hk_events_trigger = module.params['hk_events_trigger']
hk_events_service = module.params['hk_events_service']
hk_events_internal = module.params['hk_events_internal']
hk_events_discovery = module.params['hk_events_discovery']
hk_events_autoreg = module.params['hk_events_autoreg']
hk_services_mode = module.params['hk_services_mode']
hk_services = module.params['hk_services']
hk_audit_mode = module.params['hk_audit_mode']
hk_audit = module.params['hk_audit']
hk_sessions_mode = module.params['hk_sessions_mode']
hk_sessions = module.params['hk_sessions']
hk_history_mode = module.params['hk_history_mode']
hk_history_global = module.params['hk_history_global']
hk_history = module.params['hk_history']
hk_trends_mode = module.params['hk_trends_mode']
hk_trends_global = module.params['hk_trends_global']
hk_trends = module.params['hk_trends']
compression_status = module.params['compression_status']
compress_older = module.params['compress_older']
housekeeping = Housekeeping(module)
current_housekeeping = housekeeping.get_housekeeping()
housekeeping.update_housekeeping(
current_housekeeping,
hk_events_mode,
hk_events_trigger,
hk_events_service,
hk_events_internal,
hk_events_discovery,
hk_events_autoreg,
hk_services_mode,
hk_services,
hk_audit_mode,
hk_audit,
hk_sessions_mode,
hk_sessions,
hk_history_mode,
hk_history_global,
hk_history,
hk_trends_mode,
hk_trends_global,
hk_trends,
compression_status,
compress_older
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,472 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_maintenance
short_description: Create Zabbix maintenance windows
description:
- This module will let you create Zabbix maintenance windows.
author: "Alexander Bulimov (@abulimov)"
requirements:
- "python >= 2.6"
options:
state:
description:
- Create or remove a maintenance window. Maintenance window to remove is identified by name.
default: present
choices: [ "present", "absent" ]
type: str
host_names:
description:
- Hosts to manage maintenance window for.
- B(Required) option when I(state=present) and I(host_groups) is not used.
aliases: [ "host_name" ]
type: list
elements: str
host_groups:
description:
- Host groups to manage maintenance window for.
- B(Required) option when I(state=present) and I(host_names) is not used.
aliases: [ "host_group" ]
type: list
elements: str
minutes:
description:
- Length of maintenance window in minutes.
default: 10
type: int
name:
description:
- Unique name of maintenance window.
required: true
type: str
desc:
description:
- Short description of maintenance window.
default: Created by Ansible
type: str
collect_data:
description:
- Type of maintenance. With data collection, or without.
type: bool
default: 'yes'
visible_name:
description:
- Type of zabbix host name to use for identifying hosts to include in the maintenance.
- I(visible_name=yes) to search by visible name, I(visible_name=no) to search by technical name.
type: bool
default: 'yes'
version_added: '2.0.0'
tags:
description:
- List of tags to assign to the hosts in maintenance.
- Requires I(collect_data=yes).
type: list
elements: dict
suboptions:
tag:
description:
- Name of the tag.
type: str
required: true
value:
description:
- Value of the tag.
type: str
default: ''
operator:
description:
- Condition operator.
- Possible values is
- 0 - Equals
- 2 - Contains
type: int
default: 2
extends_documentation_fragment:
- community.zabbix.zabbix
notes:
- Useful for setting hosts in maintenance mode before big update,
and removing maintenance window after update.
- Module creates maintenance window from now() to now() + minutes,
so if Zabbix server's time and host's time are not synchronized,
you will get strange results.
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create a named maintenance window for host www1 for 90 minutes
community.zabbix.zabbix_maintenance:
name: Update of www1
host_name: www1.example.com
state: present
minutes: 90
- name: Create a named maintenance window for host www1 and host groups Office and Dev
community.zabbix.zabbix_maintenance:
name: Update of www1
host_name: www1.example.com
host_groups:
- Office
- Dev
state: present
tags:
- tag: ExampleHostsTag
- tag: ExampleHostsTag2
value: ExampleTagValue
- tag: ExampleHostsTag3
value: ExampleTagValue
operator: 0
- name: Create a named maintenance window for hosts www1 and db1, without data collection.
community.zabbix.zabbix_maintenance:
name: update
host_names:
- www1.example.com
- db1.example.com
state: present
collect_data: False
- name: Remove maintenance window by name
community.zabbix.zabbix_maintenance:
name: Test1
state: absent
'''
import datetime
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
class MaintenanceModule(ZabbixBase):
def create_maintenance(self, group_ids, host_ids, start_time,
maintenance_type, period, name, desc, tags):
end_time = start_time + period
try:
parameters = {
"groupids": group_ids,
"hostids": host_ids,
"name": name,
"maintenance_type": maintenance_type,
"active_since": str(start_time),
"active_till": str(end_time),
"description": desc,
"timeperiods": [{
"timeperiod_type": "0",
"start_date": str(start_time),
"period": str(period),
}]
}
if tags is not None:
parameters['tags'] = tags
self._zapi.maintenance.create(parameters)
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
return 0, None, None
def update_maintenance(self, maintenance_id, group_ids, host_ids,
start_time, maintenance_type, period, desc, tags):
end_time = start_time + period
try:
parameters = {
"maintenanceid": maintenance_id,
"groupids": group_ids,
"hostids": host_ids,
"maintenance_type": maintenance_type,
"active_since": str(start_time),
"active_till": str(end_time),
"description": desc,
"timeperiods": [{
"timeperiod_type": "0",
"start_date": str(start_time),
"period": str(period),
}]
}
if tags is not None:
parameters['tags'] = tags
else:
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
parameters['tags'] = []
self._zapi.maintenance.update(parameters)
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
return 0, None, None
def get_maintenance(self, name):
try:
maintenances = self._zapi.maintenance.get(
{
"filter":
{
"name": name,
},
"selectGroups": "extend",
"selectHosts": "extend",
"selectTags": "extend"
}
)
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
for maintenance in maintenances:
maintenance["groupids"] = [group["groupid"] for group
in maintenance["groups"]] if "groups" in maintenance else []
maintenance["hostids"] = [host["hostid"] for host
in maintenance["hosts"]] if "hosts" in maintenance else []
return 0, maintenance, None
return 0, None, None
def delete_maintenance(self, maintenance_id):
try:
self._zapi.maintenance.delete([maintenance_id])
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
return 0, None, None
def get_group_ids(self, host_groups):
group_ids = []
for group in host_groups:
try:
result = self._zapi.hostgroup.get(
{
"output": "extend",
"filter":
{
"name": group
}
}
)
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
if not result:
return 1, None, "Group id for group %s not found" % group
group_ids.append(result[0]["groupid"])
return 0, group_ids, None
def get_host_ids(self, host_names, zabbix_host):
host_ids = []
for host in host_names:
try:
result = self._zapi.host.get(
{
"output": "extend",
"filter":
{
zabbix_host: host
}
}
)
# zabbix_api can call sys.exit() so we need to catch SystemExit here
except (Exception, SystemExit) as e:
return 1, None, str(e)
if not result:
return 1, None, "Host id for host %s not found" % host
host_ids.append(result[0]["hostid"])
return 0, host_ids, None
def check_maint_properties(self, maintenance, group_ids, host_ids, maintenance_type,
start_time, period, desc, tags):
if sorted(group_ids) != sorted(maintenance["groupids"]):
return True
if sorted(host_ids) != sorted(maintenance["hostids"]):
return True
if str(maintenance_type) != maintenance["maintenance_type"]:
return True
if str(int(start_time)) != maintenance["active_since"]:
return True
if str(int(start_time + period)) != maintenance["active_till"]:
return True
if str(desc) != maintenance['description']:
return True
if tags is not None and 'tags' in maintenance:
if sorted(tags, key=lambda k: k['tag']) != sorted(maintenance['tags'], key=lambda k: k['tag']):
return True
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
state=dict(type='str', required=False, default='present',
choices=['present', 'absent']),
host_names=dict(type='list', required=False,
default=None, aliases=['host_name']),
minutes=dict(type='int', required=False, default=10),
host_groups=dict(type='list', required=False,
default=None, aliases=['host_group']),
name=dict(type='str', required=True),
desc=dict(type='str', required=False, default="Created by Ansible"),
collect_data=dict(type='bool', required=False, default=True),
visible_name=dict(type='bool', required=False, default=True),
tags=dict(
type='list',
elements='dict',
required=False,
options=dict(
tag=dict(type='str', required=True),
operator=dict(type='int', default=2),
value=dict(type='str', default='')
)
)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
maint = MaintenanceModule(module)
host_names = module.params['host_names']
host_groups = module.params['host_groups']
state = module.params['state']
minutes = module.params['minutes']
name = module.params['name']
desc = module.params['desc']
collect_data = module.params['collect_data']
visible_name = module.params['visible_name']
tags = module.params['tags']
if collect_data:
maintenance_type = 0
else:
maintenance_type = 1
if tags is not None:
module.fail_json(msg="Tags cannot be provided for maintenance without data collection.")
if visible_name:
zabbix_host = "name"
else:
zabbix_host = "host"
changed = False
if state == "present":
if not host_names and not host_groups:
module.fail_json(
msg="At least one host_name or host_group must be defined for each created maintenance.")
now = datetime.datetime.now().replace(second=0)
start_time = int(time.mktime(now.timetuple()))
period = 60 * int(minutes) # N * 60 seconds
if host_groups:
(rc, group_ids, error) = maint.get_group_ids(host_groups)
if rc != 0:
module.fail_json(msg="Failed to get group_ids: %s" % error)
else:
group_ids = []
if host_names:
(rc, host_ids, error) = maint.get_host_ids(host_names, zabbix_host)
if rc != 0:
module.fail_json(msg="Failed to get host_ids: %s" % error)
else:
host_ids = []
(rc, maintenance, error) = maint.get_maintenance(name)
if rc != 0:
module.fail_json(
msg="Failed to check maintenance %s existence: %s" % (name, error))
if maintenance and maint.check_maint_properties(maintenance, group_ids, host_ids, maintenance_type,
start_time, period, desc, tags):
if module.check_mode:
changed = True
else:
(rc, data, error) = maint.update_maintenance(
maintenance["maintenanceid"], group_ids, host_ids, start_time, maintenance_type, period, desc, tags)
if rc == 0:
changed = True
else:
module.fail_json(
msg="Failed to update maintenance: %s" % error)
if not maintenance:
if module.check_mode:
changed = True
else:
(rc, data, error) = maint.create_maintenance(
group_ids, host_ids, start_time, maintenance_type, period, name, desc, tags)
if rc == 0:
changed = True
else:
module.fail_json(
msg="Failed to create maintenance: %s" % error)
if state == "absent":
(rc, maintenance, error) = maint.get_maintenance(name)
if rc != 0:
module.fail_json(
msg="Failed to check maintenance %s existence: %s" % (name, error))
if maintenance:
if module.check_mode:
changed = True
else:
(rc, data, error) = maint.delete_maintenance(
maintenance["maintenanceid"])
if rc == 0:
changed = True
else:
module.fail_json(
msg="Failed to remove maintenance: %s" % error)
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,819 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017-2018, Antony Alekseyev <antony.alekseyev@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_map
author:
- "Antony Alekseyev (@Akint)"
short_description: Create/update/delete Zabbix maps
description:
- "This module allows you to create, modify and delete Zabbix map entries,
using Graphviz binaries and text description written in DOT language.
Nodes of the graph will become map elements and edges will become links between map elements.
See U(https://en.wikipedia.org/wiki/DOT_(graph_description_language)) and U(https://www.graphviz.org/) for details.
Inspired by U(http://blog.zabbix.com/maps-for-the-lazy/)."
- "The following extra node attributes are supported:
C(zbx_host) contains name of the host in Zabbix. Use this if desired type of map element is C(host).
C(zbx_group) contains name of the host group in Zabbix. Use this if desired type of map element is C(host group).
C(zbx_sysmap) contains name of the map in Zabbix. Use this if desired type of map element is C(map).
C(zbx_label) contains label of map element.
C(zbx_image) contains name of the image used to display the element in default state.
C(zbx_image_disabled) contains name of the image used to display disabled map element.
C(zbx_image_maintenance) contains name of the image used to display map element in maintenance.
C(zbx_image_problem) contains name of the image used to display map element with problems.
C(zbx_url) contains map element URL in C(name:url) format.
More than one URL could be specified by adding a postfix (e.g., C(zbx_url1), C(zbx_url2))."
- "The following extra link attributes are supported:
C(zbx_draw_style) contains link line draw style. Possible values: C(line), C(bold), C(dotted), C(dashed).
C(zbx_trigger) contains name of the trigger used as a link indicator in C(host_name:trigger_name) format.
More than one trigger could be specified by adding a postfix (e.g., C(zbx_trigger1), C(zbx_trigger2)).
C(zbx_trigger_color) contains indicator color specified either as CSS3 name or as a hexadecimal code starting with C(#).
C(zbx_trigger_draw_style) contains indicator draw style. Possible values are the same as for C(zbx_draw_style)."
requirements:
- "python >= 2.6"
- pydotplus
- webcolors
- Pillow
- Graphviz
options:
name:
description:
- Name of the map.
required: true
aliases: [ "map_name" ]
type: str
data:
description:
- Graph written in DOT language.
required: false
aliases: [ "dot_data" ]
type: str
state:
description:
- State of the map.
- On C(present), it will create if map does not exist or update the map if the associated data is different.
- On C(absent) will remove the map if it exists.
required: false
choices: ['present', 'absent']
default: "present"
type: str
width:
description:
- Width of the map.
required: false
default: 800
type: int
height:
description:
- Height of the map.
required: false
default: 600
type: int
margin:
description:
- Size of white space between map's borders and its elements.
required: false
default: 40
type: int
expand_problem:
description:
- Whether the problem trigger will be displayed for elements with a single problem.
required: false
type: bool
default: true
highlight:
description:
- Whether icon highlighting is enabled.
required: false
type: bool
default: true
label_type:
description:
- Map element label type.
required: false
choices: ['label', 'ip', 'name', 'status', 'nothing', 'custom']
default: "name"
type: str
default_image:
description:
- Name of the Zabbix image used to display the element if this element doesn't have the C(zbx_image) attribute defined.
required: false
aliases: [ "image" ]
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
RETURN = r''' # '''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
###
### Example inventory:
# [web]
# web[01:03].example.com ansible_host=127.0.0.1
# [db]
# db.example.com ansible_host=127.0.0.1
# [backup]
# backup.example.com ansible_host=127.0.0.1
###
### Each inventory host is present in Zabbix with a matching name.
###
### Contents of 'map.j2':
# digraph G {
# graph [layout=dot splines=false overlap=scale]
# INTERNET [zbx_url="Google:https://google.com" zbx_image="Cloud_(96)"]
# {% for web_host in groups.web %}
# {% set web_loop = loop %}
# web{{ '%03d' % web_loop.index }} [zbx_host="{{ web_host }}"]
# INTERNET -> web{{ '%03d' % web_loop.index }} [zbx_trigger="{{ web_host }}:Zabbix agent on {HOST.NAME} is unreachable for 5 minutes"]
# {% for db_host in groups.db %}
# {% set db_loop = loop %}
# web{{ '%03d' % web_loop.index }} -> db{{ '%03d' % db_loop.index }}
# {% endfor %}
# {% endfor %}
# { rank=same
# {% for db_host in groups.db %}
# {% set db_loop = loop %}
# db{{ '%03d' % db_loop.index }} [zbx_host="{{ db_host }}"]
# {% for backup_host in groups.backup %}
# {% set backup_loop = loop %}
# db{{ '%03d' % db_loop.index }} -> backup{{ '%03d' % backup_loop.index }} [color="blue"]
# {% endfor %}
# {% endfor %}
# {% for backup_host in groups.backup %}
# {% set backup_loop = loop %}
# backup{{ '%03d' % backup_loop.index }} [zbx_host="{{ backup_host }}"]
# {% endfor %}
# }
# }
###
### Create Zabbix map "Demo Map" made of template 'map.j2'
- name: Create Zabbix map
community.zabbix.zabbix_map:
name: Demo map
state: present
data: "{{ lookup('template', 'map.j2') }}"
default_image: Server_(64)
expand_problem: no
highlight: no
label_type: label
delegate_to: localhost
run_once: yes
'''
import base64
import traceback
from io import BytesIO
from operator import itemgetter
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
try:
import pydotplus
HAS_PYDOTPLUS = True
PYDOT_IMP_ERR = Exception()
except ImportError:
PYDOT_IMP_ERR = traceback.format_exc()
HAS_PYDOTPLUS = False
try:
import webcolors
HAS_WEBCOLORS = True
WEBCOLORS_IMP_ERR = Exception()
except ImportError:
WEBCOLORS_IMP_ERR = traceback.format_exc()
HAS_WEBCOLORS = False
try:
from PIL import Image
HAS_PIL = True
PIL_IMP_ERR = Exception()
except ImportError:
PIL_IMP_ERR = traceback.format_exc()
HAS_PIL = False
class Map(ZabbixBase):
def __init__(self, module, zbx=None, zapi_wrapper=None):
super(Map, self).__init__(module, zbx, zapi_wrapper)
self.map_name = module.params['name']
self.dot_data = module.params['data']
self.width = module.params['width']
self.height = module.params['height']
self.state = module.params['state']
self.default_image = module.params['default_image']
self.map_id = self._get_sysmap_id(self.map_name)
self.margin = module.params['margin']
self.expand_problem = module.params['expand_problem']
self.highlight = module.params['highlight']
self.label_type = module.params['label_type']
self.selements_sort_keys = self._get_selements_sort_keys()
def _build_graph(self):
try:
graph_without_positions = pydotplus.graph_from_dot_data(self.dot_data)
dot_data_with_positions = graph_without_positions.create_dot()
graph_with_positions = pydotplus.graph_from_dot_data(dot_data_with_positions)
if graph_with_positions:
return graph_with_positions
except Exception as e:
self._module.fail_json(msg="Failed to build graph from DOT data: %s" % e)
def get_map_config(self):
if not self.dot_data:
self._module.fail_json(msg="'data' is mandatory with state 'present'")
graph = self._build_graph()
nodes = self._get_graph_nodes(graph)
edges = self._get_graph_edges(graph)
icon_ids = self._get_icon_ids()
map_config = {
'name': self.map_name,
'label_type': self._get_label_type_id(self.label_type),
'expandproblem': int(self.expand_problem),
'highlight': int(self.highlight),
'width': self.width,
'height': self.height,
'selements': self._get_selements(graph, nodes, icon_ids),
'links': self._get_links(nodes, edges),
}
return map_config
def _get_label_type_id(self, label_type):
label_type_ids = {
'label': 0,
'ip': 1,
'name': 2,
'status': 3,
'nothing': 4,
'custom': 5,
}
try:
label_type_id = label_type_ids[label_type]
except Exception as e:
self._module.fail_json(msg="Failed to find id for label type '%s': %s" % (label_type, e))
return label_type_id
def _get_images_info(self, data, icon_ids):
images = [
{
'dot_tag': 'zbx_image',
'zbx_property': 'iconid_off',
'mandatory': True
},
{
'dot_tag': 'zbx_image_disabled',
'zbx_property': 'iconid_disabled',
'mandatory': False
},
{
'dot_tag': 'zbx_image_maintenance',
'zbx_property': 'iconid_maintenance',
'mandatory': False
},
{
'dot_tag': 'zbx_image_problem',
'zbx_property': 'iconid_on',
'mandatory': False
}
]
images_info = {}
default_image = self.default_image if self.default_image else sorted(icon_ids.items())[0][0]
for image in images:
image_name = data.get(image['dot_tag'], None)
if not image_name:
if image['mandatory']:
image_name = default_image
else:
continue
image_name = remove_quotes(image_name)
if image_name in icon_ids:
images_info[image['zbx_property']] = icon_ids[image_name]
if not image['mandatory']:
images_info['use_iconmap'] = 0
else:
self._module.fail_json(msg="Failed to find id for image '%s'" % image_name)
return images_info
def _get_element_type(self, data):
types = {
'host': 0,
'sysmap': 1,
'trigger': 2,
'group': 3,
'image': 4
}
element_type = {
'elementtype': types['image'],
}
if LooseVersion(self._zbx_api_version) < LooseVersion('3.4'):
element_type.update({
'elementid': "0",
})
for type_name, type_id in sorted(types.items()):
field_name = 'zbx_' + type_name
if field_name in data:
method_name = '_get_' + type_name + '_id'
element_name = remove_quotes(data[field_name])
get_element_id = getattr(self, method_name, None)
if get_element_id:
elementid = get_element_id(element_name)
if elementid and int(elementid) > 0:
element_type.update({
'elementtype': type_id,
'label': element_name
})
if LooseVersion(self._zbx_api_version) < LooseVersion('3.4'):
element_type.update({
'elementid': elementid,
})
else:
element_type.update({
'elements': [{
type_name + 'id': elementid,
}],
})
break
else:
self._module.fail_json(msg="Failed to find id for %s '%s'" % (type_name, element_name))
return element_type
# get list of map elements (nodes)
def _get_selements(self, graph, nodes, icon_ids):
selements = []
icon_sizes = {}
scales = self._get_scales(graph)
for selementid, (node, data) in enumerate(nodes.items(), start=1):
selement = {
'selementid': selementid
}
data['selementid'] = selementid
images_info = self._get_images_info(data, icon_ids)
selement.update(images_info)
image_id = images_info['iconid_off']
if image_id not in icon_sizes:
icon_sizes[image_id] = self._get_icon_size(image_id)
pos = self._convert_coordinates(data['pos'], scales, icon_sizes[image_id])
selement.update(pos)
selement['label'] = remove_quotes(node)
element_type = self._get_element_type(data)
selement.update(element_type)
label = self._get_label(data)
if label:
selement['label'] = label
urls = self._get_urls(data)
if urls:
selement['urls'] = urls
selements.append(selement)
return selements
def _get_links(self, nodes, edges):
links = {}
for edge in edges:
link_id = tuple(sorted(edge.obj_dict['points']))
node1, node2 = link_id
data = edge.obj_dict['attributes']
if "style" in data and data['style'] == "invis":
continue
if link_id not in links:
links[link_id] = {
'selementid1': min(nodes[node1]['selementid'], nodes[node2]['selementid']),
'selementid2': max(nodes[node1]['selementid'], nodes[node2]['selementid']),
}
link = links[link_id]
if "color" not in link:
link['color'] = self._get_color_hex(remove_quotes(data.get('color', 'green')))
if "zbx_draw_style" not in link:
link['drawtype'] = self._get_link_draw_style_id(remove_quotes(data.get('zbx_draw_style', 'line')))
label = self._get_label(data)
if label and "label" not in link:
link['label'] = label
triggers = self._get_triggers(data)
if triggers:
if "linktriggers" not in link:
link['linktriggers'] = []
link['linktriggers'] += triggers
return list(links.values())
def _get_urls(self, data):
urls = []
for url_raw in [remove_quotes(value) for key, value in data.items() if key.startswith("zbx_url")]:
try:
name, url = url_raw.split(':', 1)
except Exception as e:
self._module.fail_json(msg="Failed to parse zbx_url='%s': %s" % (url_raw, e))
urls.append({
'name': name,
'url': url,
})
return urls
def _get_triggers(self, data):
triggers = []
for trigger_definition in [remove_quotes(value) for key, value in data.items() if key.startswith("zbx_trigger")]:
triggerid = self._get_trigger_id(trigger_definition)
if triggerid:
triggers.append({
'triggerid': triggerid,
'color': self._get_color_hex(remove_quotes(data.get('zbx_trigger_color', 'red'))),
'drawtype': self._get_link_draw_style_id(remove_quotes(data.get('zbx_trigger_draw_style', 'bold'))),
})
else:
self._module.fail_json(msg="Failed to find trigger '%s'" % (trigger_definition))
return triggers
@staticmethod
def _get_label(data, default=None):
if "zbx_label" in data:
label = remove_quotes(data['zbx_label']).replace('\\n', '\n')
elif "label" in data:
label = remove_quotes(data['label'])
else:
label = default
return label
def _get_sysmap_id(self, map_name):
exist_map = self._zapi.map.get({'filter': {'name': map_name}})
if exist_map:
return exist_map[0]['sysmapid']
return None
def _get_group_id(self, group_name):
exist_group = self._zapi.hostgroup.get({'filter': {'name': group_name}})
if exist_group:
return exist_group[0]['groupid']
return None
def map_exists(self):
return bool(self.map_id)
def create_map(self, map_config):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
result = self._zapi.map.create(map_config)
if result:
return result
except Exception as e:
self._module.fail_json(msg="Failed to create map: %s" % e)
def update_map(self, map_config):
if not self.map_id:
self._module.fail_json(msg="Failed to update map: map_id is unknown. Try to create_map instead.")
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
map_config['sysmapid'] = self.map_id
result = self._zapi.map.update(map_config)
if result:
return result
except Exception as e:
self._module.fail_json(msg="Failed to update map: %s" % e)
def delete_map(self):
if not self.map_id:
self._module.fail_json(msg="Failed to delete map: map_id is unknown.")
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.map.delete([self.map_id])
except Exception as e:
self._module.fail_json(msg="Failed to delete map, Exception: %s" % e)
def is_exist_map_correct(self, generated_map_config):
exist_map_configs = self._zapi.map.get({
'sysmapids': self.map_id,
'selectLinks': 'extend',
'selectSelements': 'extend'
})
exist_map_config = exist_map_configs[0]
if not self._is_dicts_equal(generated_map_config, exist_map_config):
return False
if not self._is_selements_equal(generated_map_config['selements'], exist_map_config['selements']):
return False
self._update_ids(generated_map_config, exist_map_config)
if not self._is_links_equal(generated_map_config['links'], exist_map_config['links']):
return False
return True
def _get_selements_sort_keys(self):
keys_to_sort = ['label']
if LooseVersion(self._zbx_api_version) < LooseVersion('3.4'):
keys_to_sort.insert(0, 'elementid')
return keys_to_sort
def _is_selements_equal(self, generated_selements, exist_selements):
if len(generated_selements) != len(exist_selements):
return False
generated_selements_sorted = sorted(generated_selements, key=itemgetter(*self.selements_sort_keys))
exist_selements_sorted = sorted(exist_selements, key=itemgetter(*self.selements_sort_keys))
for (generated_selement, exist_selement) in zip(generated_selements_sorted, exist_selements_sorted):
if LooseVersion(self._zbx_api_version) >= LooseVersion('3.4'):
if not self._is_elements_equal(generated_selement.get('elements', []), exist_selement.get('elements', [])):
return False
if not self._is_dicts_equal(generated_selement, exist_selement, ['selementid']):
return False
if not self._is_urls_equal(generated_selement.get('urls', []), exist_selement.get('urls', [])):
return False
return True
def _is_urls_equal(self, generated_urls, exist_urls):
if len(generated_urls) != len(exist_urls):
return False
generated_urls_sorted = sorted(generated_urls, key=itemgetter('name', 'url'))
exist_urls_sorted = sorted(exist_urls, key=itemgetter('name', 'url'))
for (generated_url, exist_url) in zip(generated_urls_sorted, exist_urls_sorted):
if not self._is_dicts_equal(generated_url, exist_url, ['selementid']):
return False
return True
def _is_elements_equal(self, generated_elements, exist_elements):
if len(generated_elements) != len(exist_elements):
return False
generated_elements_sorted = sorted(generated_elements, key=lambda k: k.values()[0])
exist_elements_sorted = sorted(exist_elements, key=lambda k: k.values()[0])
for (generated_element, exist_element) in zip(generated_elements_sorted, exist_elements_sorted):
if not self._is_dicts_equal(generated_element, exist_element, ['selementid']):
return False
return True
# since generated IDs differ from real Zabbix ones, make real IDs match generated ones
def _update_ids(self, generated_map_config, exist_map_config):
generated_selements_sorted = sorted(generated_map_config['selements'], key=itemgetter(*self.selements_sort_keys))
exist_selements_sorted = sorted(exist_map_config['selements'], key=itemgetter(*self.selements_sort_keys))
id_mapping = {}
for (generated_selement, exist_selement) in zip(generated_selements_sorted, exist_selements_sorted):
id_mapping[exist_selement['selementid']] = generated_selement['selementid']
for link in exist_map_config['links']:
link['selementid1'] = id_mapping[link['selementid1']]
link['selementid2'] = id_mapping[link['selementid2']]
if link['selementid2'] < link['selementid1']:
link['selementid1'], link['selementid2'] = link['selementid2'], link['selementid1']
def _is_links_equal(self, generated_links, exist_links):
if len(generated_links) != len(exist_links):
return False
generated_links_sorted = sorted(generated_links, key=itemgetter('selementid1', 'selementid2', 'color', 'drawtype'))
exist_links_sorted = sorted(exist_links, key=itemgetter('selementid1', 'selementid2', 'color', 'drawtype'))
for (generated_link, exist_link) in zip(generated_links_sorted, exist_links_sorted):
if not self._is_dicts_equal(generated_link, exist_link, ['selementid1', 'selementid2']):
return False
if not self._is_triggers_equal(generated_link.get('linktriggers', []), exist_link.get('linktriggers', [])):
return False
return True
def _is_triggers_equal(self, generated_triggers, exist_triggers):
if len(generated_triggers) != len(exist_triggers):
return False
generated_triggers_sorted = sorted(generated_triggers, key=itemgetter('triggerid'))
exist_triggers_sorted = sorted(exist_triggers, key=itemgetter('triggerid'))
for (generated_trigger, exist_trigger) in zip(generated_triggers_sorted, exist_triggers_sorted):
if not self._is_dicts_equal(generated_trigger, exist_trigger):
return False
return True
@staticmethod
def _is_dicts_equal(d1, d2, exclude_keys=None):
if exclude_keys is None:
exclude_keys = []
for key in d1.keys():
if isinstance(d1[key], dict) or isinstance(d1[key], list):
continue
if key in exclude_keys:
continue
# compare as strings since Zabbix API returns everything as strings
if key not in d2 or str(d2[key]) != str(d1[key]):
return False
return True
def _get_host_id(self, hostname):
hostid = self._zapi.host.get({'filter': {'host': hostname}})
if hostid:
return str(hostid[0]['hostid'])
def _get_trigger_id(self, trigger_definition):
try:
host, trigger = trigger_definition.split(':', 1)
except Exception as e:
self._module.fail_json(msg="Failed to parse zbx_trigger='%s': %s" % (trigger_definition, e))
triggerid = self._zapi.trigger.get({
'host': host,
'filter': {
'description': trigger
}
})
if triggerid:
return str(triggerid[0]['triggerid'])
def _get_icon_ids(self):
icons_list = self._zapi.image.get({})
icon_ids = {}
for icon in icons_list:
icon_ids[icon['name']] = icon['imageid']
return icon_ids
def _get_icon_size(self, icon_id):
icons_list = self._zapi.image.get({
'imageids': [
icon_id
],
'select_image': True
})
if len(icons_list) > 0:
icon_base64 = icons_list[0]['image']
else:
self._module.fail_json(msg="Failed to find image with id %s" % icon_id)
image = Image.open(BytesIO(base64.b64decode(icon_base64)))
icon_width, icon_height = image.size
return icon_width, icon_height
@staticmethod
def _get_node_attributes(node):
attr = {}
if "attributes" in node.obj_dict:
attr.update(node.obj_dict['attributes'])
pos = node.get_pos()
if pos is not None:
pos = remove_quotes(pos)
xx, yy = pos.split(",")
attr['pos'] = (float(xx), float(yy))
return attr
def _get_graph_nodes(self, parent):
nodes = {}
for node in parent.get_nodes():
node_name = node.get_name()
if node_name in ('node', 'graph', 'edge'):
continue
nodes[node_name] = self._get_node_attributes(node)
for subgraph in parent.get_subgraphs():
nodes.update(self._get_graph_nodes(subgraph))
return nodes
def _get_graph_edges(self, parent):
edges = []
for edge in parent.get_edges():
edges.append(edge)
for subgraph in parent.get_subgraphs():
edges += self._get_graph_edges(subgraph)
return edges
def _get_scales(self, graph):
bb = remove_quotes(graph.get_bb())
min_x, min_y, max_x, max_y = bb.split(",")
scale_x = (self.width - self.margin * 2) / (float(max_x) - float(min_x)) if float(max_x) != float(min_x) else 0
scale_y = (self.height - self.margin * 2) / (float(max_y) - float(min_y)) if float(max_y) != float(min_y) else 0
return {
'min_x': float(min_x),
'min_y': float(min_y),
'max_x': float(max_x),
'max_y': float(max_y),
'scale_x': float(scale_x),
'scale_y': float(scale_y),
}
# transform Graphviz coordinates to Zabbix's ones
def _convert_coordinates(self, pos, scales, icon_size):
return {
'x': int((pos[0] - scales['min_x']) * scales['scale_x'] - icon_size[0] / 2 + self.margin),
'y': int((scales['max_y'] - pos[1] + scales['min_y']) * scales['scale_y'] - icon_size[1] / 2 + self.margin),
}
def _get_color_hex(self, color_name):
if color_name.startswith('#'):
color_hex = color_name
else:
try:
color_hex = webcolors.name_to_hex(color_name)
except Exception as e:
self._module.fail_json(msg="Failed to get RGB hex for color '%s': %s" % (color_name, e))
color_hex = color_hex.strip('#').upper()
return color_hex
def _get_link_draw_style_id(self, draw_style):
draw_style_ids = {
'line': 0,
'bold': 2,
'dotted': 3,
'dashed': 4
}
try:
draw_style_id = draw_style_ids[draw_style]
except Exception as e:
self._module.fail_json(msg="Failed to find id for draw type '%s': %s" % (draw_style, e))
return draw_style_id
# If a string has single or double quotes around it, remove them.
def remove_quotes(s):
if (s[0] == s[-1]) and s.startswith(("'", '"')):
s = s[1:-1]
return s
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True, aliases=['map_name']),
data=dict(type='str', required=False, aliases=['dot_data']),
width=dict(type='int', default=800),
height=dict(type='int', default=600),
state=dict(type='str', default="present", choices=['present', 'absent']),
default_image=dict(type='str', required=False, aliases=['image']),
margin=dict(type='int', default=40),
expand_problem=dict(type='bool', default=True),
highlight=dict(type='bool', default=True),
label_type=dict(type='str', default='name', choices=['label', 'ip', 'name', 'status', 'nothing', 'custom']),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
if not HAS_PYDOTPLUS:
module.fail_json(msg=missing_required_lib('pydotplus', url='https://pypi.org/project/pydotplus/'), exception=PYDOT_IMP_ERR)
if not HAS_WEBCOLORS:
module.fail_json(msg=missing_required_lib('webcolors', url='https://pypi.org/project/webcolors/'), exception=WEBCOLORS_IMP_ERR)
if not HAS_PIL:
module.fail_json(msg=missing_required_lib('Pillow', url='https://pypi.org/project/Pillow/'), exception=PIL_IMP_ERR)
sysmap = Map(module)
if sysmap.state == "absent":
if sysmap.map_exists():
sysmap.delete_map()
module.exit_json(changed=True, result="Successfully deleted map: %s" % sysmap.map_name)
else:
module.exit_json(changed=False)
else:
map_config = sysmap.get_map_config()
if sysmap.map_exists():
if sysmap.is_exist_map_correct(map_config):
module.exit_json(changed=False)
else:
sysmap.update_map(map_config)
module.exit_json(changed=True, result="Successfully updated map: %s" % sysmap.map_name)
else:
sysmap.create_map(map_config)
module.exit_json(changed=True, result="Successfully created map: %s" % sysmap.map_name)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,802 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_mediatype
short_description: Create/Update/Delete Zabbix media types
description:
- This module allows you to create, modify and delete Zabbix media types.
author:
- Ruben Tsirunyan (@rubentsirunyan)
requirements:
- "python >= 2.6"
options:
name:
type: 'str'
description:
- Name of the media type.
required: true
description:
type: 'str'
description:
- Description of the media type.
- Works only with Zabbix versions 4.4 or newer.
default: ''
state:
type: 'str'
description:
- Desired state of the mediatype.
- On C(present), it will create a mediatype if it does not exist or update the mediatype if the associated data is different.
- On C(absent), it will remove the mediatype if it exists.
choices:
- present
- absent
default: 'present'
type:
type: 'str'
description:
- Type of the media type.
- Media types I(jabber) and I(ez_texting) works only with Zabbix versions 4.2 or older.
- Media type I(webhook) works only with Zabbix versions 4.4 or newer.
choices:
- email
- script
- sms
- webhook
- jabber
- ez_texting
required: true
status:
type: 'str'
description:
- Whether the media type is enabled or no.
choices:
- enabled
- disabled
default: 'enabled'
max_sessions:
type: 'int'
description:
- The maximum number of alerts that can be processed in parallel.
- Possible value is 1 when I(type=sms) and 0-100 otherwise.
- Works only with Zabbix versions 3.4 or newer.
default: 1
max_attempts:
type: 'int'
description:
- The maximum number of attempts to send an alert.
- Possible range is 0-10.
- Works only with Zabbix versions 3.4 or newer.
default: 3
attempt_interval:
type: 'str'
description:
- The interval between retry attempts.
- Possible range is 0-60s in Zabbix < 5.0 or 0-1h in Zabbix >= 5.0.
- Works only with Zabbix versions 3.4 or newer.
default: 10s
script_name:
type: 'str'
description:
- The name of the executed script.
- Required when I(type=script).
script_params:
type: 'list'
elements: str
description:
- List of script parameters.
- Required when I(type=script).
gsm_modem:
type: 'str'
description:
- Serial device name of the gsm modem.
- Required when I(type=sms).
username:
type: 'str'
description:
- Username or Jabber identifier.
- Required when I(type=jabber) or I(type=ez_texting).
- Required when I(type=email) and I(smtp_authentication=true).
password:
type: 'str'
description:
- Authentication password.
- Required when I(type=jabber) or I(type=ez_texting).
- Required when I(type=email) and I(smtp_authentication=true).
smtp_server:
type: 'str'
description:
- SMTP server host.
- Required when I(type=email).
default: 'localhost'
smtp_server_port:
type: 'int'
description:
- SMTP server port.
- Required when I(type=email).
default: 25
smtp_helo:
type: 'str'
description:
- SMTP HELO.
- Required when I(type=email).
default: 'localhost'
smtp_email:
type: 'str'
description:
- Email address from which notifications will be sent.
- Required when I(type=email).
smtp_authentication:
type: 'bool'
description:
- Whether SMTP authentication with username and password should be enabled or not.
- If set to C(true), C(username) and C(password) should be specified.
default: false
smtp_security:
type: 'str'
description:
- SMTP connection security level to use.
choices:
- None
- STARTTLS
- SSL/TLS
smtp_verify_host:
type: 'bool'
description:
- SSL verify host for SMTP.
- Can be specified when I(smtp_security=STARTTLS) or I(smtp_security=SSL/TLS)
default: false
smtp_verify_peer:
type: 'bool'
description:
- SSL verify peer for SMTP.
- Can be specified when I(smtp_security=STARTTLS) or I(smtp_security=SSL/TLS)
default: false
message_text_limit:
type: 'str'
description:
- The message text limit.
- Required when I(type=ez_texting).
- 160 characters for USA and 136 characters for Canada.
choices:
- USA
- Canada
webhook_script:
type: 'str'
description:
- Required when I(type=webhook).
- JavaScript code that will perform webhook operation.
- This code has access to all parameters in I(webhook_params).
- It may perform HTTP GET, POST, PUT and DELETE requests and has control over HTTP headers and request body.
- It may return OK status along with an optional list of tags and tag values or an error string.
- Works only with Zabbix versions 4.4 or newer.
webhook_timeout:
type: 'str'
description:
- Can be used when I(type=webhook).
- Execution timeout for JavaScript code in I(webhook_script).
- Possible values are 1-60s.
default: 30s
process_tags:
type: 'bool'
description:
- Can be used when I(type=webhook).
- Process returned JSON property values as tags.
- These tags are added to the already existing (if any) problem event tags in Zabbix.
default: false
event_menu:
type: 'bool'
description:
- Can be used when I(type=webhook).
- Includes entry in Event menu with link to created external ticket.
default: false
event_menu_url:
type: 'str'
description:
- Requred when I(event_menu=True).
- Event menu entry underlying URL.
event_menu_name:
type: 'str'
description:
- Requred when I(event_menu=True).
- Event menu entry name.
webhook_params:
type: 'list'
elements: 'dict'
description:
- Can be used when I(type=webhook).
- Webhook variables that are passed to webhook script when executed.
default: []
suboptions:
name:
type: 'str'
description:
- Name of the parameter.
required: true
value:
type: 'str'
description:
- Value of the parameter.
- All macros that are supported in problem notifications are supported in the parameters.
- Values are URL-encoded automatically. Values from macros are resolved and then URL-encoded automatically.
default: ''
message_templates:
type: 'list'
elements: 'dict'
description:
- Default notification messages for the event types.
- Works only with Zabbix versions 5.0 or newer.
default: []
suboptions:
eventsource:
type: 'str'
description:
- Event source.
- Required when I(recovery) is used.
choices:
- triggers
- discovery
- autoregistration
- internal
recovery:
type: 'str'
description:
- Operation mode.
- Required when I(eventsource) is used.
choices:
- operations
- recovery_operations
- update_operations
subject:
type: 'str'
description:
- Subject of the default message.
- May contain macros and is limited to 255 characters.
default: ''
body:
type: 'str'
description:
- Body of the default message.
- May contain macros.
default: ''
extends_documentation_fragment:
- community.zabbix.zabbix
'''
RETURN = r''' # '''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: 'Create an email mediatype with SMTP authentication'
community.zabbix.zabbix_mediatype:
name: "Ops email"
type: 'email'
smtp_server: 'example.com'
smtp_server_port: 2000
smtp_email: 'ops@example.com'
smtp_authentication: true
username: 'smtp_user'
password: 'smtp_pass'
- name: 'Create a script mediatype'
community.zabbix.zabbix_mediatype:
name: "my script"
type: 'script'
script_name: 'my_script.py'
script_params:
- 'arg1'
- 'arg2'
- name: 'Create a jabber mediatype'
community.zabbix.zabbix_mediatype:
name: "My jabber"
type: 'jabber'
username: 'jabber_id'
password: 'jabber_pass'
- name: 'Create a SMS mediatype'
community.zabbix.zabbix_mediatype:
name: "My SMS Mediatype"
type: 'sms'
gsm_modem: '/dev/ttyS0'
# Supported since Zabbix 4.4
- name: 'Create a webhook mediatype'
community.zabbix.zabbix_mediatype:
name: "My webhook Mediatype"
type: 'webhook'
webhook_script: "{{ lookup('file', 'slack.js') }}"
webhook_params:
- name: alert_message
value: '{ALERT.MESSAGE}'
- name: zabbix_url
value: '{$ZABBIX.URL}'
process_tags: True
event_menu: true
event_menu_name: "Open in Slack: '{EVENT.TAGS.__channel_name}'"
event_menu_url: '{EVENT.TAGS.__message_link}'
# Supported since Zabbix 5.0
- name: 'Create an email mediatype with message templates'
community.zabbix.zabbix_mediatype:
name: "Ops email"
type: 'email'
smtp_email: 'ops@example.com'
message_templates:
- eventsource: triggers
recovery: operations
subject: "Problem: {EVENT.NAME}"
body: "Problem started at {EVENT.TIME} on {EVENT.DATE}\r\nProblem name: {EVENT.NAME}\r\n"
- eventsource: triggers
recovery: recovery_operations
subject: "Resolved: {EVENT.NAME}"
body: "Problem resolved at {EVENT.TIME} on {EVENT.DATE}\r\nProblem name: {EVENT.NAME}\r\n"
- eventsource: triggers
recovery: update_operations
subject: "Updated problem: {EVENT.NAME}"
body: "{USER.FULLNAME} {EVENT.UPDATE.ACTION} problem at {EVENT.UPDATE.DATE} {EVENT.UPDATE.TIME}.\r\n"
- eventsource: discovery
recovery: operations
subject: "Discovery: {DISCOVERY.DEVICE.STATUS} {DISCOVERY.DEVICE.IPADDRESS}"
body: "Discovery rule: {DISCOVERY.RULE.NAME}\r\n\r\nDevice IP: {DISCOVERY.DEVICE.IPADDRESS}"
- eventsource: autoregistration
recovery: operations
subject: "Autoregistration: {HOST.HOST}"
body: "Host name: {HOST.HOST}\r\nHost IP: {HOST.IP}\r\nAgent port: {HOST.PORT}"
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
def diff(existing, new):
"""Constructs the diff for Ansible's --diff option.
Args:
existing (dict): Existing mediatype data.
new (dict): New mediatype data.
Returns:
A dictionary like {'before': existing, 'after': new}
with filtered empty values.
"""
before = {}
after = {}
for key in new:
before[key] = existing[key]
if new[key] is None:
after[key] = ''
else:
after[key] = new[key]
return {'before': before, 'after': after}
class MediaTypeModule(ZabbixBase):
def check_if_mediatype_exists(self, name):
"""Checks if mediatype exists.
Args:
name: Zabbix mediatype name
Returns:
Tuple of (True, `id of the mediatype`) if mediatype exists, (False, None) otherwise
"""
filter_key_name = 'description'
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.4'):
# description key changed to name key from zabbix 4.4
filter_key_name = 'name'
try:
mediatype_list = self._zapi.mediatype.get({
'output': 'extend',
'filter': {filter_key_name: [name]}
})
if len(mediatype_list) < 1:
return False, None
else:
return True, mediatype_list[0]['mediatypeid']
except Exception as e:
self._module.fail_json(msg="Failed to get ID of the mediatype '{name}': {e}".format(name=name, e=e))
def construct_parameters(self):
"""Translates data to a format suitable for Zabbix API and filters
the ones that are related to the specified mediatype type.
Returns:
A dictionary of arguments that are related to transport type,
and are in a format that is understandable by Zabbix API.
"""
truths = {'False': '0', 'True': '1'}
parameters = dict(
status='0' if self._module.params['status'] == 'enabled' else '1',
type={
'email': '0',
'script': '1',
'sms': '2',
'jabber': '3',
'webhook': '4',
'ez_texting': '100'
}.get(self._module.params['type']),
)
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.4'):
parameters.update(dict(
name=self._module.params['name'],
description=self._module.params['description'],
))
else:
parameters.update(dict(description=self._module.params['name']))
if LooseVersion(self._zbx_api_version) >= LooseVersion('3.4'):
parameters.update(dict(
maxsessions=str(self._module.params['max_sessions']),
maxattempts=str(self._module.params['max_attempts']),
attempt_interval=str(self._module.params['attempt_interval'])
))
if self._module.params['message_templates'] and LooseVersion(self._zbx_api_version) >= LooseVersion('5.0'):
msg_templates = []
for template in self._module.params['message_templates']:
msg_templates.append(dict(
eventsource={
'triggers': '0',
'discovery': '1',
'autoregistration': '2',
'internal': '3'}.get(template['eventsource']),
recovery={
'operations': '0',
'recovery_operations': '1',
'update_operations': '2'}.get(template['recovery']),
subject=template['subject'],
message=template['body']
))
parameters.update(dict(message_templates=msg_templates))
if self._module.params['type'] == 'email':
parameters.update(dict(
smtp_server=self._module.params['smtp_server'],
smtp_port=str(self._module.params['smtp_server_port']),
smtp_helo=self._module.params['smtp_helo'],
smtp_email=self._module.params['smtp_email'],
smtp_security={'None': '0', 'STARTTLS': '1', 'SSL/TLS': '2'}.get(str(self._module.params['smtp_security'])),
smtp_authentication=truths.get(str(self._module.params['smtp_authentication'])),
smtp_verify_host=truths.get(str(self._module.params['smtp_verify_host'])),
smtp_verify_peer=truths.get(str(self._module.params['smtp_verify_peer'])),
username=self._module.params['username'],
passwd=self._module.params['password']
))
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.0'):
if parameters['smtp_authentication'] == '0':
parameters.pop('username')
parameters.pop('passwd')
return parameters
elif self._module.params['type'] == 'script':
if self._module.params['script_params'] is None:
_script_params = '' # ZBX-15706
else:
_script_params = '\n'.join(str(i) for i in self._module.params['script_params']) + '\n'
parameters.update(dict(
exec_path=self._module.params['script_name'],
exec_params=_script_params
))
return parameters
elif self._module.params['type'] == 'sms':
parameters.update(dict(gsm_modem=self._module.params['gsm_modem']))
return parameters
elif self._module.params['type'] == 'webhook' and LooseVersion(self._zbx_api_version) >= LooseVersion('4.4'):
parameters.update(dict(
script=self._module.params['webhook_script'],
timeout=self._module.params['webhook_timeout'],
process_tags=truths.get(str(self._module.params['process_tags'])),
show_event_menu=truths.get(str(self._module.params['event_menu'])),
parameters=self._module.params['webhook_params']
))
if self._module.params['event_menu']:
parameters.update(dict(
event_menu_url=self._module.params['event_menu_url'],
event_menu_name=self._module.params['event_menu_name']
))
return parameters
elif self._module.params['type'] == 'jabber' and LooseVersion(self._zbx_api_version) <= LooseVersion('4.2'):
parameters.update(dict(
username=self._module.params['username'],
passwd=self._module.params['password']
))
return parameters
elif self._module.params['type'] == 'ez_texting' and LooseVersion(self._zbx_api_version) <= LooseVersion('4.2'):
parameters.update(dict(
username=self._module.params['username'],
passwd=self._module.params['password'],
exec_path={'USA': '0', 'Canada': '1'}.get(self._module.params['message_text_limit']),
))
return parameters
self._module.fail_json(msg="%s is unsupported for Zabbix version %s" % (parameters['unsupported_parameter'], parameters['zbx_api_version']))
def validate_params(self, params):
"""Validates arguments that are required together.
Fails the module with the message that shows the missing
requirements if there are some.
Args:
params (list): Each element of this list
is a list like
['argument_key', 'argument_value', ['required_arg_1',
'required_arg_2']].
Format is the same as `required_if` parameter of AnsibleModule.
"""
for param in params:
if self._module.params[param[0]] == param[1]:
if None in [self._module.params[i] for i in param[2]]:
self._module.fail_json(
msg="Following arguments are required when {key} is {value}: {arguments}".format(
key=param[0],
value=param[1],
arguments=', '.join(param[2])
)
)
def get_update_params(self, mediatype_id, **kwargs):
"""Filters only the parameters that are different and need to be updated.
Args:
mediatype_id (int): ID of the mediatype to be updated.
**kwargs: Parameters for the new mediatype.
Returns:
A tuple where the first element is a dictionary of parameters
that need to be updated and the second one is a dictionary
returned by diff() function with
existing mediatype data and new params passed to it.
"""
get_params = {'output': 'extend', 'mediatypeids': [mediatype_id]}
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.0'):
get_params.update({'selectMessageTemplates': 'extend'})
existing_mediatype = self._zapi.mediatype.get(get_params)[0]
if existing_mediatype['type'] != kwargs['type']:
return kwargs, diff(existing_mediatype, kwargs)
else:
params_to_update = {}
for key in kwargs:
# sort list of parameters to prevent mismatch due to reordering
if key == 'parameters' and (kwargs[key] != [] or existing_mediatype[key] != []):
kwargs[key] = sorted(kwargs[key], key=lambda x: x['name'])
existing_mediatype[key] = sorted(existing_mediatype[key], key=lambda x: x['name'])
if key == 'message_templates' and (kwargs[key] != [] or existing_mediatype[key] != []):
kwargs[key] = sorted(kwargs[key], key=lambda x: x['subject'])
existing_mediatype[key] = sorted(existing_mediatype[key], key=lambda x: x['subject'])
if (not (kwargs[key] is None and existing_mediatype[key] == '')) and kwargs[key] != existing_mediatype[key]:
params_to_update[key] = kwargs[key]
return params_to_update, diff(existing_mediatype, kwargs)
def delete_mediatype(self, mediatype_id):
try:
return self._zapi.mediatype.delete([mediatype_id])
except Exception as e:
self._module.fail_json(msg="Failed to delete mediatype '{_id}': {e}".format(_id=mediatype_id, e=e))
def update_mediatype(self, **kwargs):
try:
self._zapi.mediatype.update(kwargs)
except Exception as e:
self._module.fail_json(msg="Failed to update mediatype '{_id}': {e}".format(_id=kwargs['mediatypeid'], e=e))
def create_mediatype(self, **kwargs):
try:
self._zapi.mediatype.create(kwargs)
except Exception as e:
self._module.fail_json(msg="Failed to create mediatype '{name}': {e}".format(name=kwargs['name'], e=e))
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
description=dict(type='str', required=False, default=''),
state=dict(type='str', default='present', choices=['present', 'absent']),
type=dict(type='str', choices=['email', 'script', 'sms', 'webhook', 'jabber', 'ez_texting'], required=True),
status=dict(type='str', default='enabled', choices=['enabled', 'disabled'], required=False),
max_sessions=dict(type='int', default=1, required=False),
max_attempts=dict(type='int', default=3, required=False),
attempt_interval=dict(type='str', default='10s', required=False),
# Script
script_name=dict(type='str', required=False),
script_params=dict(type='list', required=False),
# SMS
gsm_modem=dict(type='str', required=False),
# Jabber
username=dict(type='str', required=False),
password=dict(type='str', required=False, no_log=True),
# Email
smtp_server=dict(type='str', default='localhost', required=False),
smtp_server_port=dict(type='int', default=25, required=False),
smtp_helo=dict(type='str', default='localhost', required=False),
smtp_email=dict(type='str', required=False),
smtp_security=dict(type='str', required=False, choices=['None', 'STARTTLS', 'SSL/TLS']),
smtp_authentication=dict(type='bool', default=False, required=False),
smtp_verify_host=dict(type='bool', default=False, required=False),
smtp_verify_peer=dict(type='bool', default=False, required=False),
# EZ Text
message_text_limit=dict(type='str', required=False, choices=['USA', 'Canada']),
# Webhook
webhook_script=dict(type='str'),
webhook_timeout=dict(type='str', default='30s'),
process_tags=dict(type='bool', default=False),
event_menu=dict(type='bool', default=False),
event_menu_url=dict(type='str'),
event_menu_name=dict(type='str'),
webhook_params=dict(
type='list',
elements='dict',
default=[],
required=False,
options=dict(
name=dict(type='str', required=True),
value=dict(type='str', default='')
)
),
message_templates=dict(
type='list',
elements='dict',
default=[],
required=False,
options=dict(
eventsource=dict(type='str', choices=['triggers', 'discovery', 'autoregistration', 'internal']),
recovery=dict(type='str', choices=['operations', 'recovery_operations', 'update_operations']),
subject=dict(type='str', default=''),
body=dict(type='str', default='')
),
required_together=[
['eventsource', 'recovery']
],
)
))
# this is used to simulate `required_if` of `AnsibleModule`, but only when state=present
required_params = [
['type', 'email', ['smtp_email']],
['type', 'script', ['script_name']],
['type', 'sms', ['gsm_modem']],
['type', 'jabber', ['username', 'password']],
['type', 'ez_texting', ['username', 'password', 'message_text_limit']],
['type', 'webhook', ['webhook_script']],
['event_menu', True, ['event_menu_url', 'event_menu_name']],
['smtp_authentication', True, ['username', 'password']]
]
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
state = module.params['state']
name = module.params['name']
mediatype = MediaTypeModule(module)
if module.params['state'] == 'present':
mediatype.validate_params(required_params)
mediatype_exists, mediatype_id = mediatype.check_if_mediatype_exists(name)
parameters = mediatype.construct_parameters()
if mediatype_exists:
if state == 'absent':
if module.check_mode:
module.exit_json(
changed=True,
msg="Mediatype would have been deleted. Name: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
mediatype_id = mediatype.delete_mediatype(mediatype_id)
module.exit_json(
changed=True,
msg="Mediatype deleted. Name: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
else:
params_to_update, diff = mediatype.get_update_params(mediatype_id, **parameters)
if params_to_update == {}:
module.exit_json(
changed=False,
msg="Mediatype is up to date: {name}".format(name=name)
)
else:
if module.check_mode:
module.exit_json(
changed=True,
diff=diff,
msg="Mediatype would have been updated. Name: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
mediatype_id = mediatype.update_mediatype(mediatypeid=mediatype_id, **params_to_update)
module.exit_json(
changed=True,
diff=diff,
msg="Mediatype updated. Name: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
else:
if state == "absent":
module.exit_json(changed=False)
else:
if module.check_mode:
module.exit_json(
changed=True,
msg="Mediatype would have been created. Name: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
mediatype_id = mediatype.create_mediatype(**parameters)
module.exit_json(
changed=True,
msg="Mediatype created: {name}, ID: {_id}".format(
name=name,
_id=mediatype_id
)
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,444 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, Alen Komic
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_proxy
short_description: Create/delete/get/update Zabbix proxies
description:
- This module allows you to create, modify, get and delete Zabbix proxy entries.
author:
- "Alen Komic (@akomic)"
requirements:
- "python >= 2.6"
options:
proxy_name:
description:
- Name of the proxy in Zabbix.
required: true
type: str
proxy_address:
description:
- Comma-delimited list of IP/CIDR addresses or DNS names to accept active proxy requests from.
- Requires I(status=active).
- Works only with >= Zabbix 4.0. ( remove option for <= 4.0 )
required: false
type: str
description:
description:
- Description of the proxy.
required: false
type: str
status:
description:
- Type of proxy. (4 - active, 5 - passive)
required: false
choices: ['active', 'passive']
default: "active"
type: str
tls_connect:
description:
- Connections to proxy.
required: false
choices: ['no_encryption','PSK','certificate']
default: 'no_encryption'
type: str
tls_accept:
description:
- Connections from proxy.
required: false
choices: ['no_encryption','PSK','certificate']
default: 'no_encryption'
type: str
ca_cert:
description:
- Certificate issuer.
required: false
aliases: [ tls_issuer ]
type: str
tls_subject:
description:
- Certificate subject.
required: false
type: str
tls_psk_identity:
description:
- PSK identity. Required if either I(tls_connect) or I(tls_accept) has PSK enabled.
required: false
type: str
tls_psk:
description:
- The preshared key, at least 32 hex digits. Required if either I(tls_connect) or I(tls_accept) has PSK enabled.
required: false
type: str
state:
description:
- State of the proxy.
- On C(present), it will create if proxy does not exist or update the proxy if the associated data is different.
- On C(absent) will remove a proxy if it exists.
required: false
choices: ['present', 'absent']
default: "present"
type: str
interface:
description:
- Dictionary with params for the interface when proxy is in passive mode.
- For more information, review proxy interface documentation at
- U(https://www.zabbix.com/documentation/4.0/manual/api/reference/proxy/object#proxy_interface).
required: false
suboptions:
useip:
type: int
description:
- Connect to proxy interface with IP address instead of DNS name.
- 0 (don't use ip), 1 (use ip).
default: 0
choices: [0, 1]
ip:
type: str
description:
- IP address used by proxy interface.
- Required if I(useip=1).
default: ''
dns:
type: str
description:
- DNS name of the proxy interface.
- Required if I(useip=0).
default: ''
port:
type: str
description:
- Port used by proxy interface.
default: '10051'
type:
type: int
description:
- Interface type to add.
- This suboption is currently ignored for Zabbix proxy.
- This suboption is deprecated since Ansible 2.10 and will eventually be removed in 2.14.
required: false
default: 0
main:
type: int
description:
- Whether the interface is used as default.
- This suboption is currently ignored for Zabbix proxy.
- This suboption is deprecated since Ansible 2.10 and will eventually be removed in 2.14.
required: false
default: 0
default: {}
type: dict
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create or update a proxy with proxy type active
community.zabbix.zabbix_proxy:
proxy_name: ExampleProxy
description: ExampleProxy
status: active
state: present
proxy_address: ExampleProxy.local
- name: Create a new passive proxy using only it's IP
community.zabbix.zabbix_proxy:
proxy_name: ExampleProxy
description: ExampleProxy
status: passive
state: present
interface:
useip: 1
ip: 10.1.1.2
port: 10051
- name: Create a new passive proxy using only it's DNS
community.zabbix.zabbix_proxy:
proxy_name: ExampleProxy
description: ExampleProxy
status: passive
state: present
interface:
dns: proxy.example.com
port: 10051
'''
RETURN = r''' # '''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Proxy(ZabbixBase):
def __init__(self, module, zbx=None, zapi_wrapper=None):
super(Proxy, self).__init__(module, zbx, zapi_wrapper)
self.existing_data = None
def proxy_exists(self, proxy_name):
result = self._zapi.proxy.get({'output': 'extend',
'selectInterface': 'extend',
'filter': {'host': proxy_name}})
if len(result) > 0 and 'proxyid' in result[0]:
self.existing_data = result[0]
return result[0]['proxyid']
else:
return result
def add_proxy(self, data):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
parameters = {}
for item in data:
if data[item]:
parameters[item] = data[item]
if 'proxy_address' in data and data['status'] != '5':
parameters.pop('proxy_address', False)
if 'interface' in data and data['status'] != '6':
parameters.pop('interface', False)
else:
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.0'):
parameters['interface'].pop('type')
parameters['interface'].pop('main')
proxy_ids_list = self._zapi.proxy.create(parameters)
self._module.exit_json(changed=True,
result="Successfully added proxy %s (%s)" % (data['host'], data['status']))
if len(proxy_ids_list) >= 1:
return proxy_ids_list['proxyids'][0]
except Exception as e:
self._module.fail_json(msg="Failed to create proxy %s: %s" % (data['host'], e))
def delete_proxy(self, proxy_id, proxy_name):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.proxy.delete([proxy_id])
self._module.exit_json(changed=True, result="Successfully deleted proxy %s" % proxy_name)
except Exception as e:
self._module.fail_json(msg="Failed to delete proxy %s: %s" % (proxy_name, str(e)))
def update_proxy(self, proxy_id, data):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
parameters = {}
for key in data:
if data[key]:
parameters[key] = data[key]
if 'interface' in parameters:
if parameters['status'] == '5':
# Active proxy
parameters.pop('interface', False)
else:
# Passive proxy
parameters['interface']['useip'] = str(parameters['interface']['useip'])
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.0.0'):
parameters['interface'].pop('type', False)
parameters['interface'].pop('main', False)
else:
parameters['interface']['type'] = '0'
parameters['interface']['main'] = '1'
if ('interface' in self.existing_data
and isinstance(self.existing_data['interface'], dict)):
new_interface = self.existing_data['interface'].copy()
new_interface.update(parameters['interface'])
parameters['interface'] = new_interface
if parameters['status'] == '5':
# Active proxy
parameters.pop('tls_connect', False)
else:
# Passive proxy
parameters.pop('tls_accept', False)
parameters['proxyid'] = proxy_id
change_parameters = {}
difference = zabbix_utils.helper_cleanup_data(zabbix_utils.helper_compare_dictionaries(parameters, self.existing_data, change_parameters))
if difference == {}:
self._module.exit_json(changed=False)
else:
difference['proxyid'] = proxy_id
self._zapi.proxy.update(parameters)
self._module.exit_json(
changed=True,
result="Successfully updated proxy %s (%s)" %
(data['host'], proxy_id)
)
except Exception as e:
self._module.fail_json(msg="Failed to update proxy %s: %s" %
(data['host'], e))
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
proxy_name=dict(type='str', required=True),
proxy_address=dict(type='str', required=False),
status=dict(type='str', default="active", choices=['active', 'passive']),
state=dict(type='str', default="present", choices=['present', 'absent']),
description=dict(type='str', required=False),
tls_connect=dict(type='str', default='no_encryption', choices=['no_encryption', 'PSK', 'certificate']),
tls_accept=dict(type='str', default='no_encryption', choices=['no_encryption', 'PSK', 'certificate']),
ca_cert=dict(type='str', required=False, default=None, aliases=['tls_issuer']),
tls_subject=dict(type='str', required=False, default=None),
tls_psk_identity=dict(type='str', required=False, default=None),
tls_psk=dict(type='str', required=False, default=None, no_log=True),
interface=dict(
type='dict',
required=False,
default={},
options=dict(
useip=dict(type='int', choices=[0, 1], default=0),
ip=dict(type='str', default=''),
dns=dict(type='str', default=''),
port=dict(type='str', default='10051'),
type=dict(type='int', default=0, removed_in_version="3.0.0", removed_from_collection='community.zabbix'), # was Ansible 2.14
main=dict(type='int', default=0, removed_in_version="3.0.0", removed_from_collection='community.zabbix'), # was Ansible 2.14
),
)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
proxy_name = module.params['proxy_name']
proxy_address = module.params['proxy_address']
description = module.params['description']
status = module.params['status']
tls_connect = module.params['tls_connect']
tls_accept = module.params['tls_accept']
tls_issuer = module.params['ca_cert']
tls_subject = module.params['tls_subject']
tls_psk_identity = module.params['tls_psk_identity']
tls_psk = module.params['tls_psk']
state = module.params['state']
interface = module.params['interface']
# convert enabled to 0; disabled to 1
status = 6 if status == "passive" else 5
if tls_connect == 'certificate':
tls_connect = 4
elif tls_connect == 'PSK':
tls_connect = 2
else:
tls_connect = 1
if tls_accept == 'certificate':
tls_accept = 4
elif tls_accept == 'PSK':
tls_accept = 2
else:
tls_accept = 1
proxy = Proxy(module)
# check if proxy already exists
proxy_id = proxy.proxy_exists(proxy_name)
if proxy_id:
if state == "absent":
# remove proxy
proxy.delete_proxy(proxy_id, proxy_name)
else:
proxy.update_proxy(proxy_id, {
'host': proxy_name,
'description': description,
'status': str(status),
'tls_connect': str(tls_connect),
'tls_accept': str(tls_accept),
'tls_issuer': tls_issuer,
'tls_subject': tls_subject,
'tls_psk_identity': tls_psk_identity,
'tls_psk': tls_psk,
'interface': interface,
'proxy_address': proxy_address
})
else:
if state == "absent":
# the proxy is already deleted.
module.exit_json(changed=False)
proxy_id = proxy.add_proxy(data={
'host': proxy_name,
'description': description,
'status': str(status),
'tls_connect': str(tls_connect),
'tls_accept': str(tls_accept),
'tls_issuer': tls_issuer,
'tls_subject': tls_subject,
'tls_psk_identity': tls_psk_identity,
'tls_psk': tls_psk,
'interface': interface,
'proxy_address': proxy_address
})
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,183 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, D3DeFi
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: zabbix_proxy_info
short_description: Gather information about Zabbix proxy
version_added: 1.5.0
author:
- Dusan Matejka (@D3DeFi)
description:
- This module allows you to obtain detailed information about configured zabbix proxies.
requirements:
- "python >= 2.6"
options:
proxy_name:
description:
- Name of the Zabbix proxy.
required: true
type: str
proxy_hosts:
description:
- Also return list of hosts monitored by the proxy.
required: false
default: false
type: bool
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get zabbix proxy info alongside the list of hosts monitored by the proxy
community.zabbix.zabbix_proxy_info:
server_url: "http://zabbix.example.com/zabbix/"
login_user: admin
login_password: secret
proxy_name: zbx01.example.com
proxy_hosts: True
'''
RETURN = '''
zabbix_proxy:
description: example
returned: always
type: dict
sample: {
"auto_compress": "1",
"custom_interfaces": "0",
"description": "ExampleProxy",
"discover": "0",
"flags": "0",
"host": "ExampleProxy",
"hosts": [
{
"host": "ExampleHost",
"hostid": "10453"
}
],
"interface": {
"available": "0",
"details": [],
"disable_until": "0",
"dns": "ExampleProxy.local",
"error": "",
"errors_from": "0",
"hostid": "10452",
"interfaceid": "10",
"ip": "10.1.1.2",
"main": "1",
"port": "10051",
"type": "0",
"useip": "1"
},
"ipmi_authtype": "-1",
"ipmi_password": "",
"ipmi_privilege": "2",
"ipmi_username": "",
"lastaccess": "0",
"maintenance_from": "0",
"maintenance_status": "0",
"maintenance_type": "0",
"maintenanceid": "0",
"name": "",
"proxy_address": "",
"proxy_hostid": "0",
"proxyid": "10452",
"status": "6",
"templateid": "0",
"tls_accept": "1",
"tls_connect": "1",
"tls_issuer": "",
"tls_subject": "",
"uuid": ""
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Proxy(ZabbixBase):
def get_proxy(self, name, hosts=False):
result = {}
params = {
'filter': {
'host': name
},
'output': 'extend',
'selectInterface': 'extend',
}
if hosts:
params['selectHosts'] = ['host', 'hostid']
try:
result = self._zapi.proxy.get(params)
except Exception as e:
self._module.fail_json(msg="Failed to get proxy information: %s" % e)
return result[0] if result else {}
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
proxy_name=dict(type='str', required=True),
proxy_hosts=dict(type='bool', required=False, default=False),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
name = module.params['proxy_name']
hosts = module.params['proxy_hosts']
proxy = Proxy(module)
result = proxy.get_proxy(name, hosts)
module.exit_json(changed=False, zabbix_proxy=result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,470 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013-2014, Epic Games, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_screen
short_description: Create/update/delete Zabbix screens
description:
- This module allows you to create, modify and delete Zabbix screens and associated graph data.
author:
- "Cove (@cove)"
- "Tony Minfei Ding (!UNKNOWN)"
- "Harrison Gu (@harrisongu)"
requirements:
- "python >= 2.6"
- "Zabbix <= 5.2"
options:
screens:
description:
- List of screens to be created/updated/deleted (see example).
type: list
elements: dict
required: true
suboptions:
screen_name:
description:
- Screen name will be used.
- If a screen has already been added, the screen name won't be updated.
type: str
required: true
host_group:
description:
- Host group(s) will be used for searching hosts.
- Required if I(state=present).
type: list
elements: str
aliases: [ 'host_groups' ]
state:
description:
- I(present) - Create a screen if it doesn't exist. If the screen already exists, the screen will be updated as needed.
- I(absent) - If a screen exists, the screen will be deleted.
type: str
default: present
choices:
- absent
- present
graph_names:
description:
- Graph names will be added to a screen. Case insensitive.
- Required if I(state=present).
type: list
elements: str
graph_width:
description:
- Graph width will be set in graph settings.
type: int
graph_height:
description:
- Graph height will be set in graph settings.
type: int
graphs_in_row:
description:
- Limit columns of a screen and make multiple rows.
type: int
default: 3
sort:
description:
- Sort hosts alphabetically.
- If there are numbers in hostnames, leading zero should be used.
type: bool
default: no
extends_documentation_fragment:
- community.zabbix.zabbix
notes:
- Too many concurrent updates to the same screen may cause Zabbix to return errors, see examples for a workaround if needed.
- Screens where removed from Zabbix with Version 5.4
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Screens where removed from Zabbix with Version 5.4
# Create/update a screen.
- name: Create a new screen or update an existing screen's items 5 in a row
community.zabbix.zabbix_screen:
screens:
- screen_name: ExampleScreen1
host_group: Example group1
state: present
graph_names:
- Example graph1
- Example graph2
graph_width: 200
graph_height: 100
graphs_in_row: 5
# Create/update multi-screen
- name: Create two of new screens or update the existing screens' items
community.zabbix.zabbix_screen:
screens:
- screen_name: ExampleScreen1
host_group: Example group1
state: present
graph_names:
- Example graph1
- Example graph2
graph_width: 200
graph_height: 100
- screen_name: ExampleScreen2
host_group: Example group2
state: present
graph_names:
- Example graph1
- Example graph2
graph_width: 200
graph_height: 100
# Limit the Zabbix screen creations to one host since Zabbix can return an error when doing concurrent updates
- name: Create a new screen or update an existing screen's items
community.zabbix.zabbix_screen:
state: present
screens:
- screen_name: ExampleScreen
host_group: Example group
state: present
graph_names:
- Example graph1
- Example graph2
graph_width: 200
graph_height: 100
when: inventory_hostname==groups['group_name'][0]
# Create/update using multiple hosts_groups. Hosts NOT present in all listed host_groups will be skipped.
- name: Create new screen or update the existing screen's items for hosts in both given groups
community.zabbix.zabbix_screen:
screens:
- screen_name: ExampleScreen1
host_group:
- Example group1
- Example group2
state: present
graph_names:
- Example graph1
- Example graph2
graph_width: 200
graph_height: 100
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.wrappers import ScreenItem
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Screen(ZabbixBase):
# get list of group ids by list of group names
def get_host_group_ids(self, group_names):
if not group_names:
self._module.fail_json(msg="group_name is required")
hostGroup_list = self._zapi.hostgroup.get({'output': 'extend', 'filter': {'name': group_names}})
if not hostGroup_list:
self._module.fail_json(msg="Host group not found: {0}".format(group_names))
else:
hostGroup_ids = [g['groupid'] for g in hostGroup_list]
return hostGroup_ids
# get monitored host_ids by host_group_ids
# (the hosts belonging to all given groups)
def get_host_ids_by_group_ids(self, group_ids, sort):
host_list = self._zapi.host.get({'output': 'extend', 'selectGroups': 'groupid', 'groupids': group_ids, 'monitored_hosts': 1})
if not host_list:
self._module.fail_json(msg="No hosts in the all group(s) with ids {0}".format(group_ids))
else:
if sort:
host_list = sorted(host_list, key=lambda name: name['name'])
host_ids = []
for host in host_list:
host_group_ids = [g['groupid'] for g in host['groups']]
# Check if all search group ids are in hosts group ids
if set(group_ids).issubset(host_group_ids):
host_id = host['hostid']
host_ids.append(host_id)
return host_ids
# get screen
def get_screen_id(self, screen_name):
if screen_name == "":
self._module.fail_json(msg="screen_name is required")
try:
screen_id_list = self._zapi.screen.get({'output': 'extend', 'search': {"name": screen_name}})
if len(screen_id_list) >= 1:
screen_id = screen_id_list[0]['screenid']
return screen_id
return None
except Exception as e:
self._module.fail_json(msg="Failed to get screen %s from Zabbix: %s" % (screen_name, e))
# create screen
def create_screen(self, screen_name, h_size, v_size):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
screen = self._zapi.screen.create({'name': screen_name, 'hsize': h_size, 'vsize': v_size})
return screen['screenids'][0]
except Exception as e:
self._module.fail_json(msg="Failed to create screen %s: %s" % (screen_name, e))
# update screen
def update_screen(self, screen_id, screen_name, h_size, v_size):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.screen.update({'screenid': screen_id, 'hsize': h_size, 'vsize': v_size})
except Exception as e:
self._module.fail_json(msg="Failed to update screen %s: %s" % (screen_name, e))
# delete screen
def delete_screen(self, screen_id, screen_name):
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.screen.delete([screen_id])
except Exception as e:
self._module.fail_json(msg="Failed to delete screen %s: %s" % (screen_name, e))
# get graph ids
def get_graph_ids(self, hosts, graph_name_list):
graph_id_lists = []
vsize = 1
for host in hosts:
graph_id_list = self.get_graphs_by_host_id(graph_name_list, host)
size = len(graph_id_list)
if size > 0:
graph_id_lists.extend(graph_id_list)
if vsize < size:
vsize = size
return graph_id_lists, vsize
# getGraphs
def get_graphs_by_host_id(self, graph_name_list, host_id):
graph_ids = []
for graph_name in graph_name_list:
graphs_list = self._zapi.graph.get({'output': 'extend', 'search': {'name': graph_name}, 'hostids': host_id})
graph_id_list = []
if len(graphs_list) > 0:
for graph in graphs_list:
graph_id = graph['graphid']
graph_id_list.append(graph_id)
if len(graph_id_list) > 0:
graph_ids.extend(graph_id_list)
return graph_ids
# get screen items
def get_screen_items(self, screen_id):
screen_item_list = self._zapi.screenitem.get({'output': 'extend', 'screenids': screen_id})
return screen_item_list
# delete screen items
def delete_screen_items(self, screen_id, screen_item_id_list):
if len(screen_item_id_list) == 0:
return True
screen_item_list = self.get_screen_items(screen_id)
if len(screen_item_list) > 0:
if self._module.check_mode:
self._module.exit_json(changed=True)
ScreenItem.delete(self, screen_item_id_list)
return True
return False
# get screen's hsize and vsize
def get_hsize_vsize(self, hosts, v_size, graphs_in_row):
h_size = len(hosts)
# when there is only one host, put all graphs in a row
if h_size == 1:
if v_size <= graphs_in_row:
h_size = v_size
else:
h_size = graphs_in_row
v_size = (v_size - 1) // h_size + 1
# when len(hosts) is more then graphs_in_row
elif len(hosts) > graphs_in_row:
h_size = graphs_in_row
v_size = (len(hosts) // graphs_in_row + 1) * v_size
return h_size, v_size
# create screen_items
def create_screen_items(self, screen_id, hosts, graph_name_list, width, height, h_size, graphs_in_row):
if len(hosts) < 4:
if width is None or width < 0:
width = 500
else:
if width is None or width < 0:
width = 200
if height is None or height < 0:
height = 100
# when there're only one host, only one row is not good.
if len(hosts) == 1:
graph_id_list = self.get_graphs_by_host_id(graph_name_list, hosts[0])
for i, graph_id in enumerate(graph_id_list):
if graph_id is not None:
ScreenItem.create(self, ignoreExists=True, data={'screenid': screen_id, 'resourcetype': 0, 'resourceid': graph_id,
'width': width, 'height': height,
'x': i % h_size, 'y': i // h_size, 'colspan': 1, 'rowspan': 1,
'elements': 0, 'valign': 0, 'halign': 0,
'style': 0, 'dynamic': 0, 'sort_triggers': 0})
else:
for i, host in enumerate(hosts):
graph_id_list = self.get_graphs_by_host_id(graph_name_list, host)
for j, graph_id in enumerate(graph_id_list):
if graph_id is not None:
ScreenItem.create(self, ignoreExists=True, data={'screenid': screen_id, 'resourcetype': 0, 'resourceid': graph_id,
'width': width, 'height': height,
'x': i % graphs_in_row, 'y': len(graph_id_list) * (i // graphs_in_row) + j,
'colspan': 1, 'rowspan': 1,
'elements': 0, 'valign': 0, 'halign': 0,
'style': 0, 'dynamic': 0, 'sort_triggers': 0})
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
screens=dict(
type='list',
elements='dict',
required=True,
options=dict(
screen_name=dict(type='str', required=True),
host_group=dict(type='list', aliases=['host_groups'], elements='str'),
state=dict(type='str', default='present', choices=['absent', 'present']),
graph_names=dict(type='list', elements='str'),
graph_width=dict(type='int', default=None),
graph_height=dict(type='int', default=None),
graphs_in_row=dict(type='int', default=3),
sort=dict(default=False, type='bool'),
),
required_if=[
['state', 'present', ['host_group']]
]
)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
screens = module.params['screens']
screen = Screen(module)
if LooseVersion(screen._zbx_api_version) >= LooseVersion('5.4'):
module.fail_json(msg="Zabbix 5.4 removed the Screens feature see (%s)." % (
"https://www.zabbix.com/documentation/current/en/manual/api/changes_5.2_-_5.4"
))
created_screens = []
changed_screens = []
deleted_screens = []
for zabbix_screen in screens:
screen_name = zabbix_screen['screen_name']
screen_id = screen.get_screen_id(screen_name)
state = zabbix_screen['state']
sort = zabbix_screen['sort']
if state == "absent":
if screen_id:
screen_item_list = screen.get_screen_items(screen_id)
screen_item_id_list = []
for screen_item in screen_item_list:
screen_item_id = screen_item['screenitemid']
screen_item_id_list.append(screen_item_id)
screen.delete_screen_items(screen_id, screen_item_id_list)
screen.delete_screen(screen_id, screen_name)
deleted_screens.append(screen_name)
else:
host_group = zabbix_screen['host_group']
graph_names = zabbix_screen['graph_names']
graphs_in_row = zabbix_screen['graphs_in_row']
graph_width = zabbix_screen['graph_width']
graph_height = zabbix_screen['graph_height']
host_group_ids = screen.get_host_group_ids(host_group)
hosts = screen.get_host_ids_by_group_ids(host_group_ids, sort)
if not hosts:
module.fail_json(msg="No hosts found belongin to all given groups: %s" % host_group)
screen_item_id_list = []
resource_id_list = []
graph_ids, v_size = screen.get_graph_ids(hosts, graph_names)
h_size, v_size = screen.get_hsize_vsize(hosts, v_size, graphs_in_row)
if not screen_id:
# create screen
screen_id = screen.create_screen(screen_name, h_size, v_size)
screen.create_screen_items(screen_id, hosts, graph_names, graph_width, graph_height, h_size, graphs_in_row)
created_screens.append(screen_name)
else:
screen_item_list = screen.get_screen_items(screen_id)
for screen_item in screen_item_list:
screen_item_id = screen_item['screenitemid']
resource_id = screen_item['resourceid']
screen_item_id_list.append(screen_item_id)
resource_id_list.append(resource_id)
# when the screen items changed, then update
if graph_ids != resource_id_list:
deleted = screen.delete_screen_items(screen_id, screen_item_id_list)
if deleted:
screen.update_screen(screen_id, screen_name, h_size, v_size)
screen.create_screen_items(screen_id, hosts, graph_names, graph_width, graph_height, h_size, graphs_in_row)
changed_screens.append(screen_name)
if created_screens and changed_screens:
module.exit_json(changed=True, result="Successfully created screen(s): %s, and updated screen(s): %s" % (",".join(created_screens),
",".join(changed_screens)))
elif created_screens:
module.exit_json(changed=True, result="Successfully created screen(s): %s" % ",".join(created_screens))
elif changed_screens:
module.exit_json(changed=True, result="Successfully updated screen(s): %s" % ",".join(changed_screens))
elif deleted_screens:
module.exit_json(changed=True, result="Successfully deleted screen(s): %s" % ",".join(deleted_screens))
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,465 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, BGmot
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: zabbix_script
short_description: Create/update/delete Zabbix scripts
version_added: 1.7.0
author:
- Evgeny Yurchenko (@BGmot)
description:
- This module allows you to create, update and delete scripts.
requirements:
- "python >= 2.6"
options:
name:
description:
- Name of the script.
required: true
type: str
script_type:
description:
- Script type.
- Types C(ssh), C(telnet) and C(webhook) works only with Zabbix >= 5.4.
type: str
required: true
choices: ['script', 'ipmi', 'ssh', 'telnet', 'webhook']
command:
description:
- Command to run.
type: str
required: true
scope:
description:
- Script scope.
- Works only with Zabbix >= 5.4. For lower versions is silently ignored which is equivalent of C(manual_host_action).
type: str
required: false
choices: ['action_operation', 'manual_host_action', 'manual_event_action']
default: 'action_operation'
execute_on:
description:
- Where to run the script.
- Used if type is C(script).
type: str
required: false
choices: ['zabbix_agent', 'zabbix_server', 'zabbix_server_proxy']
default: 'zabbix_server_proxy'
menu_path:
description:
- Folders separated by slash that form a menu like navigation in frontend when clicked on host or event.
- Used if scope is C(manual_host_action) or C(manual_event_action).
- Works only with Zabbix >= 5.4. For lower versions is silently ignored. Prepend menu path to name instead.
type: str
required: false
authtype:
description:
- Authentication method used for SSH script type.
- Used if type is C(ssh).
type: str
required: false
choices: ['password', 'public_key']
username:
description:
- User name used for authentication.
- Used if type is C(ssh) or C(telnet)
type: str
required: false
password:
description:
- Password used for SSH scripts with password authentication and Telnet scripts.
- Used if type is C(ssh) and authtype is C(password) or type is C(telnet).
type: str
required: false
publickey:
description:
- Name of the public key file used for SSH scripts with public key authentication.
- Used if type is C(ssh) and authtype is C(public_key).
type: str
required: false
privatekey:
description:
- Name of the private key file used for SSH scripts with public key authentication.
- Used if type is C(ssh) and authtype is C(public_key).
type: str
required: false
port:
description:
- Port number used for SSH and Telnet scripts.
- Used if type is C(ssh) or C(telnet).
type: str
required: false
host_group:
description:
- host group name that the script can be run on. If set to 'all', the script will be available on all host groups.
type: str
required: false
default: 'all'
user_group:
description:
- user group name that will be allowed to run the script. If set to 'all', the script will be available for all user groups.
- Used if scope is C(manual_host_action) or C(manual_event_action).
type: str
required: false
default: 'all'
host_access:
description:
- Host permissions needed to run the script.
- Used if scope is C(manual_host_action) or C(manual_event_action).
type: str
required: false
choices: ['read', 'write']
default: 'read'
confirmation:
description:
- Confirmation pop up text. The pop up will appear when trying to run the script from the Zabbix frontend.
- Used if scope is C(manual_host_action) or C(manual_event_action).
type: str
required: false
script_timeout:
description:
- Webhook script execution timeout in seconds. Time suffixes are supported, e.g. 30s, 1m.
- Required if type is C(webhook).
- 'Possible values: 1-60s.'
type: str
default: '30s'
required: false
parameters:
description:
- Array of webhook input parameters.
- Used if type is C(webhook).
type: list
elements: dict
suboptions:
name:
description:
- Parameter name.
type: str
required: true
value:
description:
- Parameter value. Supports macros.
type: str
required: false
default: ''
description:
description:
- Description of the script.
type: str
required: false
state:
description:
- State of the script.
type: str
required: false
choices: ['present', 'absent']
default: 'present'
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: test - Create new action operation script to execute webhook
zabbix_script:
name: Test action operation script
scope: action_operation
script_type: webhook
command: 'return 0'
description: "Test action operation script"
state: present
'''
RETURN = '''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Script(ZabbixBase):
def get_script_ids(self, script_name):
script_ids = []
scripts = self._zapi.script.get({'filter': {'name': script_name}})
for script in scripts:
script_ids.append(script['scriptid'])
return script_ids
def create_script(self, name, script_type, command, scope, execute_on, menu_path, authtype, username, password,
publickey, privatekey, port, host_group, user_group, host_access, confirmation, script_timeout, parameters, description):
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.script.create(self.generate_script_config(name, script_type, command, scope, execute_on, menu_path,
authtype, username, password, publickey, privatekey, port, host_group, user_group, host_access, confirmation,
script_timeout, parameters, description))
def delete_script(self, script_ids):
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.script.delete(script_ids)
def generate_script_config(self, name, script_type, command, scope, execute_on, menu_path, authtype, username, password,
publickey, privatekey, port, host_group, user_group, host_access, confirmation, script_timeout, parameters, description):
if host_group == 'all':
groupid = '0'
else:
groups = self._zapi.hostgroup.get({'filter': {'name': host_group}})
if not groups:
self._module.fail_json(changed=False, msg='Host group "%s" not found' % host_group)
groupid = groups[0]['groupid']
if user_group == 'all':
usrgrpid = '0'
else:
user_groups = self._zapi.usergroup.get({'filter': {'name': user_group}})
if not user_groups:
self._module.fail_json(changed=False, msg='User group "%s" not found' % user_group)
usrgrpid = user_groups[0]['usrgrpid']
request = {
'name': name,
'type': str(zabbix_utils.helper_to_numeric_value([
'script',
'ipmi',
'ssh',
'telnet',
'',
'webhook'], script_type)),
'command': command,
'scope': str(zabbix_utils.helper_to_numeric_value([
'',
'action_operation',
'manual_host_action',
'',
'manual_event_action'], scope)),
'groupid': groupid
}
if description is not None:
request['description'] = description
if script_type == 'script':
if execute_on is None:
execute_on = 'zabbix_server_proxy'
request['execute_on'] = str(zabbix_utils.helper_to_numeric_value([
'zabbix_agent',
'zabbix_server',
'zabbix_server_proxy'], execute_on))
if scope in ['manual_host_action', 'manual_event_action']:
if menu_path is None:
request['menu_path'] = ''
else:
request['menu_path'] = menu_path
request['usrgrpid'] = usrgrpid
request['host_access'] = str(zabbix_utils.helper_to_numeric_value([
'',
'',
'read',
'write'], host_access))
if confirmation is None:
request['confirmation'] = ''
else:
request['confirmation'] = confirmation
if script_type == 'ssh':
if authtype is None:
self._module.fail_json(changed=False, msg='authtype must be provided for ssh script type')
request['authtype'] = str(zabbix_utils.helper_to_numeric_value([
'password',
'public_key'], authtype))
if authtype == 'public_key':
if publickey is None or privatekey is None:
self._module.fail_json(changed=False, msg='publickey and privatekey must be provided for ssh script type with publickey authtype')
request['publickey'] = publickey
request['privatekey'] = privatekey
if script_type in ['ssh', 'telnet']:
if username is None:
self._module.fail_json(changed=False, msg='username must be provided for "ssh" and "telnet" script types')
request['username'] = username
if (script_type == 'ssh' and authtype == 'password') or script_type == 'telnet':
if password is None:
self._module.fail_json(changed=False, msg='password must be provided for telnet script type or ssh script type with password autheype')
request['password'] = password
if port is not None:
request['port'] = port
if script_type == 'webhook':
request['timeout'] = script_timeout
if parameters:
request['parameters'] = parameters
if LooseVersion(self._zbx_api_version) < LooseVersion('5.4'):
if script_type not in ['script', 'ipmi']:
self._module.fail_json(changed=False, msg='script_type must be script or ipmi in version <5.4')
if 'scope' in request:
del request['scope']
if 'menu_path' in request:
del request['menu_path']
return request
def update_script(self, script_id, name, script_type, command, scope, execute_on, menu_path, authtype, username, password,
publickey, privatekey, port, host_group, user_group, host_access, confirmation, script_timeout, parameters, description):
generated_config = self.generate_script_config(name, script_type, command, scope, execute_on, menu_path, authtype, username,
password, publickey, privatekey, port, host_group, user_group, host_access,
confirmation, script_timeout, parameters, description)
live_config = self._zapi.script.get({'filter': {'name': name}})[0]
change_parameters = {}
difference = zabbix_utils.helper_cleanup_data(zabbix_utils.helper_compare_dictionaries(generated_config, live_config, change_parameters))
if not difference:
self._module.exit_json(changed=False, msg="Script %s up to date" % name)
if self._module.check_mode:
self._module.exit_json(changed=True)
generated_config['scriptid'] = live_config['scriptid']
self._zapi.script.update(generated_config)
self._module.exit_json(changed=True, msg="Script %s updated" % name)
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
script_type=dict(
type='str',
required=True,
choices=['script', 'ipmi', 'ssh', 'telnet', 'webhook']),
command=dict(type='str', required=True),
scope=dict(
type='str',
required=False,
choices=['action_operation', 'manual_host_action', 'manual_event_action'],
default='action_operation'),
execute_on=dict(
type='str',
required=False,
choices=['zabbix_agent', 'zabbix_server', 'zabbix_server_proxy'],
default='zabbix_server_proxy'),
menu_path=dict(type='str', required=False),
authtype=dict(
type='str',
required=False,
choices=['password', 'public_key']),
username=dict(type='str', required=False),
password=dict(type='str', required=False, no_log=True),
publickey=dict(type='str', required=False),
privatekey=dict(type='str', required=False, no_log=True),
port=dict(type='str', required=False),
host_group=dict(type='str', required=False, default='all'),
user_group=dict(type='str', required=False, default='all'),
host_access=dict(
type='str',
required=False,
choices=['read', 'write'],
default='read'),
confirmation=dict(type='str', required=False),
script_timeout=dict(type='str', default='30s', required=False),
parameters=dict(
type='list',
elements='dict',
options=dict(
name=dict(type='str', required=True),
value=dict(type='str', required=False, default='')
)
),
description=dict(type='str', required=False),
state=dict(
type='str',
required=False,
default='present',
choices=['present', 'absent'])
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
name = module.params['name']
script_type = module.params['script_type']
command = module.params['command']
scope = module.params['scope']
execute_on = module.params['execute_on']
menu_path = module.params['menu_path']
authtype = module.params['authtype']
username = module.params['username']
password = module.params['password']
publickey = module.params['publickey']
privatekey = module.params['privatekey']
port = module.params['port']
host_group = module.params['host_group']
user_group = module.params['user_group']
host_access = module.params['host_access']
confirmation = module.params['confirmation']
script_timeout = module.params['script_timeout']
parameters = module.params['parameters']
description = module.params['description']
state = module.params['state']
script = Script(module)
script_ids = script.get_script_ids(name)
# Delete script
if state == "absent":
if not script_ids:
module.exit_json(changed=False, msg="Script not found, no change: %s" % name)
script.delete_script(script_ids)
module.exit_json(changed=True, result="Successfully deleted script(s) %s" % name)
elif state == "present":
if not script_ids:
script.create_script(name, script_type, command, scope, execute_on, menu_path, authtype, username, password,
publickey, privatekey, port, host_group, user_group, host_access, confirmation, script_timeout, parameters, description)
module.exit_json(changed=True, msg="Script %s created" % name)
else:
script.update_script(script_ids[0], name, script_type, command, scope, execute_on, menu_path, authtype, username,
password, publickey, privatekey, port, host_group, user_group, host_access, confirmation,
script_timeout, parameters, description)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,706 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, OVH SAS
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: zabbix_service
short_description: Create/update/delete Zabbix service
description:
- Create/update/delete Zabbix service.
author:
- "Emmanuel Riviere (@emriver)"
- "Evgeny Yurchenko (@BGmot)"
requirements:
- "python >= 2.7"
options:
name:
description:
- Name of Zabbix service
required: true
type: str
parent:
description:
- Name of Zabbix service parent
- With >= Zabbix 6.0 this field is removed from the API and is dropped silently by module.
required: false
type: str
sla:
description:
- Sla value (i.e 99.99), goodsla in Zabbix API
- With >= Zabbix 6.0 this field is removed from the API and is dropped silently by module.
required: false
type: float
calculate_sla:
description:
- If yes, calculate the SLA value for this service, showsla in Zabbix API
- With >= Zabbix 6.0 this field is removed from the API and is dropped silently by module.
required: false
default: false
type: bool
algorithm:
description:
- Algorithm used to calculate the sla with < Zabbix 6.0
- ' - C(no), sla is not calculated'
- ' - C(one_child), problem if at least one child has a problem'
- ' - C(all_children), problem if all children have problems'
- Status calculation rule. Only applicable if child services exists with >= Zabbix 6.0
- ' - C(status_to_ok), set status to OK with'
- ' - C(most_crit_if_all_children), most critical if all children have problems'
- ' - C(most_crit_of_child_serv), most critical of child services with'
required: false
type: str
choices: ["no", "one_child", "all_children", "status_to_ok", "most_crit_if_all_children", "most_crit_of_child_serv"]
default: one_child
trigger_name:
description:
- Name of trigger linked to the service.
- With >= Zabbix 6.0 this field is removed from the API and is dropped silently by module.
required: false
type: str
trigger_host:
description:
- Name of host linked to the service.
- With >= Zabbix 6.0 this field is removed from the API and is dropped silently by module.
required: false
type: str
state:
description:
- 'State: present - create/update service; absent - delete service.'
required: false
choices: [present, absent]
default: "present"
type: str
sortorder:
description:
- Position of the service used for sorting.
required: true
type: str
weight:
description:
- Service weight.
- New field with >= Zabbix 6.0.
required: false
default: '0'
type: str
description:
description:
- Description of the service.
- New field with >= Zabbix 6.0.
required: false
type: str
tags:
description:
- Service tags to be created for the service.
- New field with >= Zabbix 6.0.
required: false
type: list
elements: dict
suboptions:
tag:
description:
- Service tag name.
required: true
type: str
value:
description:
- Service tag value.
required: false
type: str
problem_tags:
description:
- Problem tags to be created for the service.
- New field with >= Zabbix 6.0.
required: false
type: list
elements: dict
suboptions:
tag:
description:
- Problem tag name.
required: true
type: str
operator:
description:
- Mapping condition operator.
- C(equals)
- C(like)
choices: ['equals', 'like']
required: false
default: "equals"
type: str
value:
description:
- Problem tag value.
required: false
default: ""
type: str
parents:
description:
- Parent services to be linked to the service.
- New field with >= Zabbix 6.0.
required: false
type: list
elements: str
children:
description:
- Child services to be linked to the service.
- New field with >= Zabbix 6.0.
required: false
type: list
elements: str
propagation_rule:
description:
- Status propagation value. Must be set together with propagation_rule.
- New field with >= Zabbix 6.0.
- C(as_is) propagate service status as is - without any changes
- C(increase) increase the propagated status by a given propagation_value (by 1 to 5 severities)
- C(decrease) decrease the propagated status by a given propagation_value (by 1 to 5 severities)
- C(ignore) ignore this service - the status is not propagated to the parent service at all
- C(fixed) set fixed service status using a given propagation_value
- Required with C(propagation_value)
required: false
type: str
default: as_is
propagation_value:
description:
- Status propagation value. Must be set together with propagation_rule.
- New field with >= Zabbix 6.0.
- 'Possible values when I(propagation_rule=as_is or ignore):'
- ' - C(not_classified)'
- 'Possible values when I(propagation_rule=increase or decrease):'
- ' - C(information)'
- ' - C(warning)'
- ' - C(average)'
- ' - C(high)'
- ' - C(disaster)'
- 'Possible values when I(propagation_rule=fixed):'
- ' - C(ok)'
- ' - C(not_classified)'
- ' - C(information)'
- ' - C(warning)'
- ' - C(average)'
- ' - C(high)'
- ' - C(disaster)'
- Required with C(propagation_rule)
required: false
type: str
status_rules:
description:
- Status rules for the service.
- New field with >= Zabbix 6.0.
required: false
type: list
elements: dict
suboptions:
type:
description:
- Condition for setting (New status) status.
- C(at_least_n_child_services_have_status_or_above) if at least (N) child services have (Status) status or above
- C(at_least_npct_child_services_have_status_or_above) if at least (N%) of child services have (Status) status or above
- C(less_than_n_child_services_have_status_or_below) if less than (N) child services have (Status) status or below
- C(less_than_npct_child_services_have_status_or_below) if less than (N%) of child services have (Status) status or below
- C(weight_child_services_with_status_or_above_at_least_w) if weight of child services with (Status) status or above is at least (W)
- C(weight_child_services_with_status_or_above_at_least_npct) if weight of child services with (Status) status or above is at least (N%)
- C(weight_child_services_with_status_or_below_less_w) if weight of child services with (Status) status or below is less than (W)
- C(weight_child_services_with_status_or_below_less_npct) if weight of child services with (Status) status or below is less than (N%)
required: true
type: str
limit_value:
description:
- 'Limit value: N, N% or W'
- 'Possible values: 1-100000 for N and W, 1-100 for N%'
required: true
type: int
limit_status:
description:
- Limit status.
- C(ok) OK
- C(not_classified) Not classified
- C(information) Information
- C(warning) Warning
- C(average) Average
- C(high) High
- C(disaster) Disaster
required: true
type: str
new_status:
description:
- New status value.
- C(not_classified) Not classified
- C(information) Information
- C(warning) Warning
- C(average) Average
- C(high) High
- C(disaster) Disaster
required: true
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
---
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Creates a new Zabbix service with Zabbix < 6.0
- name: Manage services
community.zabbix.zabbix_service:
name: apache2 service
sla: 99.99
calculate_sla: yes
algorithm: one_child
trigger_name: apache2 service status
trigger_host: webserver01
state: present
# Creates a new Zabbix service with Zabbix >= 6.0
- name: Create Zabbix service monitoring Apache2 in DCs in Toronto area
community.zabbix.zabbix_service:
name: 'apache2 service Toronto'
description: Apache2 services in Toronto area
sortorder: 0
propagation_rule: increase
propagation_value: warning
weight: 1
state: present
tags:
- tag: zabbix_service
value: apache2
- tag: area
value: Toronto
problem_tags:
- tag: service_name
value: httpd
- tag: area
operator: like
value: toronto
status_rules:
- type: at_least_n_child_services_have_status_or_above
limit_value: 4242
limit_status: ok
new_status: average
- name: Create Zabbix service monitoring all Apache2 services
community.zabbix.zabbix_service:
name: apache2 service
description: Apache2 services
tags:
- tag: zabbix_service
value: apache2
- tag: area
value: global
children:
- 'apache2 service Toronto'
'''
RETURN = '''
---
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
class Service(ZabbixBase):
def get_service_ids(self, service_name):
service_ids = []
services = self._zapi.service.get({'filter': {'name': service_name}})
for service in services:
service_ids.append(service['serviceid'])
return service_ids
def delete_service(self, service_ids):
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.service.delete(service_ids)
def dump_services(self, service_ids):
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
services = self._zapi.service.get({'output': 'extend', 'filter': {'serviceid': service_ids}, 'selectParent': '1'})
else:
services = self._zapi.service.get({'output': 'extend', 'filter': {'serviceid': service_ids}, 'selectParents': 'extend',
'selectTags': 'extend', 'selectProblemTags': 'extend', 'selectChildren': 'extend',
'selectStatusRules': 'extend'})
return services
def generate_service_config(self, name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight,
algorithm, description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules):
algorithms = {'no': '0', 'one_child': '1', 'all_children': '2',
'status_to_ok': '0', 'most_crit_if_all_children': '1', 'most_crit_of_child_serv': '2'}
algorithm = algorithms[algorithm]
if LooseVersion(self._zbx_api_version) < LooseVersion('6.0'):
if calculate_sla:
calculate_sla = 1
else:
calculate_sla = 0
else:
sla = 0 # Parameter does not exist in >= 6.0 but we needed for format() function constructing request
# Zabbix api return when no trigger
trigger_id = 0
if trigger_host and trigger_name:
# Retrieving the host to get the trigger
hosts = self._zapi.host.get({'filter': {'host': trigger_host}})
if not hosts:
self._module.fail_json(msg="Target host %s not found" % trigger_host)
host_id = hosts[0]['hostid']
triggers = self._zapi.trigger.get({'filter': {'description': trigger_name}, 'hostids': [host_id]})
if not triggers:
self._module.fail_json(msg="Trigger %s not found on host %s" % (trigger_name, trigger_host))
trigger_id = triggers[0]['triggerid']
request = {
'name': name,
'algorithm': algorithm,
'showsla': str(calculate_sla),
'sortorder': sortorder,
'goodsla': format(sla, '.4f'), # Sla has 4 decimals
'triggerid': str(trigger_id)
}
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.0'):
request.pop('showsla')
request.pop('triggerid')
request.pop('goodsla')
request['description'] = description
request['weight'] = weight
if tags:
request['tags'] = tags
else:
request['tags'] = []
request['problem_tags'] = []
if problem_tags:
p_operators = {'equals': '0', 'like': '2'}
for p_tag in problem_tags:
pt = {'tag': p_tag['tag'], 'operator': '0', 'value': ''}
if 'operator' in p_tag:
pt['operator'] = p_operators[p_tag['operator']]
if 'value' in p_tag:
pt['value'] = p_tag['value']
request['problem_tags'].append(pt)
if parents:
p_service_ids = []
p_services = self._zapi.service.get({'filter': {'name': parents}})
for p_service in p_services:
p_service_ids.append({'serviceid': p_service['serviceid']})
request['parents'] = p_service_ids
else:
request['parents'] = []
if children:
c_service_ids = []
c_services = self._zapi.service.get({'filter': {'name': children}})
for c_service in c_services:
c_service_ids.append({'serviceid': c_service['serviceid']})
request['children'] = c_service_ids
else:
request['children'] = []
request['status_rules'] = []
if status_rules:
for s_rule in status_rules:
status_rule = {}
if 'type' in s_rule:
sr_type_map = {'at_least_n_child_services_have_status_or_above': '0',
'at_least_npct_child_services_have_status_or_above': '1',
'less_than_n_child_services_have_status_or_below': '2',
'less_than_npct_child_services_have_status_or_below': '3',
'weight_child_services_with_status_or_above_at_least_w': '4',
'weight_child_services_with_status_or_above_at_least_npct': '5',
'weight_child_services_with_status_or_below_less_w': '6',
'weight_child_services_with_status_or_below_less_npct': '7'}
if s_rule['type'] not in sr_type_map:
self._module.fail_json(msg="Wrong value for 'type' parameter in status rule.")
status_rule['type'] = sr_type_map[s_rule['type']]
else:
self._module.fail_json(msg="'type' is mandatory paremeter for status rule.")
if 'limit_value' in s_rule:
lv = s_rule['limit_value']
if status_rule['type'] in ['0', '2', '4', '6']:
if int(lv) < 1 or int(lv) > 100000:
self._module.fail_json(msg="'limit_value' for N and W must be between 1 and 100000 but provided %s" % lv)
else:
if int(lv) < 1 or int(lv) > 100:
self._module.fail_json(msg="'limit_value' for N%% must be between 1 and 100 but provided %s" % lv)
status_rule['limit_value'] = str(lv)
else:
self._module.fail_json(msg="'limit_value' is mandatory paremeter for status rule.")
if 'limit_status' in s_rule:
sr_ls_map = {'ok': '-1', 'not_classified': '0', 'information': '1', 'warning': '2',
'average': '3', 'high': '4', 'disaster': 5}
if s_rule['limit_status'] not in sr_ls_map:
self._module.fail_json(msg="Wrong value for 'limit_status' parameter in status rule.")
status_rule['limit_status'] = sr_ls_map[s_rule['limit_status']]
else:
self._module.fail_json(msg="'limit_status' is mandatory paremeter for status rule.")
if 'new_status' in s_rule:
sr_ns_map = {'not_classified': '0', 'information': '1', 'warning': '2',
'average': '3', 'high': '4', 'disaster': '5'}
if s_rule['new_status'] not in sr_ns_map:
self._module.fail_json(msg="Wrong value for 'new_status' parameter in status rule.")
status_rule['new_status'] = sr_ns_map[s_rule['new_status']]
else:
self._module.fail_json(msg="'new_status' is mandatory paremeter for status rule.")
request['status_rules'].append(status_rule)
request['propagation_rule'] = '0'
if propagation_rule:
if propagation_value is None:
self._module.fail_json(msg="If 'propagation_rule' is provided then 'propagation_value' must be provided too.")
pr_map = {'as_is': '0', 'increase': '1', 'decrease': '2', 'ignore': '3', 'fixed': '4'}
if propagation_rule not in pr_map:
self._module.fail_json(msg="Wrong value for 'propagation_rule' parameter.")
else:
request['propagation_rule'] = pr_map[propagation_rule]
request['propagation_value'] = '0'
if propagation_value:
if propagation_rule is None:
self._module.fail_json(msg="If 'propagation_value' is provided then 'propagation_rule' must be provided too.")
pv_map = {'ok': '-1', 'not_classified': '0', 'information': '1', 'warning': '2',
'average': '3', 'high': '4', 'disaster': '5'}
if propagation_value not in pv_map:
self._module.fail_json(msg="Wrong value for 'propagation_value' parameter.")
else:
request['propagation_value'] = pv_map[propagation_value]
else:
if parent:
parent_ids = self.get_service_ids(parent)
if not parent_ids:
self._module.fail_json(msg="Parent %s not found" % parent)
request['parentid'] = parent_ids[0]
return request
def create_service(self, name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight, algorithm,
description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules):
if self._module.check_mode:
self._module.exit_json(changed=True)
self._zapi.service.create(self.generate_service_config(name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight,
algorithm, description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules))
def update_service(self, service_id, name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight, algorithm,
description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules):
generated_config = self.generate_service_config(name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight, algorithm,
description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules)
live_config = self.dump_services(service_id)[0]
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.0'):
if len(live_config['parents']) > 0:
# Need to rewrite parents list to only service ids
new_parents = []
for parent in live_config['parents']:
new_parents.append({'serviceid': parent['serviceid']})
live_config['parents'] = new_parents
if len(live_config['children']) > 0:
# Need to rewrite children list to only service ids
new_children = []
for child in live_config['children']:
new_children.append({'serviceid': child['serviceid']})
live_config['children'] = new_children
else:
if 'goodsla' in live_config:
live_config['goodsla'] = format(float(live_config['goodsla']), '.4f')
if 'parentid' in generated_config:
if 'serviceid' in live_config['parent']:
live_config['parentid'] = live_config['parent']['serviceid']
change_parameters = {}
difference = zabbix_utils.helper_cleanup_data(zabbix_utils.helper_compare_dictionaries(generated_config, live_config, change_parameters))
if difference == {}:
self._module.exit_json(changed=False, msg="Service %s up to date" % name)
if self._module.check_mode:
self._module.exit_json(changed=True)
generated_config['serviceid'] = service_id
self._zapi.service.update(generated_config)
self._module.exit_json(changed=True, msg="Service %s updated" % name)
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
parent=dict(type='str', required=False),
sla=dict(type='float', required=False),
calculate_sla=dict(type='bool', required=False, default=False),
algorithm=dict(default='one_child', required=False, choices=['no', 'one_child', 'all_children',
'status_to_ok', 'most_crit_if_all_children', 'most_crit_of_child_serv']),
trigger_name=dict(type='str', required=False),
trigger_host=dict(type='str', required=False),
sortorder=dict(type='str', required=True),
weight=dict(default='0', type='str', required=False),
state=dict(default="present", choices=['present', 'absent']),
description=dict(type='str', required=False),
tags=dict(
type='list',
required=False,
elements='dict',
options=dict(
tag=dict(
type='str',
required=True
),
value=dict(
type='str',
required=False
)
)
),
problem_tags=dict(
type='list',
required=False,
elements='dict',
options=dict(
tag=dict(
type='str',
required=True
),
operator=dict(
type='str',
required=False,
choices=[
'equals',
'like'
],
default='equals'
),
value=dict(
type='str',
required=False,
default=''
)
)
),
parents=dict(type='list', required=False, elements='str'),
children=dict(type='list', required=False, elements='str'),
propagation_rule=dict(default='as_is', type='str', required=False),
propagation_value=dict(type='str', required=False),
status_rules=dict(
type='list',
required=False,
elements='dict',
options=dict(
type=dict(
type='str',
required=True
),
limit_value=dict(
type='int',
required=True
),
limit_status=dict(
type='str',
required=True
),
new_status=dict(
type='str',
required=True
)
)
)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
name = module.params['name']
parent = module.params['parent']
sla = module.params['sla']
calculate_sla = module.params['calculate_sla']
algorithm = module.params['algorithm']
trigger_name = module.params['trigger_name']
trigger_host = module.params['trigger_host']
sortorder = module.params['sortorder']
weight = module.params['weight']
state = module.params['state']
description = module.params['description']
tags = module.params['tags']
problem_tags = module.params['problem_tags']
parents = module.params['parents']
children = module.params['children']
propagation_rule = module.params['propagation_rule']
propagation_value = module.params['propagation_value']
status_rules = module.params['status_rules']
# Load service module
service = Service(module)
service_ids = service.get_service_ids(name)
# Delete service
if state == "absent":
if not service_ids:
module.exit_json(changed=False, msg="Service not found, no change: %s" % name)
service.delete_service(service_ids)
module.exit_json(changed=True, result="Successfully deleted service(s) %s" % name)
elif state == "present":
if (trigger_name and not trigger_host) or (trigger_host and not trigger_name):
module.fail_json(msg="Specify either both trigger_host and trigger_name or none to create or update a service")
# Does not exists going to create it
if not service_ids:
service.create_service(name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight, algorithm, description,
tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules)
module.exit_json(changed=True, msg="Service %s created" % name)
# Else we update it if needed
else:
service.update_service(service_ids[0], name, parent, sla, calculate_sla, trigger_name, trigger_host, sortorder, weight,
algorithm, description, tags, problem_tags, parents, children, propagation_rule, propagation_value, status_rules)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,848 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, sookido
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_template
short_description: Create/update/delete/dump Zabbix template
description:
- This module allows you to create, modify, delete and dump Zabbix templates.
- Multiple templates can be created or modified at once if passing JSON or XML to module.
author:
- "sookido (@sookido)"
- "Logan Vig (@logan2211)"
- "Dusan Matejka (@D3DeFi)"
requirements:
- "python >= 2.6"
options:
template_name:
description:
- Name of Zabbix template.
- Required when I(template_json) or I(template_xml) are not used.
- Mutually exclusive with I(template_json) and I(template_xml).
required: false
type: str
template_json:
description:
- JSON dump of templates to import.
- Multiple templates can be imported this way.
- Mutually exclusive with I(template_name) and I(template_xml).
required: false
type: json
template_xml:
description:
- XML dump of templates to import.
- Multiple templates can be imported this way.
- You are advised to pass XML structure matching the structure used by your version of Zabbix server.
- Custom XML structure can be imported as long as it is valid, but may not yield consistent idempotent
results on subsequent runs.
- Mutually exclusive with I(template_name) and I(template_json).
required: false
type: str
template_groups:
description:
- List of template groups to add template to when template is created.
- Replaces the current template groups the template belongs to if the template is already present.
- Required when creating a new template with C(state=present) and I(template_name) is used.
Not required when updating an existing template.
required: false
type: list
elements: str
link_templates:
description:
- List of template names to be linked to the template.
- Templates that are not specified and are linked to the existing template will be only unlinked and not
cleared from the template.
required: false
type: list
elements: str
clear_templates:
description:
- List of template names to be unlinked and cleared from the template.
- This option is ignored if template is being created for the first time.
required: false
type: list
elements: str
macros:
description:
- List of user macros to create for the template.
- Macros that are not specified and are present on the existing template will be replaced.
- See examples on how to pass macros.
required: false
type: list
elements: dict
suboptions:
macro:
description:
- Name of the macro.
- Must be specified in {$NAME} format.
type: str
required: true
value:
description:
- Value of the macro.
type: str
required: true
tags:
description:
- List of tags to assign to the template.
- Works only with >= Zabbix 4.2.
- Providing I(tags=[]) with I(force=yes) will clean all of the tags from the template.
required: false
type: list
elements: dict
suboptions:
tag:
description:
- Name of the template tag.
type: str
required: true
value:
description:
- Value of the template tag.
type: str
default: ''
dump_format:
description:
- Format to use when dumping template with C(state=dump).
- This option is deprecated and will eventually be removed in 2.14.
required: false
choices: [json, xml]
default: "json"
type: str
omit_date:
description:
- Removes the date field for the exported/dumped template
- Requires C(state=dump)
required: false
type: bool
default: false
state:
description:
- Required state of the template.
- On C(state=present) template will be created/imported or updated depending if it is already present.
- On C(state=dump) template content will get dumped into required format specified in I(dump_format).
- On C(state=absent) template will be deleted.
- The C(state=dump) is deprecated and will be removed in 2.14. The M(community.zabbix.zabbix_template_info) module should be used instead.
required: false
choices: [present, absent, dump]
default: "present"
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
notes:
- there where breaking changes in the Zabbix API with version 5.4 onwards (especially UUIDs) which may
require you to export the templates again (see version tag >= 5.4 in the resulting file/data).
'''
EXAMPLES = r'''
---
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create a new Zabbix template linked to groups, macros and templates
community.zabbix.zabbix_template:
template_name: ExampleHost
template_groups:
- Role
- Role2
link_templates:
- Example template1
- Example template2
macros:
- macro: '{$EXAMPLE_MACRO1}'
value: 30000
- macro: '{$EXAMPLE_MACRO2}'
value: 3
- macro: '{$EXAMPLE_MACRO3}'
value: 'Example'
state: present
- name: Unlink and clear templates from the existing Zabbix template
community.zabbix.zabbix_template:
template_name: ExampleHost
clear_templates:
- Example template3
- Example template4
state: present
- name: Import Zabbix templates from JSON
community.zabbix.zabbix_template:
template_json: "{{ lookup('file', 'zabbix_apache2.json') }}"
state: present
- name: Import Zabbix templates from XML
community.zabbix.zabbix_template:
template_xml: "{{ lookup('file', 'zabbix_apache2.xml') }}"
state: present
- name: Import Zabbix template from Ansible dict variable
community.zabbix.zabbix_template:
template_json:
zabbix_export:
version: '3.2'
templates:
- name: Template for Testing
description: 'Testing template import'
template: Test Template
groups:
- name: Templates
applications:
- name: Test Application
state: present
- name: Configure macros on the existing Zabbix template
community.zabbix.zabbix_template:
template_name: Template
macros:
- macro: '{$TEST_MACRO}'
value: 'Example'
state: present
- name: Add tags to the existing Zabbix template
community.zabbix.zabbix_template:
template_name: Template
tags:
- tag: class
value: application
state: present
- name: Delete Zabbix template
community.zabbix.zabbix_template:
template_name: Template
state: absent
- name: Dump Zabbix template as JSON
community.zabbix.zabbix_template:
template_name: Template
omit_date: yes
state: dump
register: template_dump
- name: Dump Zabbix template as XML
community.zabbix.zabbix_template:
template_name: Template
dump_format: xml
omit_date: false
state: dump
register: template_dump
'''
RETURN = r'''
---
template_json:
description: The JSON dump of the template
returned: when state is dump and omit_date is no
type: str
sample: {
"zabbix_export":{
"date":"2017-11-29T16:37:24Z",
"templates":[{
"templates":[],
"description":"",
"httptests":[],
"screens":[],
"applications":[],
"discovery_rules":[],
"groups":[{"name":"Templates"}],
"name":"Test Template",
"items":[],
"macros":[],
"template":"test"
}],
"version":"3.2",
"groups":[{
"name":"Templates"
}]
}
}
template_xml:
description: dump of the template in XML representation
returned: when state is dump, dump_format is xml and omit_date is yes
type: str
sample: |-
<?xml version="1.0" ?>
<zabbix_export>
<version>4.2</version>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<templates>
<template>
<template>test</template>
<name>Test Template</name>
<description/>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<applications/>
<items/>
<discovery_rules/>
<httptests/>
<macros/>
<templates/>
<screens/>
<tags/>
</template>
</templates>
</zabbix_export>
'''
import json
import traceback
import re
import xml.etree.ElementTree as ET
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.six import PY2
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Template(ZabbixBase):
# get group ids by group names
def get_group_ids_by_group_names(self, group_names):
group_ids = []
for group_name in group_names:
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.2'):
group = self._zapi.templategroup.get({'output': ['groupid'], 'filter': {'name': group_name}})
else:
group = self._zapi.hostgroup.get({'output': ['groupid'], 'filter': {'name': group_name}})
if group:
group_ids.append({'groupid': group[0]['groupid']})
else:
self._module.fail_json(msg="Template group not found: %s" % group_name)
return group_ids
def get_template_ids(self, template_list):
template_ids = []
if template_list is None or len(template_list) == 0:
return template_ids
for template in template_list:
template_list = self._zapi.template.get({'output': 'extend', 'filter': {'host': template}})
if len(template_list) < 1:
continue
else:
template_id = template_list[0]['templateid']
template_ids.append({'templateid': template_id})
return template_ids
def add_template(self, template_name, group_ids, link_template_ids, macros, tags):
if self._module.check_mode:
self._module.exit_json(changed=True)
new_template = {'host': template_name, 'groups': group_ids, 'templates': link_template_ids, 'macros': macros, 'tags': tags}
if macros is None:
new_template.update({'macros': []})
if tags is None:
new_template.update({'tags': []})
if link_template_ids is None:
new_template.update({'templates': []})
self._zapi.template.create(new_template)
def check_template_changed(self, template_ids, template_groups, link_templates, clear_templates,
template_macros, template_tags, template_content, template_type):
"""Compares template parameters to already existing values if any are found.
template_json - JSON structures are compared as deep sorted dictionaries,
template_xml - XML structures are compared as strings, but filtered and formatted first,
If none above is used, all the other arguments are compared to their existing counterparts
retrieved from Zabbix API."""
changed = False
# Compare filtered and formatted XMLs strings for any changes. It is expected that provided
# XML has same structure as Zabbix uses (e.g. it was optimally exported via Zabbix GUI or API)
if template_content is not None and template_type == 'xml':
existing_template = self.dump_template(template_ids, template_type='xml')
if self.filter_xml_template(template_content) != self.filter_xml_template(existing_template):
changed = True
return changed
existing_template = self.dump_template(template_ids, template_type='json')
# Compare JSON objects as deep sorted python dictionaries
if template_content is not None and template_type == 'json':
parsed_template_json = self.load_json_template(template_content)
if self.diff_template(parsed_template_json, existing_template):
changed = True
return changed
# If neither template_json or template_xml were used, user provided all parameters via module options
if template_groups is not None:
if LooseVersion(self._zbx_api_version) >= LooseVersion('6.2'):
existing_groups = [g['name'] for g in existing_template['zabbix_export']['template_groups']]
else:
existing_groups = [g['name'] for g in existing_template['zabbix_export']['groups']]
if set(template_groups) != set(existing_groups):
changed = True
if 'templates' not in existing_template['zabbix_export']['templates'][0]:
existing_template['zabbix_export']['templates'][0]['templates'] = []
# Check if any new templates would be linked or any existing would be unlinked
exist_child_templates = [t['name'] for t in existing_template['zabbix_export']['templates'][0]['templates']]
if link_templates is not None:
if set(link_templates) != set(exist_child_templates):
changed = True
else:
if set([]) != set(exist_child_templates):
changed = True
# Mark that there will be changes when at least one existing template will be unlinked
if clear_templates is not None:
for t in clear_templates:
if t in exist_child_templates:
changed = True
break
if 'macros' not in existing_template['zabbix_export']['templates'][0]:
existing_template['zabbix_export']['templates'][0]['macros'] = []
if template_macros is not None:
existing_macros = existing_template['zabbix_export']['templates'][0]['macros']
if template_macros != existing_macros:
changed = True
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.2'):
if 'tags' not in existing_template['zabbix_export']['templates'][0]:
existing_template['zabbix_export']['templates'][0]['tags'] = []
if template_tags is not None:
existing_tags = existing_template['zabbix_export']['templates'][0]['tags']
if template_tags != existing_tags:
changed = True
return changed
def update_template(self, template_ids, group_ids, link_template_ids, clear_template_ids, template_macros, template_tags):
template_changes = {}
if group_ids is not None:
template_changes.update({'groups': group_ids})
if link_template_ids is not None:
template_changes.update({'templates': link_template_ids})
else:
template_changes.update({'templates': []})
if clear_template_ids is not None:
template_changes.update({'templates_clear': clear_template_ids})
if template_macros is not None:
template_changes.update({'macros': template_macros})
else:
template_changes.update({'macros': []})
if template_tags is not None:
template_changes.update({'tags': template_tags})
else:
template_changes.update({'tags': []})
if template_changes:
# If we got here we know that only one template was provided via template_name
template_changes.update(template_ids[0])
self._zapi.template.update(template_changes)
def delete_template(self, templateids):
if self._module.check_mode:
self._module.exit_json(changed=True)
templateids_list = [t.get('templateid') for t in templateids]
self._zapi.template.delete(templateids_list)
def ordered_json(self, obj):
# Deep sort json dicts for comparison
if isinstance(obj, dict):
return sorted((k, self.ordered_json(v)) for k, v in obj.items())
if isinstance(obj, list):
return sorted(self.ordered_json(x) for x in obj)
else:
return obj
def dump_template(self, template_ids, template_type='json', omit_date=False):
template_ids_list = [t.get('templateid') for t in template_ids]
try:
dump = self._zapi.configuration.export({'format': template_type, 'options': {'templates': template_ids_list}})
if template_type == 'xml':
xmlroot = ET.fromstring(dump.encode('utf-8'))
# remove date field if requested
if omit_date:
date = xmlroot.find(".date")
if date is not None:
xmlroot.remove(date)
if PY2:
return str(ET.tostring(xmlroot, encoding='utf-8'))
else:
return str(ET.tostring(xmlroot, encoding='utf-8').decode('utf-8'))
else:
return self.load_json_template(dump, omit_date=omit_date)
except Exception as e:
self._module.fail_json(msg='Unable to export template: %s' % e)
def diff_template(self, template_json_a, template_json_b):
# Compare 2 zabbix templates and return True if they differ.
template_json_a = self.filter_template(template_json_a)
template_json_b = self.filter_template(template_json_b)
if self.ordered_json(template_json_a) == self.ordered_json(template_json_b):
return False
return True
def filter_template(self, template_json):
# Filter the template json to contain only the keys we will update
keep_keys = set(['graphs', 'templates', 'triggers', 'value_maps'])
unwanted_keys = set(template_json['zabbix_export']) - keep_keys
for unwanted_key in unwanted_keys:
del template_json['zabbix_export'][unwanted_key]
# Versions older than 2.4 do not support description field within template
desc_not_supported = False
if LooseVersion(self._zbx_api_version) < LooseVersion('2.4'):
desc_not_supported = True
# Filter empty attributes from template object to allow accurate comparison
for template in template_json['zabbix_export']['templates']:
for key in list(template.keys()):
if not template[key] or (key == 'description' and desc_not_supported):
template.pop(key)
return template_json
def filter_xml_template(self, template_xml):
"""Filters out keys from XML template that may wary between exports (e.g date or version) and
keys that are not imported via this module.
It is advised that provided XML template exactly matches XML structure used by Zabbix"""
# Strip last new line and convert string to ElementTree
parsed_xml_root = self.load_xml_template(template_xml.strip())
keep_keys = ['graphs', 'templates', 'triggers', 'value_maps']
# Remove unwanted XML nodes
for node in list(parsed_xml_root):
if node.tag not in keep_keys:
parsed_xml_root.remove(node)
# Filter empty attributes from template objects to allow accurate comparison
for template in list(parsed_xml_root.find('templates')):
for element in list(template):
if element.text is None and len(list(element)) == 0:
template.remove(element)
# Filter new lines and indentation
xml_root_text = list(line.strip() for line in ET.tostring(parsed_xml_root, encoding='utf8', method='xml').decode().split('\n'))
return ''.join(xml_root_text)
def load_json_template(self, template_json, omit_date=False):
try:
jsondoc = json.loads(template_json)
if omit_date and 'date' in jsondoc['zabbix_export']:
del jsondoc['zabbix_export']['date']
return jsondoc
except ValueError as e:
self._module.fail_json(msg='Invalid JSON provided', details=to_native(e), exception=traceback.format_exc())
def load_xml_template(self, template_xml):
try:
return ET.fromstring(template_xml)
except ET.ParseError as e:
self._module.fail_json(msg='Invalid XML provided', details=to_native(e), exception=traceback.format_exc())
def import_template(self, template_content, template_type='json'):
if self._module.check_mode:
self._module.exit_json(changed=True)
# rules schema latest version
update_rules = {
'applications': {
'createMissing': True,
'deleteMissing': True
},
'discoveryRules': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'graphs': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'host_groups': {
'createMissing': True
},
'httptests': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'items': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'templates': {
'createMissing': True,
'updateExisting': True
},
'template_groups': {
'createMissing': True
},
'templateLinkage': {
'createMissing': True
},
'templateScreens': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'triggers': {
'createMissing': True,
'updateExisting': True,
'deleteMissing': True
},
'valueMaps': {
'createMissing': True,
'updateExisting': True
}
}
try:
# updateExisting for application removed from zabbix api after 3.2
if LooseVersion(self._zbx_api_version) <= LooseVersion('3.2'):
update_rules['applications']['updateExisting'] = True
# templateLinkage.deleteMissing only available in 4.0 branch higher .16 and higher 4.4.4
# it's not available in 4.2 branches or lower 4.0.16
if LooseVersion(self._zbx_api_version).version[:2] == LooseVersion('4.0').version and \
LooseVersion(self._zbx_api_version).version[:3] >= LooseVersion('4.0.16').version:
update_rules['templateLinkage']['deleteMissing'] = True
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.4.4'):
update_rules['templateLinkage']['deleteMissing'] = True
# templateScreens is named templateDashboards in zabbix >= 5.2
# https://support.zabbix.com/browse/ZBX-18677
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.2'):
update_rules["templateDashboards"] = update_rules.pop("templateScreens")
# Zabbix 5.4 no longer supports applications
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.4'):
update_rules.pop('applications', None)
# before Zabbix 6.2 host_groups and template_group are joined into groups parameter
if LooseVersion(self._zbx_api_version) < LooseVersion('6.2'):
update_rules['groups'] = {'createMissing': True}
update_rules.pop('host_groups', None)
update_rules.pop('template_groups', None)
# The loaded unicode slash of multibyte as a string is escaped when parsing JSON by json.loads in Python2.
# So, it is imported in the unicode string into Zabbix.
# The following processing is removing the unnecessary slash in escaped for decoding correctly to the multibyte string.
# https://github.com/ansible-collections/community.zabbix/issues/314
if PY2:
template_content = re.sub(r'\\\\u([0-9a-z]{,4})', r'\\u\1', template_content)
import_data = {'format': template_type, 'source': template_content, 'rules': update_rules}
self._zapi.configuration.import_(import_data)
except Exception as e:
self._module.fail_json(msg='Unable to import template', details=to_native(e),
exception=traceback.format_exc())
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
template_name=dict(type='str', required=False),
template_json=dict(type='json', required=False),
template_xml=dict(type='str', required=False),
template_groups=dict(type='list', required=False),
link_templates=dict(type='list', required=False),
clear_templates=dict(type='list', required=False),
macros=dict(
type='list',
elements='dict',
options=dict(
macro=dict(type='str', required=True),
value=dict(type='str', required=True)
)
),
tags=dict(
type='list',
elements='dict',
options=dict(
tag=dict(type='str', required=True),
value=dict(type='str', default='')
)
),
omit_date=dict(type='bool', required=False, default=False),
dump_format=dict(type='str', required=False, default='json', choices=['json', 'xml']),
state=dict(type='str', default="present", choices=['present', 'absent', 'dump']),
))
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=[
['template_name', 'template_json', 'template_xml']
],
mutually_exclusive=[
['template_name', 'template_json', 'template_xml']
],
required_if=[
['state', 'absent', ['template_name']],
['state', 'dump', ['template_name']]
],
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
template_name = module.params['template_name']
template_json = module.params['template_json']
template_xml = module.params['template_xml']
template_groups = module.params['template_groups']
link_templates = module.params['link_templates']
clear_templates = module.params['clear_templates']
template_macros = module.params['macros']
template_tags = module.params['tags']
omit_date = module.params['omit_date']
dump_format = module.params['dump_format']
state = module.params['state']
template = Template(module)
# Identify template names for IDs retrieval
# Template names are expected to reside in ['zabbix_export']['templates'][*]['template'] for both data types
template_content, template_type = None, None
if template_json is not None:
template_type = 'json'
template_content = template_json
json_parsed = template.load_json_template(template_content)
template_names = list(t['template'] for t in json_parsed['zabbix_export']['templates'])
elif template_xml is not None:
template_type = 'xml'
template_content = template_xml
xml_parsed = template.load_xml_template(template_content)
template_names = list(t.find('template').text for t in list(xml_parsed.find('templates')))
else:
template_names = [template_name]
template_ids = template.get_template_ids(template_names)
if state == "absent":
if not template_ids:
module.exit_json(changed=False, msg="Template not found. No changed: %s" % template_name)
template.delete_template(template_ids)
module.exit_json(changed=True, result="Successfully deleted template %s" % template_name)
elif state == "dump":
module.deprecate("The 'dump' state has been deprecated and will be removed, use 'zabbix_template_info' module instead.",
collection_name="community.zabbix", version='3.0.0') # was 2.14
if not template_ids:
module.fail_json(msg='Template not found: %s' % template_name)
if dump_format == 'json':
module.exit_json(changed=False, template_json=template.dump_template(template_ids, template_type='json', omit_date=omit_date))
elif dump_format == 'xml':
module.exit_json(changed=False, template_xml=template.dump_template(template_ids, template_type='xml', omit_date=omit_date))
elif state == "present":
# Load all subelements for template that were provided by user
group_ids = None
if template_groups is not None:
group_ids = template.get_group_ids_by_group_names(template_groups)
link_template_ids = None
if link_templates is not None:
link_template_ids = template.get_template_ids(link_templates)
clear_template_ids = None
if clear_templates is not None:
clear_template_ids = template.get_template_ids(clear_templates)
if template_macros is not None:
# Zabbix configuration.export does not differentiate python types (numbers are returned as strings)
for macroitem in template_macros:
for key in macroitem:
macroitem[key] = str(macroitem[key])
if template_tags is not None:
for tagitem in template_tags:
for key in tagitem:
tagitem[key] = str(tagitem[key])
if not template_ids:
# Assume new templates are being added when no ID's were found
if template_content is not None:
template.import_template(template_content, template_type)
module.exit_json(changed=True, result="Template import successful")
else:
if group_ids is None:
module.fail_json(msg='template_groups are required when creating a new Zabbix template')
template.add_template(template_name, group_ids, link_template_ids, template_macros, template_tags)
module.exit_json(changed=True, result="Successfully added template: %s" % template_name)
else:
changed = template.check_template_changed(template_ids, template_groups, link_templates, clear_templates,
template_macros, template_tags, template_content, template_type)
if module.check_mode:
module.exit_json(changed=changed)
if changed:
if template_type is not None:
template.import_template(template_content, template_type)
else:
template.update_template(template_ids, group_ids, link_template_ids, clear_template_ids,
template_macros, template_tags)
module.exit_json(changed=changed, result="Template successfully updated")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,325 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, sky-joker
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: zabbix_template_info
short_description: Gather information about Zabbix template
author:
- sky-joker (@sky-joker)
description:
- This module allows you to search for Zabbix template.
requirements:
- "python >= 2.6"
options:
template_name:
description:
- Name of the template in Zabbix.
required: true
type: str
format:
description:
- Format to use when dumping template.
- C(yaml) works only with Zabbix >= 5.2.
choices: ['json', 'xml', 'yaml', 'none']
default: json
type: str
omit_date:
description:
- Removes the date field for the dumped template
required: false
type: bool
default: false
extends_documentation_fragment:
- community.zabbix.zabbix
notes:
- there where breaking changes in the Zabbix API with version 5.4 onwards (especially UUIDs) which may
require you to export the templates again (see version tag >= 5.4 in the resulting file/data).
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get Zabbix template as JSON
community.zabbix.zabbix_template_info:
template_name: Template
format: json
omit_date: yes
register: template_json
- name: Get Zabbix template as XML
community.zabbix.zabbix_template_info:
template_name: Template
format: xml
omit_date: no
register: template_json
- name: Get Zabbix template as YAML
community.zabbix.zabbix_template_info:
template_name: Template
format: yaml
omit_date: no
register: template_yaml
- name: Determine if Zabbix template exists
community.zabbix.zabbix_template_info:
template_name: Template
format: none
register: template
'''
RETURN = '''
---
template_id:
description: The ID of the template
returned: always
type: str
template_json:
description: The JSON of the template
returned: when format is json and omit_date is true
type: str
sample: {
"zabbix_export": {
"version": "4.0",
"groups": [
{
"name": "Templates"
}
],
"templates": [
{
"template": "Test Template",
"name": "Template for Testing",
"description": "Testing template import",
"groups": [
{
"name": "Templates"
}
],
"applications": [
{
"name": "Test Application"
}
],
"items": [],
"discovery_rules": [],
"httptests": [],
"macros": [],
"templates": [],
"screens": []
}
]
}
}
template_xml:
description: The XML of the template
returned: when format is xml and omit_date is false
type: str
sample: >-
<zabbix_export>
<version>4.0</version>
<date>2019-10-27T14:49:57Z</date>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<templates>
<template>
<template>Test Template</template>
<name>Template for Testing</name>
<description>Testing template import</description>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<applications>
<application>
<name>Test Application</name>
</application>
</applications>
<items />
<discovery_rules />
<httptests />
<macros />
<templates />
<screens />
</template>
</templates>
</zabbix_export>
template_yaml:
description: The YAML of the template
returned: when format is yaml and omit_date is false
type: str
sample: >-
zabbix_export:
version: '6.0'
date: '2022-07-09T13:25:18Z'
groups:
-
uuid: 7df96b18c230490a9a0a9e2307226338
name: Templates
templates:
-
uuid: 88a9ad240f924f669eb7d4eed736320c
template: 'Test Template'
name: 'Template for Testing'
description: 'Testing template import'
groups:
-
name: Templates
'''
import traceback
import json
import xml.etree.ElementTree as ET
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.six import PY2
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class TemplateInfo(ZabbixBase):
def get_template_id(self, template_name):
template_id = []
try:
template_list = self._zapi.template.get({'output': ['templateid'],
'filter': {'host': template_name}})
except Exception as e:
self._module.fail_json(msg='Failed to get template: %s' % e)
if template_list:
template_id.append(template_list[0]['templateid'])
return template_id
def load_json_template(self, template_json, omit_date=False):
try:
jsondoc = json.loads(template_json)
# remove date field if requested
if omit_date and 'date' in jsondoc['zabbix_export']:
del jsondoc['zabbix_export']['date']
return jsondoc
except ValueError as e:
self._module.fail_json(msg='Invalid JSON provided', details=to_native(e), exception=traceback.format_exc())
def load_yaml_template(self, template_yaml, omit_date=False):
if omit_date:
yaml_lines = template_yaml.splitlines(True)
for index, line in enumerate(yaml_lines):
if 'date:' in line:
del yaml_lines[index]
return ''.join(yaml_lines)
else:
return template_yaml
def dump_template(self, template_id, template_type='json', omit_date=False):
try:
dump = self._zapi.configuration.export({'format': template_type, 'options': {'templates': template_id}})
if template_type == 'xml':
xmlroot = ET.fromstring(dump.encode('utf-8'))
# remove date field if requested
if omit_date:
date = xmlroot.find(".date")
if date is not None:
xmlroot.remove(date)
if PY2:
return str(ET.tostring(xmlroot, encoding='utf-8'))
else:
return str(ET.tostring(xmlroot, encoding='utf-8').decode('utf-8'))
elif template_type == 'yaml':
return self.load_yaml_template(dump, omit_date)
else:
return self.load_json_template(dump, omit_date)
except Exception as e:
self._module.fail_json(msg='Unable to export template: %s' % e)
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
template_name=dict(type='str', required=True),
omit_date=dict(type='bool', required=False, default=False),
format=dict(type='str', choices=['json', 'xml', 'yaml', 'none'], default='json')
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
template_name = module.params['template_name']
omit_date = module.params['omit_date']
format = module.params['format']
template_info = TemplateInfo(module)
template_id = template_info.get_template_id(template_name)
if not template_id:
module.fail_json(msg='Template not found: %s' % template_name)
if format == 'json':
module.exit_json(
changed=False,
template_id=template_id[0],
template_json=template_info.dump_template(template_id, template_type='json', omit_date=omit_date)
)
elif format == 'xml':
module.exit_json(
changed=False,
template_id=template_id[0],
template_xml=template_info.dump_template(template_id, template_type='xml', omit_date=omit_date)
)
elif format == 'yaml':
module.exit_json(
changed=False,
template_id=template_id[0],
template_yaml=template_info.dump_template(template_id, template_type='yaml', omit_date=omit_date)
)
elif format == 'none':
module.exit_json(changed=False, template_id=template_id[0])
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,765 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, sky-joker
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
module: zabbix_user
short_description: Create/update/delete Zabbix users
author:
- sky-joker (@sky-joker)
description:
- This module allows you to create, modify and delete Zabbix users.
requirements:
- "python >= 2.6"
options:
username:
description:
- Name of the user alias in Zabbix.
- username is the unique identifier used and cannot be updated using this module.
- alias should be replaced with username
aliases: [ alias ]
required: true
type: str
name:
description:
- Name of the user.
type: str
surname:
description:
- Surname of the user.
type: str
usrgrps:
description:
- User groups to add the user to.
- Required when I(state=present).
required: false
type: list
elements: str
passwd:
description:
- User's password.
- Required unless all of the I(usrgrps) are set to use LDAP as frontend access.
- Always required for Zabbix versions lower than 4.0.
required: false
type: str
override_passwd:
description:
- Override password for the user.
- Password will not be updated on subsequent runs without setting this value to yes.
default: no
type: bool
lang:
description:
- Language code of the user's language.
- C(default) can be used with Zabbix version 5.2 or higher.
choices:
- 'en_GB'
- 'en_US'
- 'zh_CN'
- 'cs_CZ'
- 'fr_FR'
- 'he_IL'
- 'it_IT'
- 'ko_KR'
- 'ja_JP'
- 'nb_NO'
- 'pl_PL'
- 'pt_BR'
- 'pt_PT'
- 'ru_RU'
- 'sk_SK'
- 'tr_TR'
- 'uk_UA'
- 'default'
type: str
theme:
description:
- User's theme.
choices:
- 'default'
- 'blue-theme'
- 'dark-theme'
type: str
autologin:
description:
- Whether to enable auto-login.
- If enable autologin, cannot enable autologout.
type: bool
autologout:
description:
- User session life time in seconds. If set to 0, the session will never expire.
- If enable autologout, cannot enable autologin.
type: str
refresh:
description:
- Automatic refresh period in seconds.
type: str
rows_per_page:
description:
- Amount of object rows to show per page.
type: str
after_login_url:
description:
- URL of the page to redirect the user to after logging in.
type: str
user_medias:
description:
- Set the user's media.
- If not set, makes no changes to media.
suboptions:
mediatype:
description:
- Media type name to set.
default: 'Email'
type: str
sendto:
description:
- Address, user name or other identifier of the recipient.
required: true
type: str
period:
description:
- Time when the notifications can be sent as a time period or user macros separated by a semicolon.
- Please review the documentation for more information on the supported time period.
- https://www.zabbix.com/documentation/4.0/manual/appendix/time_period
default: '1-7,00:00-24:00'
type: str
severity:
description:
- Trigger severities to send notifications about.
suboptions:
not_classified:
description:
- severity not_classified enable/disable.
default: True
type: bool
information:
description:
- severity information enable/disable.
default: True
type: bool
warning:
description:
- severity warning enable/disable.
default: True
type: bool
average:
description:
- severity average enable/disable.
default: True
type: bool
high:
description:
- severity high enable/disable.
default: True
type: bool
disaster:
description:
- severity disaster enable/disable.
default: True
type: bool
default:
not_classified: True
information: True
warning: True
average: True
high: True
disaster: True
type: dict
active:
description:
- Whether the media is enabled.
default: true
type: bool
type: list
elements: dict
type:
description:
- Type of the user.
- I(type) can be used when Zabbix version is 5.0 or lower.
choices:
- 'Zabbix user'
- 'Zabbix admin'
- 'Zabbix super admin'
type: str
timezone:
description:
- User's time zone.
- I(timezone) can be used with Zabbix version 5.2 or higher.
- For the full list of supported time zones please refer to U(https://www.php.net/manual/en/timezones.php)
type: str
version_added: 1.2.0
role_name:
description:
- User's role.
- I(role_name) can be used when Zabbix version is 5.2 or higher.
- Default is C(User role) when creating a new user.
- The default value will be removed at the version 2.0.0.
type: str
version_added: 1.2.0
state:
description:
- State of the user.
- On C(present), it will create if user does not exist or update the user if the associated data is different.
- On C(absent) will remove a user if it exists.
default: 'present'
choices: ['present', 'absent']
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: create a new zabbix user.
community.zabbix.zabbix_user:
username: example
name: user name
surname: user surname
usrgrps:
- Guests
- Disabled
passwd: password
lang: en_GB
theme: blue-theme
autologin: no
autologout: '0'
refresh: '30'
rows_per_page: '200'
after_login_url: ''
user_medias:
- mediatype: Email
sendto: example@example.com
period: 1-7,00:00-24:00
severity:
not_classified: no
information: yes
warning: yes
average: yes
high: yes
disaster: yes
active: no
type: Zabbix super admin
state: present
- name: delete existing zabbix user.
community.zabbix.zabbix_user:
username: example
usrgrps:
- Guests
passwd: password
user_medias:
- sendto: example@example.com
state: absent
'''
RETURN = r'''
user_ids:
description: User id created or changed
returned: success
type: dict
sample: { "userids": [ "5" ] }
'''
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.helpers import helper_normalize_data
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class User(ZabbixBase):
def username_key(self):
""" Returns the key name for 'username', which was 'alias'
before Zabbix 5.4.
"""
if LooseVersion(self._zbx_api_version) < LooseVersion('5.4'):
return 'alias'
return 'username'
def get_default_authentication(self):
auth = self._zapi.authentication.get({'output': 'extend'})
try:
if auth["authentication_type"] == "0":
return "internal"
elif auth["authentication_type"] == "1":
return "LDAP"
else:
self._module.fail_json(msg="Failed to query authentication type. Unknown authentication type %s" % auth)
except Exception as e:
self._module.fail_json(msg="Unhandled error while querying authentication type. %s" % (e))
def get_usergroups_by_name(self, usrgrps):
params = {
'output': ['usrgrpid', 'name', 'gui_access'],
'filter': {
'name': usrgrps
}
}
res = self._zapi.usergroup.get(params)
if res:
ids = [{'usrgrpid': g['usrgrpid']} for g in res]
# User can be created password-less only when all groups are of non-internal
# authentication types
# 0 = use system default authentication method
# 1 = use internal authentication
# 2 = use LDAP authentication
# 3 = disable access to the frontend
if bool([g for g in res if g['gui_access'] == '1']):
require_password = True
elif bool([g for g in res if g['gui_access'] == '2' or g['gui_access'] == '3']):
require_password = False
elif bool([g for g in res if g['gui_access'] == '0']):
# Zabbix API for versions < 5.2 does not have a way to query the default auth type
# so we must assume its set to internal
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.2'):
default_authentication = self.get_default_authentication()
require_password = True if default_authentication == 'internal' else False
else:
default_authentication = "internal"
require_password = True
not_found_groups = set(usrgrps) - set([g['name'] for g in res])
if not_found_groups:
self._module.fail_json(msg='User groups not found: %s' % not_found_groups)
return ids, require_password
else:
self._module.fail_json(msg='No user groups found')
def check_user_exist(self, username):
zbx_user = self._zapi.user.get({'output': 'extend', 'filter': {self.username_key(): username},
'getAccess': True, 'selectMedias': 'extend',
'selectUsrgrps': 'extend'})
return zbx_user
def convert_user_medias_parameter_types(self, user_medias):
copy_user_medias = copy.deepcopy(user_medias)
for user_media in copy_user_medias:
media_types = self._zapi.mediatype.get({'output': 'extend'})
for media_type in media_types:
if LooseVersion(self._zbx_api_version) < LooseVersion('4.4'):
if media_type['description'] == user_media['mediatype']:
user_media['mediatypeid'] = media_type['mediatypeid']
break
else:
if media_type['name'] == user_media['mediatype']:
user_media['mediatypeid'] = media_type['mediatypeid']
break
if 'mediatypeid' not in user_media:
self._module.fail_json(msg="Media type not found: %s" % user_media['mediatype'])
else:
del user_media['mediatype']
severity_binary_number = ''
for severity_key in 'disaster', 'high', 'average', 'warning', 'information', 'not_classified':
if user_media['severity'][severity_key]:
severity_binary_number = severity_binary_number + '1'
else:
severity_binary_number = severity_binary_number + '0'
user_media['severity'] = str(int(severity_binary_number, 2))
if user_media['active']:
user_media['active'] = '0'
else:
user_media['active'] = '1'
return copy_user_medias
def get_roleid_by_name(self, role_name):
roles = self._zapi.role.get({'output': 'extend'})
for role in roles:
if role['name'] == role_name:
return role['roleid']
self._module.fail_json(msg="Role not found: %s" % role_name)
def user_parameter_difference_check(self, zbx_user, username, name, surname, user_group_ids, passwd, lang, theme,
autologin, autologout, refresh, rows_per_page, url, user_medias, user_type,
timezone, role_name, override_passwd):
if user_medias:
user_medias = self.convert_user_medias_parameter_types(user_medias)
# existing data
existing_data = copy.deepcopy(zbx_user[0])
usrgrpids = []
for usrgrp in existing_data['usrgrps']:
usrgrpids.append({'usrgrpid': usrgrp['usrgrpid']})
existing_data['usrgrps'] = sorted(usrgrpids, key=lambda x: x['usrgrpid'])
# Processing for zabbix 4.0 and above.
# In zabbix 4.0 and above, Email sendto is of type list.
# This module, one media supports only one Email sendto.
# Therefore following processing extract one Email from list.
if LooseVersion(self._zbx_api_version) >= LooseVersion('4.0'):
for media in existing_data['medias']:
if isinstance(media['sendto'], list):
media['sendto'] = media['sendto'][0]
existing_data['user_medias'] = sorted(existing_data['medias'], key=lambda x: x['sendto'])
for del_key in ['medias', 'attempt_clock', 'attempt_failed', 'attempt_ip', 'debug_mode', 'users_status',
'gui_access']:
del existing_data[del_key]
for user_media in existing_data['user_medias']:
for del_key in ['mediaid', 'userid']:
del user_media[del_key]
# request data
request_data = {
'userid': zbx_user[0]['userid'],
self.username_key(): username,
'name': name,
'surname': surname,
'usrgrps': sorted(user_group_ids, key=lambda x: x['usrgrpid']),
'lang': lang,
'theme': theme,
'autologin': autologin,
'autologout': autologout,
'refresh': refresh,
'rows_per_page': rows_per_page,
'url': url,
}
if user_medias:
request_data['user_medias'] = sorted(user_medias, key=lambda x: x['sendto'])
else:
del existing_data['user_medias']
if override_passwd:
request_data['passwd'] = passwd
# The type key has changed to roleid key since Zabbix 5.2
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2'):
request_data['type'] = user_type
else:
request_data['roleid'] = self.get_roleid_by_name(role_name) if role_name else None
request_data['timezone'] = timezone
request_data, del_keys = helper_normalize_data(request_data)
existing_data, _del_keys = helper_normalize_data(existing_data, del_keys)
user_parameter_difference_check_result = True
if existing_data == request_data:
user_parameter_difference_check_result = False
diff_params = {
"before": existing_data,
"after": request_data
}
return user_parameter_difference_check_result, diff_params
def add_user(self, username, name, surname, user_group_ids, passwd, lang, theme, autologin, autologout, refresh,
rows_per_page, url, user_medias, user_type, require_password, timezone, role_name):
if role_name is None and LooseVersion(self._zbx_api_version) >= LooseVersion('5.2'):
# This variable is to set the default value because the module must have a backward-compatible.
# The default value will be removed at the version 2.0.0.
# https://github.com/ansible-collections/community.zabbix/pull/382
role_name = "User role"
if user_medias:
user_medias = self.convert_user_medias_parameter_types(user_medias)
user_ids = {}
request_data = {
self.username_key(): username,
'name': name,
'surname': surname,
'usrgrps': user_group_ids,
'lang': lang,
'theme': theme,
'autologin': autologin,
'autologout': autologout,
'refresh': refresh,
'rows_per_page': rows_per_page,
'url': url,
}
if user_medias:
request_data['user_medias'] = user_medias
if LooseVersion(self._zbx_api_version) < LooseVersion('4.0') or require_password:
request_data['passwd'] = passwd
# The type key has changed to roleid key since Zabbix 5.2
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2'):
request_data['type'] = user_type
else:
request_data['roleid'] = self.get_roleid_by_name(role_name)
request_data['timezone'] = timezone
request_data, _del_keys = helper_normalize_data(request_data)
diff_params = {}
if not self._module.check_mode:
try:
user_ids = self._zapi.user.create(request_data)
except Exception as e:
self._module.fail_json(msg="Failed to create user %s: %s" % (username, e))
else:
diff_params = {
"before": "",
"after": request_data
}
return user_ids, diff_params
def update_user(self, zbx_user, username, name, surname, user_group_ids, passwd, lang, theme, autologin, autologout,
refresh, rows_per_page, url, user_medias, user_type, timezone, role_name, override_passwd):
if user_medias:
user_medias = self.convert_user_medias_parameter_types(user_medias)
user_ids = {}
request_data = {
'userid': zbx_user[0]['userid'],
self.username_key(): username,
'name': name,
'surname': surname,
'usrgrps': user_group_ids,
'lang': lang,
'theme': theme,
'autologin': autologin,
'autologout': autologout,
'refresh': refresh,
'rows_per_page': rows_per_page,
'url': url,
}
if override_passwd:
request_data['passwd'] = passwd
# The type key has changed to roleid key since Zabbix 5.2
if LooseVersion(self._zbx_api_version) < LooseVersion('5.2'):
request_data['type'] = user_type
else:
request_data['roleid'] = self.get_roleid_by_name(role_name) if role_name else None
request_data['timezone'] = timezone
request_data, _del_keys = helper_normalize_data(request_data)
# In the case of zabbix 3.2 or less, it is necessary to use updatemedia method to update media.
if LooseVersion(self._zbx_api_version) <= LooseVersion('3.2'):
try:
user_ids = self._zapi.user.update(request_data)
except Exception as e:
self._module.fail_json(msg="Failed to update user %s: %s" % (username, e))
try:
if user_medias:
user_ids = self._zapi.user.updatemedia({
'users': [{'userid': zbx_user[0]['userid']}],
'medias': user_medias
})
except Exception as e:
self._module.fail_json(msg="Failed to update user medias %s: %s" % (username, e))
if LooseVersion(self._zbx_api_version) >= LooseVersion('3.4'):
try:
if user_medias:
request_data['user_medias'] = user_medias
user_ids = self._zapi.user.update(request_data)
except Exception as e:
self._module.fail_json(msg="Failed to update user %s: %s" % (username, e))
return user_ids
def delete_user(self, zbx_user, username):
user_ids = {}
diff_params = {}
if not self._module.check_mode:
try:
user_ids = self._zapi.user.delete([zbx_user[0]['userid']])
except Exception as e:
self._module.fail_json(msg="Failed to delete user %s: %s" % (username, e))
else:
diff_params = {
"before": zbx_user[0],
"after": ""
}
return user_ids, diff_params
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
username=dict(type='str', required=True, aliases=['alias']),
name=dict(type='str'),
surname=dict(type='str'),
usrgrps=dict(type='list'),
passwd=dict(type='str', required=False, no_log=True),
override_passwd=dict(type='bool', required=False, default=False, no_log=False),
lang=dict(type='str', choices=['en_GB', 'en_US', 'zh_CN', 'cs_CZ', 'fr_FR',
'he_IL', 'it_IT', 'ko_KR', 'ja_JP', 'nb_NO',
'pl_PL', 'pt_BR', 'pt_PT', 'ru_RU', 'sk_SK',
'tr_TR', 'uk_UA', 'default']),
theme=dict(type='str', choices=['default', 'blue-theme', 'dark-theme']),
autologin=dict(type='bool'),
autologout=dict(type='str'),
refresh=dict(type='str'),
rows_per_page=dict(type='str'),
after_login_url=dict(type='str'),
user_medias=dict(type='list', elements='dict',
options=dict(mediatype=dict(type='str', default='Email'),
sendto=dict(type='str', required=True),
period=dict(type='str', default='1-7,00:00-24:00'),
severity=dict(type='dict',
options=dict(
not_classified=dict(type='bool', default=True),
information=dict(type='bool', default=True),
warning=dict(type='bool', default=True),
average=dict(type='bool', default=True),
high=dict(type='bool', default=True),
disaster=dict(type='bool', default=True)),
default=dict(
not_classified=True,
information=True,
warning=True,
average=True,
high=True,
disaster=True)),
active=dict(type='bool', default=True))),
timezone=dict(type='str'),
role_name=dict(type='str'),
type=dict(type='str', choices=['Zabbix user', 'Zabbix admin', 'Zabbix super admin']),
state=dict(type='str', default="present", choices=['present', 'absent'])
))
module = AnsibleModule(
argument_spec=argument_spec,
required_if=[
['state', 'present', ['usrgrps']]
],
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
username = module.params['username']
name = module.params['name']
surname = module.params['surname']
usrgrps = module.params['usrgrps']
passwd = module.params['passwd']
override_passwd = module.params['override_passwd']
lang = module.params['lang']
theme = module.params['theme']
autologin = module.params['autologin']
autologout = module.params['autologout']
refresh = module.params['refresh']
rows_per_page = module.params['rows_per_page']
after_login_url = module.params['after_login_url']
user_medias = module.params['user_medias']
user_type = module.params['type']
timezone = module.params['timezone']
role_name = module.params['role_name']
state = module.params['state']
if autologin is not None:
if autologin:
autologin = '1'
else:
autologin = '0'
user_type_dict = {
'Zabbix user': '1',
'Zabbix admin': '2',
'Zabbix super admin': '3'
}
user_type = user_type_dict[user_type] if user_type else None
user = User(module)
user_ids = {}
zbx_user = user.check_user_exist(username)
if state == 'present':
user_group_ids, require_password = user.get_usergroups_by_name(usrgrps)
if LooseVersion(user._zbx_api_version) < LooseVersion('4.0') or require_password:
if passwd is None:
module.fail_json(msg='User password is required. One or more groups are not LDAP based.')
if zbx_user:
diff_check_result, diff_params = user.user_parameter_difference_check(zbx_user, username, name, surname,
user_group_ids, passwd, lang, theme,
autologin, autologout, refresh,
rows_per_page, after_login_url,
user_medias, user_type, timezone,
role_name, override_passwd)
if not module.check_mode and diff_check_result:
user_ids = user.update_user(zbx_user, username, name, surname, user_group_ids, passwd, lang,
theme, autologin, autologout, refresh, rows_per_page, after_login_url,
user_medias, user_type, timezone, role_name, override_passwd)
else:
diff_check_result = True
user_ids, diff_params = user.add_user(username, name, surname, user_group_ids, passwd, lang, theme, autologin,
autologout, refresh, rows_per_page, after_login_url, user_medias,
user_type, require_password, timezone, role_name)
if state == 'absent':
if zbx_user:
diff_check_result = True
user_ids, diff_params = user.delete_user(zbx_user, username)
else:
diff_check_result = False
diff_params = {}
if not module.check_mode:
if user_ids:
module.exit_json(changed=True, user_ids=user_ids)
else:
module.exit_json(changed=False)
else:
if diff_check_result:
module.exit_json(changed=True, diff=diff_params)
else:
module.exit_json(changed=False, diff=diff_params)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,217 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_user_directory
short_description: Create/update/delete Zabbix user directories
description:
- This module allows you to create, modify and delete Zabbix user directories.
author:
- Evgeny Yurchenko (@BGmot)
requirements:
- python >= 3.9
options:
name:
description:
- Unique name of the user directory.
required: true
type: str
host:
description:
- LDAP server host name, IP or URI. URI should contain schema, host and port (optional).
required: false
type: str
port:
description:
- LDAP server port.
required: false
type: int
base_dn:
description:
- LDAP base distinguished name string.
required: false
type: str
search_attribute:
description:
- LDAP attribute name to identify user by username in Zabbix database.
required: false
type: str
bind_dn:
description:
- LDAP bind distinguished name string. Can be empty for anonymous binding.
required: false
type: str
default: ''
bind_password:
description:
- LDAP bind password. Can be empty for anonymous binding.
- Available only for I(present) C(state).
required: false
type: str
description:
description:
- User directory description.
required: false
type: str
default: ''
search_filter:
description:
- LDAP custom filter string when authenticating user in LDAP.
default: (%{attr}=%{user})
required: false
type: str
start_tls:
description:
- LDAP startTLS option. It cannot be used with ldaps:// protocol hosts.
required: false
type: int
choices: [0, 1]
default: 0
state:
description:
- State of the user directory.
- On C(present), it will create if user directory does not exist or update it if the associated data is different.
- On C(absent) will remove the user directory if it exists.
choices: ['present', 'absent']
default: 'present'
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
---
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create new user directory or update existing info
community.zabbix.zabbix_user_directory:
server_url: http://monitor.example.com
login_user: username
login_password: password
state: present
name: TestUserDirectory
host: 'test.com'
port: 389
base_dn: 'ou=Users,dc=example,dc=org'
search_attribute: 'uid'
bind_dn: 'cn=ldap_search,dc=example,dc=org'
description: 'Test user directory'
search_filter: '(%{attr}=test_user)'
start_tls: 0
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
host=dict(type='str', required=False),
port=dict(type='int', required=False),
base_dn=dict(type='str', required=False),
search_attribute=dict(type='str', required=False),
bind_dn=dict(type='str', required=False, default=''),
bind_password=dict(type='str', required=False, no_log=True),
description=dict(type='str', required=False, default=''),
search_filter=dict(type='str', default='(%{attr}=%{user})', required=False),
start_tls=dict(type='int', required=False, choices=[0, 1], default=0),
state=dict(type='str', default='present', choices=['present', 'absent']),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
parameters = {
'name': module.params['name'],
'search_filter': module.params['search_filter']
}
for p in ['host', 'port', 'base_dn', 'search_attribute', 'bind_dn', 'bind_password', 'description', 'start_tls']:
if module.params[p]:
if p in ['port', 'start_tls']:
parameters[p] = str(module.params[p])
else:
parameters[p] = module.params[p]
state = module.params['state']
user_directory = ZabbixBase(module)
if LooseVersion(user_directory._zbx_api_version) < LooseVersion('6.2'):
module.fail_json(msg='Zabbix < 6.2 does not support user directories.')
directory = user_directory._zapi.userdirectory.get({'filter': {'name': parameters['name']}})
if not directory:
# No User Directory found with given name
if state == 'absent':
module.exit_json(changed=False, msg='User directory not found. Not changed: %s' % parameters['name'])
elif state == 'present':
if module.check_mode:
module.exit_json(changed=True)
else:
for p in ['host', 'port', 'base_dn', 'search_attribute']:
if p not in parameters:
module.fail_json(msg='host, port, base_dn and search_attribute are mandatory parameters to create a user directory')
user_directory._zapi.userdirectory.create(parameters)
module.exit_json(changed=True, result='Successfully added user directory %s' % parameters['name'])
else:
# User Directory with given name exists
if state == 'absent':
user_directory._zapi.userdirectory.delete([directory[0]['userdirectoryid']])
module.exit_json(changed=True, result='Successfully deleted user directory %s' % parameters['name'])
elif state == 'present':
diff_dict = {}
if zabbix_utils.helper_compare_dictionaries(parameters, directory[0], diff_dict):
parameters['userdirectoryid'] = directory[0]['userdirectoryid']
user_directory._zapi.userdirectory.update(parameters)
module.exit_json(changed=True, result='Successfully updated user directory %s' % parameters['name'])
else:
module.exit_json(changed=False, result='User directory %s is up-to date' % parameters['name'])
module.exit_json(changed=False, result='User directory %s is up-to date' % parameters['name'])
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,166 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, sky-joker
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: zabbix_user_info
short_description: Gather information about Zabbix user
author:
- sky-joker (@sky-joker)
description:
- This module allows you to search for Zabbix user entries.
requirements:
- "python >= 2.6"
options:
username:
description:
- Name of the user alias in Zabbix.
- username is the unique identifier used and cannot be updated using this module.
- alias should be replaced with username
aliases: [ alias ]
required: true
type: str
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = '''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Get zabbix user info
community.zabbix.zabbix_user_info:
username: example
'''
RETURN = '''
zabbix_user:
description: example
returned: always
type: dict
sample: {
"username": "example",
"attempt_clock": "0",
"attempt_failed": "0",
"attempt_ip": "",
"autologin": "0",
"autologout": "0",
"debug_mode": "0",
"gui_access": "0",
"lang": "en_GB",
"medias": [
{
"active": "0",
"mediaid": "668",
"mediatypeid": "1",
"period": "1-7,00:00-24:00",
"sendto": "example@example.com",
"severity": "63",
"userid": "660"
}
],
"name": "user",
"refresh": "30s",
"rows_per_page": "50",
"surname": "example",
"theme": "default",
"type": "1",
"url": "",
"userid": "660",
"users_status": "0",
"usrgrps": [
{
"debug_mode": "0",
"gui_access": "0",
"name": "Guests",
"users_status": "0",
"usrgrpid": "8"
}
]
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.version import LooseVersion
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class User(ZabbixBase):
def get_user_by_user_username(self, username):
zabbix_user = ""
try:
data = {'output': 'extend', 'filter': {},
'getAccess': True, 'selectMedias': 'extend',
'selectUsrgrps': 'extend'}
if LooseVersion(self._zbx_api_version) >= LooseVersion('5.4'):
data['filter']['username'] = username
else:
data['filter']['alias'] = username
zabbix_user = self._zapi.user.get(data)
except Exception as e:
self._zapi.logout()
self._module.fail_json(msg="Failed to get user information: %s" % e)
if not zabbix_user:
zabbix_user = {}
else:
zabbix_user = zabbix_user[0]
return zabbix_user
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
username=dict(type='str', required=True, aliases=['alias']),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
username = module.params['username']
user = User(module)
zabbix_user = user.get_user_by_user_username(username)
module.exit_json(changed=False, zabbix_user=zabbix_user)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,224 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, mrvanes
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
module: zabbix_user_role
short_description: Adds or removes zabbix roles
author:
- Martin van Es (@mrvanes)
description:
- This module adds or removes zabbix roles
requirements:
- "python >= 2.6"
options:
state:
description:
- State of the user_role.
- On C(present), it will create if user_role does not exist or update the user_role if the associated data is different.
- On C(absent) will remove a user_role if it exists.
default: 'present'
choices: ['present', 'absent']
type: str
required: false
name:
description:
- Name of the role to be processed
type: str
required: true
type:
description:
- User type.
choices: ["User", "Admin", "Super Admin"]
default: "User"
type: str
required: false
rules:
description:
- Rules set as defined in https://www.zabbix.com/documentation/current/en/manual/api/reference/role/object#role-rules
default: {}
type: dict
required: false
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Create user role Operators with ui elements monitoring.hosts
# disabled and monitoring.maps enabled
- name: Create Zabbix user role
community.zabbix.zabbix_user_role:
state: present
name: Operators
type: User
rules:
ui.default_access: 0
ui:
- name: "monitoring.hosts"
status: 0
- name: "monitoring.maps"
status: 1
'''
RETURN = r'''
# Return values
msg:
description: The result of the action
type: str
returned: always
sample: 'No action'
changed:
description: The consequence of the action
type: bool
returned: always
sample: False
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class UserRole(ZabbixBase):
def __find_val(self, outval, inval):
if outval == str(inval):
return True
return False
def __find_list(self, outval, inval):
if set(outval) == set(inval):
return True
return False
def __find_dict(self, outval, inval):
for out in outval:
m = True
for k, v in inval.items():
if out[k] == str(v):
continue
else:
m = False
if m:
break
return m
def is_part_of(self, inp, out):
verdict = True
for rule, value in inp.items():
if not isinstance(value, list):
verdict = verdict and self.__find_val(out.get(rule, ''), value)
else:
if len(value):
if not isinstance(value[0], dict):
verdict = verdict and self.__find_list(out.get(rule, []), value)
else:
for v in value:
verdict = verdict and self.__find_dict(out.get(rule, {}), v)
else:
verdict = verdict and self.__find_list(rule, value)
return verdict
def get_user_role(self, name):
result = self._zapi.role.get({
"output": "extend",
"selectRules": "extend",
"filter": {"name": name}
})
return result
def main():
msg = "No action"
changed = False
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
state=dict(type='str', required=False, default='present', choices=['present', 'absent']),
name=dict(type='str', required=True),
type=dict(type='str', required=False, choices=["User", "Admin", "Super Admin"], default='User'),
rules=dict(type='dict', required=False, default={}),
))
# the AnsibleModule object
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection '
'and will be removed in the next release' % p)
state = module.params['state']
name = module.params['name']
type = zabbix_utils.helper_to_numeric_value(
['', 'user', 'admin', 'super admin'], module.params['type'].lower()
)
rules = module.params['rules']
user_role = UserRole(module)
result = user_role.get_user_role(name)
if result:
if len(result) == 1:
role = result[0]
if role['readonly'] != 1:
roleid = role['roleid']
if state == 'absent':
result = user_role._zapi.role.delete([f"{roleid}"])
changed = True
msg = "Role deleted"
else:
if not user_role.is_part_of(rules, role['rules']):
result = user_role._zapi.role.update({"roleid": roleid, "rules": rules})
changed = True
msg = "Role updated"
else:
module.fail_json(msg='Too many role matches')
else:
user_role._zapi.role.create({
"name": name,
"type": type,
"rules": rules
})
changed = True
msg = "Role created"
module.exit_json(msg=msg, changed=changed)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,510 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Tobias Birkefeld (@tcraxs) <t@craxs.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_usergroup
short_description: Create/delete/update Zabbix user groups
description:
- Create user groups if they do not exist.
- Delete existing user groups if they exist and are empty.
- Update existing user groups.
author:
- "Tobias Birkefeld (@tcraxs)"
requirements:
- "python >= 2.6"
options:
name:
description:
- Name of the user group to create, update or delete.
required: true
type: str
aliases: [ "user_group" ]
gui_access:
description:
- Frontend authentication method of the users in the group.
- "Possible values:"
- default - use the system default authentication method;
- internal - use internal authentication;
- LDAP - use LDAP authentication;
- disable - disable access to the frontend.
required: false
type: str
default: "default"
choices: [ "default", "internal", "LDAP", "disable"]
debug_mode:
description:
- Whether debug mode is enabled or disabled.
required: false
type: str
default: "disabled"
choices: [ "disabled", "enabled" ]
status:
description:
- Whether the user group is enabled or disabled.
required: false
type: str
default: "enabled"
choices: [ "enabled", "disabled" ]
rights:
description:
- Permissions to assign to the group
required: false
type: list
elements: dict
suboptions:
host_group:
description:
- Name of the host group to add permission to.
required: true
type: str
permission:
description:
- Access level to the host group.
required: true
type: str
choices: [ "denied", "read-only", "read-write" ]
tag_filters:
description:
- Tag based permissions to assign to the group
required: false
type: list
elements: dict
suboptions:
host_group:
description:
- Name of the host group to add permission to.
required: true
type: str
tag:
description:
- Tag name.
required: false
type: str
default: ''
value:
description:
- Tag value.
required: false
type: str
default: ''
state:
description:
- State of the user group.
- On C(present), it will create if user group does not exist or update the user group if the associated data is different.
- On C(absent) will remove a user group if it exists.
required: false
type: str
default: "present"
choices: [ "present", "absent" ]
notes:
- Only Zabbix >= 4.0 is supported.
extends_documentation_fragment:
- community.zabbix.zabbix
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
# Base create user group example
- name: Create user group
community.zabbix.zabbix_usergroup:
name: ACME
state: present
# Base create user group with disabled gui access
- name: Create user group with disabled gui access
community.zabbix.zabbix_usergroup:
name: ACME
gui_access: disable
# Base create user group with permissions
- name: Create user group with permissions
community.zabbix.zabbix_usergroup:
name: ACME
rights:
- host_group: Webserver
permission: read-write
- host_group: Databaseserver
permission: read-only
state: present
# Base create user group with tag permissions
- name: Create user group with tag permissions
community.zabbix.zabbix_usergroup:
name: ACME
tag_filters:
- host_group: Webserver
tag: Application
value: Java
- host_group: Discovered hosts
tag: Service
value: JIRA
state: present
# Base delete user groups example
- name: Delete user groups
community.zabbix.zabbix_usergroup:
name: ACME
state: absent
'''
RETURN = r'''
state:
description: User group state at the end of execution.
returned: on success
type: str
sample: 'present'
usergroup:
description: User group name.
returned: on success
type: str
sample: 'ACME'
usrgrpid:
description: User group id, if created, changed or deleted.
returned: on success
type: str
sample: '42'
msg:
description: The result of the operation
returned: always
type: str
sample: 'User group created: ACME, ID: 42'
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
class Rights(ZabbixBase):
"""
Restructure the user defined rights to fit the Zabbix API requirements
"""
def get_hostgroup_by_hostgroup_name(self, name):
"""Get host group by host group name.
Parameters:
name: Name of the host group.
Returns:
host group matching host group name.
"""
try:
_hostgroup = self._zapi.hostgroup.get({
'output': 'extend',
'filter': {'name': [name]}
})
if len(_hostgroup) < 1:
self._module.fail_json(msg='Host group not found: %s' % name)
else:
return _hostgroup[0]
except Exception as e:
self._module.fail_json(msg='Failed to get host group "%s": %s' % (name, e))
def construct_the_data(self, _rights):
"""Construct the user defined rights to fit the Zabbix API requirements
Parameters:
_rights: rights to construct
Returns:
dict: user defined rights
"""
if _rights is None:
return []
constructed_data = []
for right in _rights:
constructed_right = {
'id': self.get_hostgroup_by_hostgroup_name(right.get('host_group'))['groupid'],
'permission': zabbix_utils.helper_to_numeric_value([
'denied',
None,
'read-only',
'read-write'], right.get('permission')
)
}
constructed_data.append(constructed_right)
return zabbix_utils.helper_cleanup_data(constructed_data)
class TagFilters(Rights):
"""
Restructure the user defined tag_filters to fit the Zabbix API requirements
"""
def construct_the_data(self, _tag_filters):
"""Construct the user defined tag filters to fit the Zabbix API requirements
Parameters:
_tag_filters: tag filters to construct
Returns:
dict: user defined tag filters
"""
if _tag_filters is None:
return []
constructed_data = []
for tag_filter in _tag_filters:
constructed_tag_filter = {
'groupid': self.get_hostgroup_by_hostgroup_name(tag_filter.get('host_group'))['groupid'],
'tag': tag_filter.get('tag'),
'value': tag_filter.get('value')
}
constructed_data.append(constructed_tag_filter)
return zabbix_utils.helper_cleanup_data(constructed_data)
class UserGroup(ZabbixBase):
def _construct_parameters(self, **kwargs):
"""Construct parameters of UserGroup object
Parameters:
**kwargs: Arbitrary keyword parameters.
Returns:
dict: dictionary of specified parameters
"""
_params = {
'name': kwargs['name'],
'gui_access': zabbix_utils.helper_to_numeric_value([
'default',
'internal',
'LDAP',
'disable'], kwargs['gui_access']
),
'debug_mode': zabbix_utils.helper_to_numeric_value([
'disabled',
'enabled'], kwargs['debug_mode']
),
'users_status': zabbix_utils.helper_to_numeric_value([
'enabled',
'disabled'], kwargs['status']
),
'rights': kwargs['rights'],
'tag_filters': kwargs['tag_filters']
}
return _params
def check_if_usergroup_exists(self, name):
"""Check if user group exists.
Parameters:
name: Name of the user group.
Returns:
The return value. True for success, False otherwise.
"""
try:
_usergroup = self._zapi.usergroup.get({
'output': 'extend',
'filter': {'name': [name]}
})
if len(_usergroup) > 0:
return _usergroup
except Exception as e:
self._module.fail_json(msg='Failed to check if user group "%s" exists: %s' % (name, e))
def get_usergroup_by_usergroup_name(self, name):
"""Get user group by user group name.
Parameters:
name: Name of the user group.
Returns:
User group matching user group name.
"""
try:
_usergroup = self._zapi.usergroup.get({
'output': 'extend',
'selectTagFilters': 'extend',
'selectRights': 'extend',
'filter': {'name': [name]}
})
if len(_usergroup) < 1:
self._module.fail_json(msg='User group not found: %s' % name)
else:
return _usergroup[0]
except Exception as e:
self._module.fail_json(msg='Failed to get user group "%s": %s' % (name, e))
def check_difference(self, **kwargs):
"""Check difference between user group and user specified parameters.
Parameters:
**kwargs: Arbitrary keyword parameters.
Returns:
dict: dictionary of differences
"""
existing_usergroup = zabbix_utils.helper_convert_unicode_to_str(self.get_usergroup_by_usergroup_name(kwargs['name']))
parameters = zabbix_utils.helper_convert_unicode_to_str(self._construct_parameters(**kwargs))
change_parameters = {}
_diff = zabbix_utils.helper_compare_dictionaries(parameters, existing_usergroup, change_parameters)
return _diff
def update(self, **kwargs):
"""Update user group.
Parameters:
**kwargs: Arbitrary keyword parameters.
Returns:
usergroup: updated user group
"""
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
return self._zapi.usergroup.update(kwargs)
except Exception as e:
self._module.fail_json(msg='Failed to update user group "%s": %s' % (kwargs['usrgrpid'], e))
def add(self, **kwargs):
"""Add user group.
Parameters:
**kwargs: Arbitrary keyword parameters.
Returns:
usergroup: added user group
"""
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
parameters = self._construct_parameters(**kwargs)
usergroup = self._zapi.usergroup.create(parameters)
return usergroup['usrgrpids'][0]
except Exception as e:
self._module.fail_json(msg='Failed to create user group "%s": %s' % (kwargs['name'], e))
def delete(self, usrgrpid):
"""Delete user group.
Parameters:
usrgrpid: User group id.
Returns:
usergroup: deleted user group
"""
try:
if self._module.check_mode:
self._module.exit_json(changed=True)
else:
return self._zapi.usergroup.delete([usrgrpid])
except Exception as e:
self._module.fail_json(msg='Failed to delete user group "%s": %s' % (usrgrpid, e))
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(
name=dict(type='str', required=True, aliases=['user_group']),
gui_access=dict(type='str', required=False, default='default', choices=['default', 'internal', 'LDAP', 'disable']),
debug_mode=dict(type='str', required=False, default='disabled', choices=['disabled', 'enabled']),
status=dict(type='str', required=False, default='enabled', choices=['enabled', 'disabled']),
rights=dict(type='list', elements='dict', required=False, options=dict(
host_group=dict(type='str', required=True),
permission=dict(type='str', required=True, choices=['denied', 'read-only', 'read-write'])
)),
tag_filters=dict(type='list', elements='dict', required=False, options=dict(
host_group=dict(type='str', required=True),
tag=dict(type='str', default=''),
value=dict(type='str', default='')
)),
state=dict(type='str', default='present', choices=['present', 'absent'])
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
name = module.params['name']
gui_access = module.params['gui_access']
debug_mode = module.params['debug_mode']
status = module.params['status']
rights = module.params['rights']
tag_filters = module.params['tag_filters']
state = module.params['state']
userGroup = UserGroup(module)
zbx = userGroup._zapi
rgts = Rights(module, zbx)
tgflts = TagFilters(module, zbx)
usergroup_exists = userGroup.check_if_usergroup_exists(name)
if usergroup_exists:
usrgrpid = userGroup.get_usergroup_by_usergroup_name(name)['usrgrpid']
if state == 'absent':
userGroup.delete(usrgrpid)
module.exit_json(changed=True, state=state, usergroup=name, usrgrpid=usrgrpid, msg='User group deleted: %s, ID: %s' % (name, usrgrpid))
else:
difference = userGroup.check_difference(
usrgrpid=usrgrpid,
name=name,
gui_access=gui_access,
debug_mode=debug_mode,
status=status,
rights=rgts.construct_the_data(rights),
tag_filters=tgflts.construct_the_data(tag_filters)
)
if difference == {}:
module.exit_json(changed=False, state=state, usergroup=name, usrgrpid=usrgrpid, msg='User group is up to date: %s' % name)
else:
userGroup.update(
usrgrpid=usrgrpid,
**difference
)
module.exit_json(changed=True, state=state, usergroup=name, usrgrpid=usrgrpid, msg='User group updated: %s, ID: %s' % (name, usrgrpid))
else:
if state == 'absent':
module.exit_json(changed=False, state=state, usergroup=name, msg='User group %s does not exists, nothing to delete' % name)
else:
usrgrpid = userGroup.add(
name=name,
gui_access=gui_access,
debug_mode=debug_mode,
status=status,
rights=rgts.construct_the_data(rights),
tag_filters=tgflts.construct_the_data(tag_filters)
)
module.exit_json(changed=True, state=state, usergroup=name, usrgrpid=usrgrpid, msg='User group created: %s, ID: %s' % (name, usrgrpid))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,318 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Ruben Tsirunyan <rubentsirunyan@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: zabbix_valuemap
short_description: Create/update/delete Zabbix value maps
description:
- This module allows you to create, modify and delete Zabbix value maps.
author:
- "Ruben Tsirunyan (@rubentsirunyan)"
requirements:
- "python >= 2.6"
options:
name:
type: 'str'
description:
- Name of the value map.
required: true
state:
type: 'str'
description:
- State of the value map.
- On C(present), it will create a value map if it does not exist or update the value map if the associated data is different.
- On C(absent), it will remove the value map if it exists.
choices: ['present', 'absent']
default: 'present'
mappings:
type: 'list'
elements: dict
description:
- List of value mappings for the value map.
- Required when I(state=present).
suboptions:
value:
type: 'str'
description: Original value.
required: true
map_to:
type: 'str'
description: Value to which the original value is mapped to.
required: true
extends_documentation_fragment:
- community.zabbix.zabbix
'''
RETURN = r'''
'''
EXAMPLES = r'''
# Set following variables for Zabbix Server host in play or inventory
- name: Set connection specific variables
set_fact:
ansible_network_os: community.zabbix.zabbix
ansible_connection: httpapi
ansible_httpapi_port: 80
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
# If you want to use Username and Password to be authenticated by Zabbix Server
- name: Set credentials to access Zabbix Server API
set_fact:
ansible_user: Admin
ansible_httpapi_pass: zabbix
# If you want to use API token to be authenticated by Zabbix Server
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
- name: Set API token
set_fact:
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
- name: Create a value map
community.zabbix.zabbix_valuemap:
name: Numbers
mappings:
- value: 1
map_to: one
- value: 2
map_to: two
state: present
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
def construct_parameters(**kwargs):
"""Translates data to a format suitable for Zabbix API
Args:
**kwargs: Arguments passed to the module.
Returns:
A dictionary of arguments in a format that is understandable by Zabbix API.
"""
if kwargs['mappings'] is None:
return dict(
name=kwargs['name']
)
return dict(
name=kwargs['name'],
mappings=[
dict(
value=mapping['value'],
newvalue=mapping['map_to']
) for mapping in kwargs['mappings']
]
)
def diff(existing, new):
"""Constructs the diff for Ansible's --diff option.
Args:
existing (dict): Existing valuemap data.
new (dict): New valuemap data.
Returns:
A dictionary like {'before': existing, 'after': new}
with filtered empty values.
"""
before = {}
after = {}
for key in new:
before[key] = existing[key]
if new[key] is None:
after[key] = ''
else:
after[key] = new[key]
return {'before': before, 'after': after}
def get_update_params(existing_valuemap, **kwargs):
"""Filters only the parameters that are different and need to be updated.
Args:
existing_valuemap (dict): Existing valuemap.
**kwargs: Parameters for the new valuemap.
Returns:
A tuple where the first element is a dictionary of parameters
that need to be updated and the second one is a dictionary
returned by diff() function with
existing valuemap data and new params passed to it.
"""
params_to_update = {}
if sorted(existing_valuemap['mappings'], key=lambda k: k['value']) != sorted(kwargs['mappings'], key=lambda k: k['value']):
params_to_update['mappings'] = kwargs['mappings']
return params_to_update, diff(existing_valuemap, kwargs)
class ValuemapModule(ZabbixBase):
def check_if_valuemap_exists(self, name):
"""Checks if value map exists.
Args:
name: Zabbix valuemap name
Returns:
tuple: First element is True if valuemap exists and False otherwise.
Second element is a dictionary of valuemap object if it exists.
"""
try:
valuemap_list = self._zapi.valuemap.get({
'output': 'extend',
'selectMappings': 'extend',
'filter': {'name': [name]}
})
if len(valuemap_list) < 1:
return False, None
else:
return True, valuemap_list[0]
except Exception as e:
self._module.fail_json(msg="Failed to get ID of the valuemap '{name}': {e}".format(name=name, e=e))
def delete(self, valuemap_id):
try:
return self._zapi.valuemap.delete([valuemap_id])
except Exception as e:
self._module.fail_json(msg="Failed to delete valuemap '{_id}': {e}".format(_id=valuemap_id, e=e))
def update(self, **kwargs):
try:
self._zapi.valuemap.update(kwargs)
except Exception as e:
self._module.fail_json(msg="Failed to update valuemap '{_id}': {e}".format(_id=kwargs['valuemapid'], e=e))
def create(self, **kwargs):
try:
self._zapi.valuemap.create(kwargs)
except Exception as e:
self._module.fail_json(msg="Failed to create valuemap '{name}': {e}".format(name=kwargs['description'], e=e))
def main():
argument_spec = zabbix_utils.zabbix_common_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
mappings=dict(
type='list',
elements='dict',
options=dict(
value=dict(type='str', required=True),
map_to=dict(type='str', required=True)
)
)
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
['state', 'present', ['mappings']],
]
)
zabbix_utils.require_creds_params(module)
for p in ['server_url', 'login_user', 'login_password', 'timeout', 'validate_certs']:
if p in module.params and not module.params[p] is None:
module.warn('Option "%s" is deprecated with the move to httpapi connection and will be removed in the next release' % p)
vm = ValuemapModule(module)
name = module.params['name']
state = module.params['state']
mappings = module.params['mappings']
valuemap_exists, valuemap_object = vm.check_if_valuemap_exists(name)
parameters = construct_parameters(
name=name,
mappings=mappings
)
if valuemap_exists:
valuemap_id = valuemap_object['valuemapid']
if state == 'absent':
if module.check_mode:
module.exit_json(
changed=True,
msg="Value map would have been deleted. Name: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
valuemap_id = vm.delete(valuemap_id)
module.exit_json(
changed=True,
msg="Value map deleted. Name: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
else:
params_to_update, diff = get_update_params(valuemap_object, **parameters)
if params_to_update == {}:
module.exit_json(
changed=False,
msg="Value map is up to date: {name}".format(name=name)
)
else:
if module.check_mode:
module.exit_json(
changed=True,
diff=diff,
msg="Value map would have been updated. Name: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
valuemap_id = vm.update(valuemapid=valuemap_id, **params_to_update)
module.exit_json(
changed=True,
diff=diff,
msg="Value map updated. Name: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
else:
if state == "absent":
module.exit_json(changed=False)
else:
if module.check_mode:
module.exit_json(
changed=True,
msg="Value map would have been created. Name: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
valuemap_id = vm.create(**parameters)
module.exit_json(
changed=True,
msg="Value map created: {name}, ID: {_id}".format(
name=name,
_id=valuemap_id
)
)
if __name__ == '__main__':
main()