From 5385de9f445edebaca4b869e7e5b7cc94b048554 Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Tue, 4 Jun 2024 15:04:05 +0300 Subject: [PATCH 1/6] added support for new version of Zabbix --- .github/scripts/compatibility_api_test_7.py | 451 ++++++++++++++++++++ .github/workflows/compatibility_70.yaml | 90 ++++ README.md | 4 +- 3 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/compatibility_api_test_7.py create mode 100644 .github/workflows/compatibility_70.yaml diff --git a/.github/scripts/compatibility_api_test_7.py b/.github/scripts/compatibility_api_test_7.py new file mode 100644 index 0000000..ba85b4e --- /dev/null +++ b/.github/scripts/compatibility_api_test_7.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file under the MIT License. +# See the LICENSE file in the project root for more information. + +import sys +import time +import unittest + +sys.path.append('.') +from zabbix_utils.api import ZabbixAPI +from zabbix_utils.sender import Sender +from zabbix_utils.getter import Getter +from zabbix_utils.aioapi import AsyncZabbixAPI +from zabbix_utils.aiosender import AsyncSender +from zabbix_utils.aiogetter import AsyncGetter +from zabbix_utils.exceptions import APIRequestError +from zabbix_utils.types import AgentResponse, ItemValue, TrapperResponse, APIVersion + +ZABBIX_URL = '127.0.0.1' +ZABBIX_USER = 'Admin' +ZABBIX_PASSWORD = 'zabbix' + + +class CompatibilityAPITest(unittest.TestCase): + """Compatibility synchronous test with Zabbix API version 7.0""" + + def setUp(self): + self.url = ZABBIX_URL + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.token_id = None + self.token = None + self.zapi = ZabbixAPI( + url=self.url + ) + self._create_token() + + def _create_token(self): + """Tests auth using username and password""" + + self.assertEqual( + type(self.zapi), ZabbixAPI, "Creating ZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + self.zapi.login( + user=self.user, + password=self.password + ) + + self.assertIsNotNone(self.zapi._ZabbixAPI__session_id, "Login by user and password was going wrong") + + resp = self.zapi.user.checkAuthentication(sessionid=self.zapi._ZabbixAPI__session_id) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + tokens = self.zapi.token.get( + filter={'name': f"{self.user} [{self.__class__.__name__}]"}, + output=['tokenid'] + ) + + if tokens: + self.token_id = int(tokens[0]['tokenid']) + self.assertEqual( + type(self.token_id), int, "Request token.get was going wrong") + else: + self.token_id = int(self.zapi.token.create( + name=f"{self.user} [{self.__class__.__name__}]" + )['tokenids'][0]) + self.assertEqual( + type(self.token_id), int, "Request token.create was going wrong") + + self.token = self.zapi.token.generate(*[self.token_id])[0]['token'] + self.assertEqual(type(self.token), str, "Request token.generate was going wrong") + + self.zapi.logout() + + self.assertIsNone(self.zapi._ZabbixAPI__session_id, "Logout was going wrong") + + with self.assertRaises(APIRequestError, + msg="Request user.checkAuthentication after logout was going wrong"): + resp = self.zapi.user.checkAuthentication(sessionid=(self.zapi._ZabbixAPI__session_id or '')) + + def test_classic_auth(self): + """Tests auth using username and password""" + + self._create_token() + + def test_token_auth(self): + """Tests auth using token""" + + self.assertEqual( + type(self.zapi), ZabbixAPI, "Creating ZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + self.zapi.login(token=self.token) + + self.assertIsNotNone(self.zapi._ZabbixAPI__session_id, "Login by token was going wrong") + + resp = self.zapi.user.checkAuthentication(token=self.token) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + +class CompatibilitySenderTest(unittest.TestCase): + """Compatibility synchronous test with Zabbix sender version 7.0""" + + def setUp(self): + self.ip = ZABBIX_URL + self.port = 10051 + self.chunk_size = 10 + self.sender = Sender( + server=self.ip, + port=self.port, + chunk_size=self.chunk_size + ) + self.hostname = f"{self.__class__.__name__}_host" + self.itemname = f"{self.__class__.__name__}_item" + self.itemkey = f"{self.__class__.__name__}" + self.prepare_items() + + def prepare_items(self): + """Creates host and items for sending values later""" + + zapi = ZabbixAPI( + url=ZABBIX_URL, + user=ZABBIX_USER, + password=ZABBIX_PASSWORD, + skip_version_check=True + ) + + hosts = zapi.host.get( + filter={'host': self.hostname}, + output=['hostid'] + ) + + hostid = None + if len(hosts) > 0: + hostid = hosts[0].get('hostid') + + if not hostid: + hostid = zapi.host.create( + host=self.hostname, + interfaces=[{ + "type": 1, + "main": 1, + "useip": 1, + "ip": "127.0.0.1", + "dns": "", + "port": "10050" + }], + groups=[{"groupid": "2"}] + )['hostids'][0] + + self.assertIsNotNone(hostid, "Creating test host was going wrong") + + items = zapi.item.get( + filter={'key_': self.itemkey}, + output=['itemid'] + ) + + itemid = None + if len(items) > 0: + itemid = items[0].get('itemid') + + if not itemid: + itemid = zapi.item.create( + name=self.itemname, + key_=self.itemkey, + hostid=hostid, + type=2, + value_type=3 + )['itemids'][0] + + time.sleep(2) + + self.assertIsNotNone(hostid, "Creating test item was going wrong") + + zapi.logout() + + def test_send_values(self): + """Tests sending item values""" + + items = [ + ItemValue(self.hostname, self.itemkey, 10), + ItemValue(self.hostname, self.itemkey, 'test message'), + ItemValue(self.hostname, 'item_key1', -1, 1695713666), + ItemValue(self.hostname, 'item_key2', '{"msg":"test message"}'), + ItemValue(self.hostname, self.itemkey, 0, 1695713666, 100), + ItemValue(self.hostname, self.itemkey, 5.5, 1695713666) + ] + resp = self.sender.send(items) + self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(resp.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(resp.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(resp.failed, (resp.total - resp.processed), "Number of the failed values is unexpected") + + first_chunk = list(resp.details.values())[0][0] + self.assertEqual(type(first_chunk), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(first_chunk.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(first_chunk.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(first_chunk.failed, (first_chunk.total - first_chunk.processed), "Number of the failed values is unexpected") + + +class CompatibilityGetTest(unittest.TestCase): + """Compatibility synchronous test with Zabbix get version 7.0""" + + def setUp(self): + self.host = ZABBIX_URL + self.port = 10050 + self.agent = Getter( + host=self.host, + port=self.port + ) + + def test_get_values(self): + """Tests getting item values""" + + resp = self.agent.get('system.uname') + + self.assertIsNotNone(resp, "Getting item values was going wrong") + self.assertEqual(type(resp), AgentResponse, "Got value is unexpected") + self.assertEqual(type(resp.value), str, "Got value is unexpected") + + +class CompatibilityAsyncAPITest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix API version 7.0""" + + async def asyncSetUp(self): + self.url = ZABBIX_URL + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.token_id = None + self.token = None + self.zapi = AsyncZabbixAPI( + url=self.url + ) + await self._create_token() + + async def asyncTearDown(self): + if self.zapi: + await self.zapi.logout() + + async def _create_token(self): + """Tests auth using username and password""" + + self.assertEqual( + type(self.zapi), AsyncZabbixAPI, "Creating AsyncZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + await self.zapi.login( + user=self.user, + password=self.password + ) + + self.assertIsNotNone(self.zapi._AsyncZabbixAPI__session_id, "Login by user and password was going wrong") + + resp = await self.zapi.user.checkAuthentication(sessionid=self.zapi._AsyncZabbixAPI__session_id) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + tokens = await self.zapi.token.get( + filter={'name': f"{self.user} [{self.__class__.__name__}]"}, + output=['tokenid'] + ) + + if tokens: + self.token_id = int(tokens[0]['tokenid']) + self.assertEqual( + type(self.token_id), int, "Request token.get was going wrong") + else: + created_token = await self.zapi.token.create( + name=f"{self.user} [{self.__class__.__name__}]" + ) + self.token_id = int(created_token['tokenids'][0]) + self.assertEqual( + type(self.token_id), int, "Request token.create was going wrong") + + generated_token = await self.zapi.token.generate(*[self.token_id]) + self.token = generated_token[0]['token'] + self.assertEqual(type(self.token), str, "Request token.generate was going wrong") + + async def test_classic_auth(self): + """Tests auth using username and password""" + + await self._create_token() + + async def test_token_auth(self): + """Tests auth using token""" + + self.assertEqual( + type(self.zapi), AsyncZabbixAPI, "Creating AsyncZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + await self.zapi.login(token=self.token) + + self.assertIsNotNone(self.zapi._AsyncZabbixAPI__session_id, "Login by token was going wrong") + + resp = await self.zapi.user.checkAuthentication(token=self.token) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + await self.zapi.logout() + + self.assertIsNone(self.zapi._AsyncZabbixAPI__session_id, "Logout was going wrong") + + with self.assertRaises(RuntimeError, + msg="Request user.checkAuthentication after logout was going wrong"): + resp = await self.zapi.user.checkAuthentication(sessionid=(self.zapi._AsyncZabbixAPI__session_id or '')) + + +class CompatibilityAsyncSenderTest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix sender version 7.0""" + + async def asyncSetUp(self): + self.ip = ZABBIX_URL + self.port = 10051 + self.chunk_size = 10 + self.sender = AsyncSender( + server=self.ip, + port=self.port, + chunk_size=self.chunk_size + ) + self.hostname = f"{self.__class__.__name__}_host" + self.itemname = f"{self.__class__.__name__}_item" + self.itemkey = f"{self.__class__.__name__}" + await self.prepare_items() + + async def prepare_items(self): + """Creates host and items for sending values later""" + + zapi = AsyncZabbixAPI( + url=ZABBIX_URL, + skip_version_check=True + ) + await zapi.login( + user=ZABBIX_USER, + password=ZABBIX_PASSWORD + ) + + hosts = await zapi.host.get( + filter={'host': self.hostname}, + output=['hostid'] + ) + + hostid = None + if len(hosts) > 0: + hostid = hosts[0].get('hostid') + + if not hostid: + created_host = await zapi.host.create( + host=self.hostname, + interfaces=[{ + "type": 1, + "main": 1, + "useip": 1, + "ip": "127.0.0.1", + "dns": "", + "port": "10050" + }], + groups=[{"groupid": "2"}] + ) + hostid = created_host['hostids'][0] + + self.assertIsNotNone(hostid, "Creating test host was going wrong") + + items = await zapi.item.get( + filter={'key_': self.itemkey}, + output=['itemid'] + ) + + itemid = None + if len(items) > 0: + itemid = items[0].get('itemid') + + if not itemid: + created_item = await zapi.item.create( + name=self.itemname, + key_=self.itemkey, + hostid=hostid, + type=2, + value_type=3 + ) + itemid = created_item['itemids'][0] + + self.assertIsNotNone(hostid, "Creating test item was going wrong") + + await zapi.logout() + + async def test_send_values(self): + """Tests sending item values""" + + time.sleep(2) + + items = [ + ItemValue(self.hostname, self.itemkey, 10), + ItemValue(self.hostname, self.itemkey, 'test message'), + ItemValue(self.hostname, 'item_key1', -1, 1695713666), + ItemValue(self.hostname, 'item_key2', '{"msg":"test message"}'), + ItemValue(self.hostname, self.itemkey, 0, 1695713666, 100), + ItemValue(self.hostname, self.itemkey, 5.5, 1695713666) + ] + resp = await self.sender.send(items) + self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(resp.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(resp.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(resp.failed, (resp.total - resp.processed), "Number of the failed values is unexpected") + + first_chunk = list(resp.details.values())[0][0] + self.assertEqual(type(first_chunk), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(first_chunk.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(first_chunk.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(first_chunk.failed, (first_chunk.total - first_chunk.processed), "Number of the failed values is unexpected") + + +class CompatibilityAsyncGetTest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix get version 7.0""" + + async def asyncSetUp(self): + self.host = ZABBIX_URL + self.port = 10050 + self.agent = AsyncGetter( + host=self.host, + port=self.port + ) + + async def test_get_values(self): + """Tests getting item values""" + + resp = await self.agent.get('system.uname') + + self.assertIsNotNone(resp, "Getting item values was going wrong") + self.assertEqual(type(resp), AgentResponse, "Got value is unexpected") + self.assertEqual(type(resp.value), str, "Got value is unexpected") + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/workflows/compatibility_70.yaml b/.github/workflows/compatibility_70.yaml new file mode 100644 index 0000000..9955570 --- /dev/null +++ b/.github/workflows/compatibility_70.yaml @@ -0,0 +1,90 @@ +name: zabbix_70 +run-name: Compatibility with Zabbix 7.0 test + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + ZABBIX_VERSION: '7.0' + ZABBIX_BRANCH: release/$ZABBIX_VERSION + CONFIG_PATH: .github/configs/ + TEST_FILE: compatibility_api_test_7.py + +jobs: + compatibility: + name: Compatibility test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: | + sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-14 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.1-pgsql php8.1-bcmath php8.1-xml php8.1-gd php8.1-ldap php8.1-mbstring libzip-dev + - name: Build from sources + run: | + WORKDIR=$(pwd) + cd /tmp/ + git -c advice.detachedHead=false clone https://git.zabbix.com/scm/zbx/zabbix.git --branch ${{ env.ZABBIX_BRANCH }} --depth 1 --single-branch /tmp/zabbix-branch + cd /tmp/zabbix-branch + ./bootstrap.sh + ./configure --enable-server --enable-agent --with-postgresql + sudo make dbschema_postgresql + sudo make + echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf + cd ui + sudo rm /var/www/html/index.html + sudo cp -a . /var/www/html/ + sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ + sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf + sudo chown -R www-data:www-data /var/www/html/ + sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini + sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini + sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini + sudo locale-gen en_US.UTF-8 + sudo update-locale + - name: Prepare environment + run: | + sudo addgroup --system --quiet zabbix + sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix + sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp + sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp + sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp + (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& + sleep 5 + cd /tmp/zabbix-branch/database/postgresql + sudo -u postgres createuser zabbix + sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix + cat schema.sql | sudo -u zabbix psql zabbix + cat images.sql | sudo -u zabbix psql zabbix + cat data.sql | sudo -u zabbix psql zabbix + sudo apache2ctl start + - name: Start Zabbix server + run: | + cd /tmp/zabbix-branch + sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf + - name: Start Zabbix agent + run: | + cd /tmp/zabbix-branch + sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf + - name: Install python3 + run: | + sudo apt-get install -y python3 python3-pip python-is-python3 + pip install -r ./requirements.txt + - name: Wait for Zabbix API + run: | + python ./.github/scripts/wait_instance_zabbix.py + - name: Compatibility test + continue-on-error: true + run: | + python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null + - name: Send report + env: + TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} + TBOT_CHAT: ${{ vars.TBOT_CHAT }} + SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL + run: | + tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 diff --git a/README.md b/README.md index 779e0fc..73aa2da 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ [![Zabbix API](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_api.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_api.yaml) [![Zabbix sender](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_sender.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_sender.yaml) [![Zabbix get](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_getter.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_getter.yaml) + [![Zabbix 5.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_50.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_50.yaml) [![Zabbix 6.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_60.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_60.yaml) [![Zabbix 6.4](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_64.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_64.yaml) +[![Zabbix 7.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_70.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_70.yaml) **zabbix_utils** is a Python library for working with [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference) as well as with [Zabbix sender](https://www.zabbix.com/documentation/current/manpages/zabbix_sender) and [Zabbix get](https://www.zabbix.com/documentation/current/manpages/zabbix_get) protocols. @@ -27,7 +29,7 @@ Supported versions: Tested on: -* Zabbix 5.0, 6.0, 6.4 and pre-7.0 +* Zabbix 5.0, 6.0, 6.4 and 7.0 * Python 3.8, 3.9, 3.10, 3.11 and 3.12 Dependencies: From d4f254abb9356fafc719e76a0040da73d78b5308 Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Thu, 6 Jun 2024 14:15:56 +0300 Subject: [PATCH 2/6] fixed github workflow action scripts for compatibility with the latest Zabbix version --- .github/scripts/compatibility_api_test_latest.py | 6 ++++-- .github/scripts/wait_instance_zabbix.py | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/scripts/compatibility_api_test_latest.py b/.github/scripts/compatibility_api_test_latest.py index 48b8da2..05f5fcb 100644 --- a/.github/scripts/compatibility_api_test_latest.py +++ b/.github/scripts/compatibility_api_test_latest.py @@ -33,7 +33,8 @@ def setUp(self): self.token_id = None self.token = None self.zapi = ZabbixAPI( - url=self.url + url=self.url, + skip_version_check=True ) self._create_token() @@ -240,7 +241,8 @@ async def asyncSetUp(self): self.token_id = None self.token = None self.zapi = AsyncZabbixAPI( - url=self.url + url=self.url, + skip_version_check=True ) await self._create_token() diff --git a/.github/scripts/wait_instance_zabbix.py b/.github/scripts/wait_instance_zabbix.py index 96e90d3..a4375fa 100644 --- a/.github/scripts/wait_instance_zabbix.py +++ b/.github/scripts/wait_instance_zabbix.py @@ -12,7 +12,12 @@ for x in range(20): try: - zapi = ZabbixAPI(url="localhost", user="Admin", password="zabbix") + zapi = ZabbixAPI( + url="localhost", + user="Admin", + password="zabbix", + skip_version_check=True + ) except APIRequestError as error: print(f'Zabbix API is not ready... Data: {error}', flush=True) time.sleep(5) From 442cc87e3aa6567c2231c64debca74aa3881fd6d Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Fri, 19 Jul 2024 12:37:43 +0300 Subject: [PATCH 3/6] added instructions for installing the library from packages --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73aa2da..c91adaf 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ Dependencies: * [aiohttp](https://github.com/aio-libs/aiohttp) (in case of async use) -## Documentation +## Installation -### Installation +### Installation from PyPI Install **zabbix_utils** library using pip: @@ -52,6 +52,56 @@ To install the library with dependencies for asynchronous work use the following $ pip install zabbix_utils[async] ``` +### Installation from Zabbix repository + +First of all, you need to install Zabbix repository. Official Zabbix packages for Red Hat Enterprise Linux and derivatives, as well as for Debian and derivatives are available on [Zabbix website](https://www.zabbix.com/download). + +**Red Hat Enterprise Linux and derivatives** + +Install **zabbix_utils** library from Zabbix repository: + +```bash +# dnf install python3-zabbix-utils +``` + +To install additional dependencies such as aiohttp for asynchronous work use the following way: +```bash +# dnf install epel-release +# dnf install python3-aiohttp +``` + +**Debian / Ubuntu and derivatives** + +Install **zabbix_utils** library from Zabbix repository: + +```bash +# apt install python3-zabbix-utils +``` + +To install additional dependencies such as aiohttp for asynchronous work use the following way: +```bash +# apt install python3-aiohttp +``` + +### Installation from GitHub + +Clone the **zabbix_utils** repository: + +```bash +$ git clone https://github.com/zabbix/python-zabbix-utils +``` + +Install **zabbix_utils** library using setup.py: + +```bash +$ cd python-zabbix-utils-master/ +$ python3 setup.py install +``` + +To install dependencies use one of the ways above. + +## Documentation + ### Use cases ##### To work with Zabbix API From 99dc3a158210ec749b9186471a2b301e4031a3c0 Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Wed, 18 Sep 2024 10:49:35 +0300 Subject: [PATCH 4/6] added support of SSL connection configuration for Zabbix API --- .github/configs/default.conf | 12 +- .github/configs/nginx.cnf | 19 +++ .github/configs/nginx.crt | 23 --- .github/configs/nginx.key | 28 ---- .github/scripts/additional_api_tests.py | 136 +++++++++++++++++- .github/scripts/library_import_tests.sh | 12 +- .github/workflows/additional_tests.yaml | 9 +- .../api/asynchronous/custom_ssl_context.py | 62 ++++++++ .../api/synchronous/custom_ssl_context.py | 36 +++++ tests/common.py | 8 ++ zabbix_utils/aioapi.py | 4 +- zabbix_utils/api.py | 12 +- 12 files changed, 295 insertions(+), 66 deletions(-) create mode 100644 .github/configs/nginx.cnf delete mode 100644 .github/configs/nginx.crt delete mode 100644 .github/configs/nginx.key create mode 100644 examples/api/asynchronous/custom_ssl_context.py create mode 100644 examples/api/synchronous/custom_ssl_context.py diff --git a/.github/configs/default.conf b/.github/configs/default.conf index 6c92c5b..3548a6e 100644 --- a/.github/configs/default.conf +++ b/.github/configs/default.conf @@ -4,11 +4,11 @@ server { root /var/www/html/; index index.php; - server_name 127.0.0.1; + server_name localhost 127.0.0.1; ssl_certificate /etc/nginx/ssl/nginx.crt; ssl_certificate_key /etc/nginx/ssl/nginx.key; - location / { + location /http_auth/ { auth_basic "Zabbix HTTP Auth"; auth_basic_user_file /etc/nginx/.htpasswd; @@ -18,4 +18,12 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffering on; } + + location /ssl_context/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_buffering on; + } } \ No newline at end of file diff --git a/.github/configs/nginx.cnf b/.github/configs/nginx.cnf new file mode 100644 index 0000000..83b8ab0 --- /dev/null +++ b/.github/configs/nginx.cnf @@ -0,0 +1,19 @@ +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +req_extensions = req_ext +x509_extensions = v3_req +prompt = no +[req_distinguished_name] +countryName = LV +localityName = Riga +organizationName = Zabbix SIA +organizationalUnitName = Integration team +emailAddress = integrationteam@zabbix.com +[req_ext] +subjectAltName = @alt_names +[v3_req] +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 \ No newline at end of file diff --git a/.github/configs/nginx.crt b/.github/configs/nginx.crt deleted file mode 100644 index 3d2b137..0000000 --- a/.github/configs/nginx.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDzzCCAregAwIBAgIUfvqWoC2rRssyrQe3JruyCkQFOpYwDQYJKoZIhvcNAQEL -BQAwdzELMAkGA1UEBhMCTFYxDTALBgNVBAgMBFJpZ2ExEzARBgNVBAoMClphYmJp -eCBTSUExGTAXBgNVBAsMEEludGVncmF0aW9uIHRlYW0xKTAnBgkqhkiG9w0BCQEW -GmludGVncmF0aW9udGVhbUB6YWJiaXguY29tMB4XDTIzMTEwNzEwMzMwOVoXDTI0 -MTEwNjEwMzMwOVowdzELMAkGA1UEBhMCTFYxDTALBgNVBAgMBFJpZ2ExEzARBgNV -BAoMClphYmJpeCBTSUExGTAXBgNVBAsMEEludGVncmF0aW9uIHRlYW0xKTAnBgkq -hkiG9w0BCQEWGmludGVncmF0aW9udGVhbUB6YWJiaXguY29tMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuW+v+fg7XbldU86WLgZSDDDEaFMZ8GWD5n2l -zJC9gj/pzK6jnPMzWib2dPbH4zHxXGIxToBXut388hiDsGvsM4KZvtf/eFZAsR/E -zMEjgPpv+4ys0ZvDZ2KfHeQBnR06uOYTLw9wqxFmGGYwC6tbaF81fQ4GTYx47Syd -pAof5dlCdjfotombvRE7sbavkoctxKznBsF8LLfmxWzbqc2eZgLfglfDoCTINF0f -pVWt+K2kcSvs1jlBREU9EQsJiJa5QFv0AyK6jRDZFx4DN0wyzAIRcPMHbBZLbMp0 -sNpRP+CYgBtF9uO9HsFQHIIedfL/5N0cOKIFSNyj707RlUELNwIDAQABo1MwUTAd -BgNVHQ4EFgQUbjxYf/ZnBC61ey0UBscYd2dbNR0wHwYDVR0jBBgwFoAUbjxYf/Zn -BC61ey0UBscYd2dbNR0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC -AQEAfYOByo8LdlRafVyi1w5VW1oA9Rc+9TqnRp6PEPSF8UqP6FH32YNpIVoWpz9z -3IMLsroVRMtxLzplBvq4muqFFTLXR4ZUi09arVxIASRmXyM1Fx0wd/ncontNS8zF -SKG+nam6+IHKC2gFq/KkzH1/o1T0d7RBuVMSSeLfBNjkVGBbueWmS2B+HquLG7uk -S/K1/YHF8bI+FIRJoQZtemX9h+MtBGmSNUsIhFWiL7EIPaxXnLgLhgSCBEJL8Zi/ -3ylq9vOIe0vSPdUt2TzZq622D4vkMZsHP5gnt/nao7ncE6QvGUCYNJ6JeF+vScSy -nyxCJk4/jJ0IMsLn7g+ylfonnA== ------END CERTIFICATE----- diff --git a/.github/configs/nginx.key b/.github/configs/nginx.key deleted file mode 100644 index a8cf18d..0000000 --- a/.github/configs/nginx.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5b6/5+DtduV1T -zpYuBlIMMMRoUxnwZYPmfaXMkL2CP+nMrqOc8zNaJvZ09sfjMfFcYjFOgFe63fzy -GIOwa+wzgpm+1/94VkCxH8TMwSOA+m/7jKzRm8NnYp8d5AGdHTq45hMvD3CrEWYY -ZjALq1toXzV9DgZNjHjtLJ2kCh/l2UJ2N+i2iZu9ETuxtq+Shy3ErOcGwXwst+bF -bNupzZ5mAt+CV8OgJMg0XR+lVa34raRxK+zWOUFERT0RCwmIlrlAW/QDIrqNENkX -HgM3TDLMAhFw8wdsFktsynSw2lE/4JiAG0X2470ewVAcgh518v/k3Rw4ogVI3KPv -TtGVQQs3AgMBAAECggEAGHSNTbcaBTNEBqBxF+LHgUoRLP0Ap76LlaMucXsJMxtA -tzi05ScBt7r5t9Uv6VuVzCw6+nJcn41VJYxsGaEyBl0L76pGy9T8NR/OcX5rM0BT -8mXMCNy0+XZ9K5D9UX4gV3AoSYWMw1LJFyCq528S4AHmaPaniCSlcgn3REH9kO9L -oW0iDYHV57Sn+VJc2YBzu4e+i+BMmmXzuV58eExsgYgM6pZ3CAtHIIFsRGe1eZva -GX/iQJLYBswthgULlzeehEye0GIXFEQmo0E0y5+DdxNllQPFy1er4Jxbcc9IITjL -kYYHIo2iZFCVLL/8pydiAo3pt6EIcaZ+PvAKO08a4QKBgQDTrUvEgY5MOJrHYCxA -phQVsFe9r+FX/5OilWaW6nt4MsQaDt9Owu7VJMv366aj6/6WFgUzjmuYWPc7vDec -jwur8Zy7Ku6EoP3ebZqqL0YHkLwnyXcMMuI19zzLZBNws+TQI6U7bVg2ZK0npcok -WlT6+tA8ZZVUmrB1dIO8RSzWpwKBgQDgQ8pcdvoVgbiall27jYpPBhnrp0vxCF4b -/BDghZiPaM9jqKA/HpnhS62RHbV/9Gp3YDd3Tf2vRLCZNqgTEoGNUyf7eSDxwetD -MBumIrO0vJhiEwWKC2XtOk7HHyQ9NfienjpeSJi89h4vPJnIbpwPOUE0IndoECe9 -4j7FenRI8QKBgGeFYf+paKEoMbH9+8ZmMn5k7pQrFubFbDtgG3sFE/s1wKgJy/Yl -6U2+IC6QRb/3I1vdSw/qv/4SzF6fbwcXAhtGH2N93IZqgZ2tNq2yTupWQ16l7Z2+ -qNjBoC7MM6GzqNyOAjntAO4hEHnKX/ClaNgRn5NaDs0AlfnrUJ3bqtETAoGAGiPs -t4BqwA0BzosOW+kwoOtuJAi5ElxOU+a6tOD9mmG2IsCqBRF1EEy463xSBpsfudBu -RqrBkidoikNTrf1vOmDh/S7uRx+6Y3d9EY1j8wI0pOHguGmBtPgh7JXVrIjY9Owu -k4TUwNnTROBEcJdr+15KGmCWGHwhlcGEXZJbcZECgYEAgF863LUJRFjT+bW59oA/ -B3S9YKZ1QG5Pr6lH4VvzILU8wCMVV58M1Y54iySbJ8QB1vaO3ethKxNveRdTOrAp -1w/92ty9WqdKBoRqUqeiuA/+sgP+OOiFM0cUEPg1aYIVAuHLvyHr+zLVfJ9mqgEo -5MuvCiPeJbPgNBtOzuL5GF8= ------END PRIVATE KEY----- diff --git a/.github/scripts/additional_api_tests.py b/.github/scripts/additional_api_tests.py index 08bdebb..1ffe373 100644 --- a/.github/scripts/additional_api_tests.py +++ b/.github/scripts/additional_api_tests.py @@ -5,8 +5,10 @@ # See the LICENSE file in the project root for more information. import sys +import ssl import base64 import unittest +from aiohttp import ClientSession, TCPConnector sys.path.append('.') from zabbix_utils.api import ZabbixAPI @@ -24,9 +26,9 @@ class IntegrationAPITest(unittest.TestCase): """Test working with a real Zabbix API instance synchronously""" def setUp(self): - self.url = ZABBIX_URL self.user = ZABBIX_USER self.password = ZABBIX_PASSWORD + self.url = ZABBIX_URL + '/http_auth/' self.api = ZabbixAPI( url=self.url, user=self.user, @@ -89,13 +91,76 @@ def test_user_get(self): self.assertEqual(type(users), list, "Request user.get was going wrong") +class CustomCertAPITest(unittest.TestCase): + """Test working with a real Zabbix API instance synchronously""" + + def setUp(self): + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.url = ZABBIX_URL + '/ssl_context/' + + context = ssl.create_default_context() + context.load_verify_locations('/etc/nginx/ssl/nginx.crt') + + self.api = ZabbixAPI( + url=self.url, + user=self.user, + password=self.password, + skip_version_check=True, + ssl_context=context + ) + + def tearDown(self): + if self.api: + self.api.logout() + + def test_login(self): + """Tests login function works properly""" + + self.assertEqual( + type(self.api), ZabbixAPI, "Login was going wrong") + self.assertEqual( + type(self.api.api_version()), APIVersion, "Version getting was going wrong") + + def test_version_get(self): + """Tests getting version info works properly""" + + version = None + if self.api: + version = self.api.apiinfo.version() + self.assertEqual( + version, str(self.api.api_version()), "Request apiinfo.version was going wrong") + + def test_check_auth(self): + """Tests checking authentication state works properly""" + + resp = None + if self.api: + if self.api._ZabbixAPI__session_id == self.api._ZabbixAPI__token: + resp = self.api.user.checkAuthentication(token=self.api._ZabbixAPI__session_id) + else: + resp = self.api.user.checkAuthentication(sessionid=self.api._ZabbixAPI__session_id) + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + def test_user_get(self): + """Tests getting users info works properly""" + + users = None + if self.api: + users = self.api.user.get( + output=['userid', 'name'] + ) + self.assertEqual(type(users), list, "Request user.get was going wrong") + + class IntegrationAsyncAPITest(unittest.IsolatedAsyncioTestCase): """Test working with a real Zabbix API instance asynchronously""" async def asyncSetUp(self): - self.url = ZABBIX_URL self.user = ZABBIX_USER self.password = ZABBIX_PASSWORD + self.url = ZABBIX_URL + '/http_auth/' self.api = AsyncZabbixAPI( url=self.url, skip_version_check=True, @@ -163,5 +228,72 @@ async def test_user_get(self): self.assertEqual(type(users), list, "Request user.get was going wrong") +class CustomCertAsyncAPITest(unittest.IsolatedAsyncioTestCase): + """Test working with a real Zabbix API instance asynchronously""" + + async def asyncSetUp(self): + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.url = ZABBIX_URL + '/ssl_context/' + + context = ssl.create_default_context() + context.load_verify_locations('/etc/nginx/ssl/nginx.crt') + session = ClientSession( + connector=TCPConnector(ssl=context) + ) + + self.api = AsyncZabbixAPI( + url=self.url, + skip_version_check=True, + client_session=session + ) + await self.api.login( + user=self.user, + password=self.password + ) + + async def asyncTearDown(self): + if self.api: + await self.api.logout() + + async def test_login(self): + """Tests login function works properly""" + + self.assertEqual( + type(self.api), AsyncZabbixAPI, "Login was going wrong") + self.assertEqual( + type(self.api.api_version()), APIVersion, "Version getting was going wrong") + + async def test_version_get(self): + """Tests getting version info works properly""" + + version = None + if self.api: + version = await self.api.apiinfo.version() + self.assertEqual( + version, str(self.api.api_version()), "Request apiinfo.version was going wrong") + + async def test_check_auth(self): + """Tests checking authentication state works properly""" + + resp = None + if self.api: + if self.api._AsyncZabbixAPI__session_id == self.api._AsyncZabbixAPI__token: + resp = await self.api.user.checkAuthentication(token=(self.api._AsyncZabbixAPI__session_id or '')) + else: + resp = await self.api.user.checkAuthentication(sessionid=(self.api._AsyncZabbixAPI__session_id or '')) + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + async def test_user_get(self): + """Tests getting users info works properly""" + + users = None + if self.api: + users = await self.api.user.get( + output=['userid', 'name'] + ) + self.assertEqual(type(users), list, "Request user.get was going wrong") + if __name__ == '__main__': unittest.main() diff --git a/.github/scripts/library_import_tests.sh b/.github/scripts/library_import_tests.sh index 1de8649..e505801 100644 --- a/.github/scripts/library_import_tests.sh +++ b/.github/scripts/library_import_tests.sh @@ -1,7 +1,13 @@ #!/bin/bash -class=$1 -error=$2 +mode=$1 +class=$2 +error=$3 -result=$(python3 -c "import sys; sys.path.append('.'); from zabbix_utils import $class; $class()" 2>&1) +cmd="import sys; sys.path.append('.'); from zabbix_utils import $class; $class()" +if [ $mode == "async" ]; then + cmd="import sys; import asyncio; sys.path.append('.'); from zabbix_utils import $class; exec('async def main():\n $class()'); asyncio.run(main())" +fi + +result=$(python3 -c "$cmd" 2>&1) echo "$result" | grep "$error" >/dev/null || echo "$result" | (python3 "./.github/scripts/telegram_msg.py" && echo "Error") diff --git a/.github/workflows/additional_tests.yaml b/.github/workflows/additional_tests.yaml index e83a1b2..be6bc60 100644 --- a/.github/workflows/additional_tests.yaml +++ b/.github/workflows/additional_tests.yaml @@ -33,7 +33,7 @@ jobs: TBOT_CHAT: ${{ vars.TBOT_CHAT }} SUBJECT: Importing test without requirements FAIL run: | - bash ./.github/scripts/library_import_tests.sh "ZabbixAPI" "Unable to connect to" > /tmp/importing.log + bash ./.github/scripts/library_import_tests.sh "sync" "ZabbixAPI" "Unable to connect to" > /tmp/importing.log - name: Check import of async without requirements continue-on-error: true env: @@ -41,7 +41,7 @@ jobs: TBOT_CHAT: ${{ vars.TBOT_CHAT }} SUBJECT: Importing test without requirements FAIL run: | - bash ./.github/scripts/library_import_tests.sh "AsyncZabbixAPI" "ModuleNotFoundError:" > /tmp/importing.log + bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "ModuleNotFoundError:" > /tmp/importing.log - name: Install requirements run: | pip install -r ./requirements.txt @@ -52,7 +52,7 @@ jobs: TBOT_CHAT: ${{ vars.TBOT_CHAT }} SUBJECT: Importing tests with requirements FAIL run: | - bash ./.github/scripts/library_import_tests.sh "AsyncZabbixAPI" "aiohttp.client.ClientSession" > /tmp/importing.log + bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "aiohttp.client.ClientSession" > /tmp/importing.log - name: Raise an exception run: | test $(cat /tmp/importing.log | wc -l) -eq 0 || exit 1 @@ -77,9 +77,8 @@ jobs: echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf sudo mkdir -p /etc/nginx/ssl/ sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/.htpasswd /etc/nginx/.htpasswd - sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/nginx.crt /etc/nginx/ssl/nginx.crt - sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/nginx.key /etc/nginx/ssl/nginx.key sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/default.conf /etc/nginx/sites-enabled/default + sudo openssl req -x509 -nodes -days 1 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt -config $WORKDIR/${{ env.CONFIG_PATH }}/nginx.cnf sudo chown -R www-data:www-data /etc/nginx/ cd ui sudo rm /var/www/html/index.html diff --git a/examples/api/asynchronous/custom_ssl_context.py b/examples/api/asynchronous/custom_ssl_context.py new file mode 100644 index 0000000..70cddfc --- /dev/null +++ b/examples/api/asynchronous/custom_ssl_context.py @@ -0,0 +1,62 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +import asyncio +from zabbix_utils import AsyncZabbixAPI +from aiohttp import ClientSession, TCPConnector + + +# Zabbix server URL or IP address +ZABBIX_SERVER = "https://example.com" + +# Zabbix server authentication credentials +ZABBIX_AUTH = { + "user": "Admin", # Zabbix user name for authentication + "password": "zabbix" # Zabbix user password for authentication +} + + +async def main(): + """ + The main function to perform asynchronous tasks. + """ + + # Create a default SSL context for secure connections + # Load a custom certificate from the specified file path to verify the server + ctx = ssl.create_default_context() + ctx.load_verify_locations("/path/to/certificate.crt") + + # Create an asynchronous client session for HTTP requests + client_session = ClientSession( + connector=TCPConnector(ssl=ctx) + ) + + # Create an instance of the AsyncZabbixAPI class + api = AsyncZabbixAPI( + url=ZABBIX_SERVER, + client_session=client_session + ) + + # Authenticating with Zabbix API using the provided token. + await api.login(**ZABBIX_AUTH) + + # Retrieve a list of hosts from the Zabbix server, including their host ID and name + hosts = await api.host.get( + output=['hostid', 'name'] + ) + + # Print the names of the retrieved hosts + for host in hosts: + print(host['name']) + + # Logout to release the Zabbix API session + await api.logout() + + # Close asynchronous client session + await client_session.close() + +# Run the main coroutine +asyncio.run(main()) diff --git a/examples/api/synchronous/custom_ssl_context.py b/examples/api/synchronous/custom_ssl_context.py new file mode 100644 index 0000000..91f7fb4 --- /dev/null +++ b/examples/api/synchronous/custom_ssl_context.py @@ -0,0 +1,36 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +from zabbix_utils import ZabbixAPI + +# Create a default SSL context for secure connections +# Load a custom certificate from the specified file path to verify the server +ctx = ssl.create_default_context() +ctx.load_verify_locations("/path/to/certificate.crt") + +# Create an instance of the ZabbixAPI class with the Zabbix server URL +# Set ssl_context value for Zabbix API requests. +ZABBIX_AUTH = { + "url": "https://example.com", + "user": "Admin", + "password": "zabbix", + "ssl_context": ctx +} + +# Login to the Zabbix API using provided user credentials +api = ZabbixAPI(**ZABBIX_AUTH) + +# Retrieve a list of hosts from the Zabbix server, including their host ID and name +hosts = api.host.get( + output=['hostid', 'name'] +) + +# Print the names of the retrieved hosts +for host in hosts: + print(host['name']) + +# Logout to release the Zabbix API session +api.logout() diff --git a/tests/common.py b/tests/common.py index 4446acf..cab5887 100644 --- a/tests/common.py +++ b/tests/common.py @@ -78,15 +78,23 @@ class MockBasicAuth(): login = API_DEFAULTS['user'] password = API_DEFAULTS['password'] +class MockSessionConn(): + def __init__(self): + self._ssl = None class MockSession(): def __init__(self, exception=None): self._default_auth = None + self._connector = MockSessionConn() self.EXC = exception def set_auth(self): self._default_auth = MockBasicAuth() def del_auth(self): self._default_auth = None + def set_ssl(self, ssl): + self._connector._ssl = ssl + def del_ssl(self): + self._connector._ssl = None def set_exception(self, exception): self.EXC = exception def del_exception(self): diff --git a/zabbix_utils/aioapi.py b/zabbix_utils/aioapi.py index 9d2bbf8..97290ad 100644 --- a/zabbix_utils/aioapi.py +++ b/zabbix_utils/aioapi.py @@ -34,7 +34,7 @@ from os import environ as env from urllib.error import URLError -from typing import Callable, Union, Optional, Any, List +from typing import Callable, Union, Optional, Any from aiohttp.client_exceptions import ContentTypeError from .types import APIVersion @@ -420,6 +420,8 @@ def send_sync_request(self, method: str, params: Optional[dict] = None, ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE + elif not isinstance(self.client_session._connector._ssl, bool): + ctx = self.client_session._connector._ssl else: ctx = None diff --git a/zabbix_utils/api.py b/zabbix_utils/api.py index c440262..1039757 100644 --- a/zabbix_utils/api.py +++ b/zabbix_utils/api.py @@ -33,7 +33,7 @@ from os import environ as env from urllib.error import URLError -from typing import Callable, Union, Optional, Any +from typing import Callable, Optional, Any from .types import APIVersion from .common import ModuleUtils @@ -119,7 +119,8 @@ class ZabbixAPI(): def __init__(self, url: Optional[str] = None, token: Optional[str] = None, user: Optional[str] = None, password: Optional[str] = None, http_user: Optional[str] = None, http_password: Optional[str] = None, - skip_version_check: bool = False, validate_certs: bool = True, timeout: int = 30): + skip_version_check: bool = False, validate_certs: bool = True, + ssl_context: Optional[ssl.SSLContext] = None, timeout: int = 30): url = url or env.get('ZABBIX_URL') or 'http://localhost/zabbix/api_jsonrpc.php' user = user or env.get('ZABBIX_USER') or None @@ -133,6 +134,11 @@ def __init__(self, url: Optional[str] = None, token: Optional[str] = None, if http_user and http_password: self.__basic_auth(http_user, http_password) + if ssl_context is not None: + if not isinstance(ssl_context, ssl.SSLContext): + raise TypeError('Function "ssl_context" must return "ssl.SSLContext".') from None + self.ssl_context = ssl_context + self.__check_version(skip_version_check) if token or user or password: @@ -337,6 +343,8 @@ def send_api_request(self, method: str, params: Optional[dict] = None, ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE + elif self.ssl_context is not None: + ctx = self.ssl_context else: ctx = None From 02bb86c86ab134d5d02942c960ee391af72bcd55 Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Wed, 18 Sep 2024 10:50:06 +0300 Subject: [PATCH 5/6] updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c91adaf..d891ad0 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ $ git clone https://github.com/zabbix/python-zabbix-utils Install **zabbix_utils** library using setup.py: ```bash -$ cd python-zabbix-utils-master/ +$ cd python-zabbix-utils/ $ python3 setup.py install ``` From ba152b8561eb62e6084e6f4d24bdeb1e67b48bca Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Wed, 18 Sep 2024 10:51:06 +0300 Subject: [PATCH 6/6] updated CHANGELOG and version number --- CHANGELOG.md | 7 +++++++ zabbix_utils/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b7e82..a068122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.1](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.0...v2.0.1) (2024-09-18) + +### Features: + +- added ssl_context argument to ZabbixAPI to allow more flexible configuration of SSL connections +- added support of SSL connection configuration to AsyncZabbixAPI + ## [2.0.0](https://github.com/zabbix/python-zabbix-utils/compare/v1.1.1...v2.0.0) (2024-04-12) ### Features: diff --git a/zabbix_utils/version.py b/zabbix_utils/version.py index 509bc53..7a3edd3 100644 --- a/zabbix_utils/version.py +++ b/zabbix_utils/version.py @@ -22,7 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -__version__ = "2.0.0" +__version__ = "2.0.1" __min_supported__ = 5.0 __max_supported__ = 7.0 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy