diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/zabbix/plugins/module_utils | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/zabbix/plugins/module_utils')
6 files changed, 760 insertions, 0 deletions
diff --git a/ansible_collections/community/zabbix/plugins/module_utils/__init__.py b/ansible_collections/community/zabbix/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/__init__.py diff --git a/ansible_collections/community/zabbix/plugins/module_utils/_version.py b/ansible_collections/community/zabbix/plugins/module_utils/_version.py new file mode 100644 index 000000000..ce027171c --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/_version.py @@ -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 diff --git a/ansible_collections/community/zabbix/plugins/module_utils/api_request.py b/ansible_collections/community/zabbix/plugins/module_utils/api_request.py new file mode 100644 index 000000000..a29f492de --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/api_request.py @@ -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 diff --git a/ansible_collections/community/zabbix/plugins/module_utils/base.py b/ansible_collections/community/zabbix/plugins/module_utils/base.py new file mode 100644 index 000000000..8858a02e1 --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/base.py @@ -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() diff --git a/ansible_collections/community/zabbix/plugins/module_utils/helpers.py b/ansible_collections/community/zabbix/plugins/module_utils/helpers.py new file mode 100644 index 000000000..6c9c0fca5 --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/helpers.py @@ -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 diff --git a/ansible_collections/community/zabbix/plugins/module_utils/wrappers.py b/ansible_collections/community/zabbix/plugins/module_utils/wrappers.py new file mode 100644 index 000000000..a982108f8 --- /dev/null +++ b/ansible_collections/community/zabbix/plugins/module_utils/wrappers.py @@ -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 |